diff --git a/math.js b/math.js index b490f88..03fe783 100644 --- a/math.js +++ b/math.js @@ -36,3 +36,12 @@ export const min = (a, b) => a < b ? a : b export const max = (a, b) => a > b ? a : b export const isNaN = Number.isNaN + +export const pow = Math.pow +/** + * Base 10 exponential function. Returns the value of 10 raised to the power of pow. + * + * @param {number} exp + * @return {number} + */ +export const exp10 = exp => Math.pow(10, exp) diff --git a/metric.js b/metric.js new file mode 100644 index 0000000..ec1b331 --- /dev/null +++ b/metric.js @@ -0,0 +1,48 @@ +import * as math from './math.js' + +export const yotta = 1e24 +export const zetta = 1e21 +export const exa = 1e18 +export const peta = 1e15 +export const tera = 1e12 +export const giga = 1e9 +export const mega = 1e6 +export const kilo = 1e3 +export const hecto = 1e2 +export const deca = 10 +export const deci = .1 +export const centi = .01 +export const milli = 1e-3 +export const micro = 1e-6 +export const nano = 1e-9 +export const pico = 1e-12 +export const femto = 1e-15 +export const atto = 1e-18 +export const zepto = 1e-21 +export const yocto = 1e-24 + +const prefixUp = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] +const prefixDown = ['', 'm', 'μ', 'n', 'p', 'f', 'a', 'z', 'y'] + +/** + * @param {number} n + * @param {number} [baseMultiplier] Multiplier of the base (10^(3*baseMultiplier)). E.g. `convert(time, -3)` if time is already in milli seconds + * @return {{n:number,prefix:string}} + */ +export const prefix = (n, baseMultiplier = 0) => { + const nPow = math.log10(n) + let mult = 0 + while (nPow < mult * 3 && baseMultiplier > -8) { + baseMultiplier-- + mult-- + } + while (nPow >= 3 + mult * 3 && baseMultiplier < 8) { + baseMultiplier++ + mult++ + } + const prefix = baseMultiplier < 0 ? prefixDown[-baseMultiplier] : prefixUp[baseMultiplier] + return { + n: math.round((mult > 0 ? n / math.exp10(mult * 3) : n * math.exp10(mult * -3)) * 1e12) / 1e12, + prefix + } +} diff --git a/metric.test.js b/metric.test.js new file mode 100644 index 0000000..ec58302 --- /dev/null +++ b/metric.test.js @@ -0,0 +1,34 @@ +import * as t from './testing.js' +import * as metric from './metric.js' + +/** + * @param {t.TestCase} tc + */ +export const testMetricPrefix = tc => { + t.compare(metric.prefix(1, -1), { n: 1, prefix: 'm' }) + t.compare(metric.prefix(1.5), { n: 1.5, prefix: '' }) + t.compare(metric.prefix(100.5), { n: 100.5, prefix: '' }) + t.compare(metric.prefix(1000.5), { n: 1.0005, prefix: 'k' }) + t.compare(metric.prefix(.3), { n: 300, prefix: 'm' }) + t.compare(metric.prefix(.001), { n: 1, prefix: 'm' }) + // up + t.compare(metric.prefix(10000), { n: 10, prefix: 'k' }) + t.compare(metric.prefix(1e7), { n: 10, prefix: 'M' }) + t.compare(metric.prefix(1e11), { n: 100, prefix: 'G' }) + t.compare(metric.prefix(1e12 + 3), { n: (1e12 + 3) / 1e12, prefix: 'T' }) + t.compare(metric.prefix(1e15), { n: 1, prefix: 'P' }) + t.compare(metric.prefix(1e20), { n: 100, prefix: 'E' }) + t.compare(metric.prefix(1e22), { n: 10, prefix: 'Z' }) + t.compare(metric.prefix(1e24), { n: 1, prefix: 'Y' }) + t.compare(metric.prefix(1e28), { n: 10000, prefix: 'Y' }) + // down + t.compare(metric.prefix(.01), { n: 10, prefix: 'm' }) + t.compare(metric.prefix(1e-4), { n: 100, prefix: 'μ' }) + t.compare(metric.prefix(1e-9), { n: 1, prefix: 'n' }) + t.compare(metric.prefix(1e-12), { n: 1, prefix: 'p' }) + t.compare(metric.prefix(1e-14), { n: 10, prefix: 'f' }) + t.compare(metric.prefix(1e-18), { n: 1, prefix: 'a' }) + t.compare(metric.prefix(1e-21), { n: 1, prefix: 'z' }) + t.compare(metric.prefix(1e-22), { n: 100, prefix: 'y' }) + t.compare(metric.prefix(1e-30), { n: .000001, prefix: 'y' }) +} diff --git a/test.js b/test.js index ee0ea63..b04cfa5 100644 --- a/test.js +++ b/test.js @@ -22,6 +22,7 @@ import * as number from './number.test.js' import * as buffer from './buffer.test.js' import * as sort from './sort.test.js' import * as url from './url.test.js' +import * as metric from './metric.test.js' import { isBrowser, isNode } from './environment.js' @@ -52,7 +53,8 @@ runTests({ number, buffer, sort, - url + url, + metric }).then(success => { /* istanbul ignore next */ if (isNode) { diff --git a/testing.js b/testing.js index 0ec8af7..09ade85 100644 --- a/testing.js +++ b/testing.js @@ -13,6 +13,7 @@ import * as statistics from './statistics.js' import * as array from './array.js' import * as env from './environment.js' import * as json from './json.js' +import * as time from './time.js' import { performance } from './isomorphic.js' @@ -129,8 +130,8 @@ export const run = async (moduleName, name, f, i, numberOfTests) => { ? ` - ${window.location.href}?filter=\\[${i + 1}/${tc._seed === null ? '' : `&seed=${tc._seed}`}` : `\nrepeat: npm run test -- --filter "\\[${i + 1}/" ${tc._seed === null ? '' : `--seed ${tc._seed}`}` const timeInfo = (repeat && err === null) - ? ` - ${times.length} repititions in ${duration.toFixed(2)}ms (best: ${times[0].toFixed(2)}ms, worst: ${array.last(times).toFixed(2)}ms, median: ${statistics.median(times).toFixed(2)}ms, average: ${statistics.average(times).toFixed(2)}ms)` - : ` in ${duration.toFixed(2)}ms` + ? ` - ${times.length} repititions in ${time.humanizeDuration(duration)} (best: ${time.humanizeDuration(times[0])}, worst: ${time.humanizeDuration(array.last(times))}, median: ${time.humanizeDuration(statistics.median(times))}, average: ${time.humanizeDuration(statistics.average(times))})` + : ` in ${time.humanizeDuration(duration)}` if (err !== null) { /* istanbul ignore else */ if (err.constructor === SkipError) { @@ -190,7 +191,7 @@ export const measureTime = async (message, f) => { iterations++ } const iterationsInfo = iterations > 1 ? `, ${iterations} repititions` : '' - log.print(log.PURPLE, message, log.GREY, ` ${(duration / iterations)}ms${iterationsInfo}`) + log.print(log.PURPLE, message, log.GREY, ` ${time.humanizeDuration(duration / iterations)}${iterationsInfo}`) } /** @@ -411,7 +412,7 @@ export const runTests = async tests => { const success = successfulTests === numberOfTests /* istanbul ignore else */ if (success) { - log.print(log.GREEN, log.BOLD, 'All tests successful!', log.GREY, log.UNBOLD, ` in ${(end - start).toFixed(2)}ms`) + log.print(log.GREEN, log.BOLD, 'All tests successful!', log.GREY, log.UNBOLD, ` in ${time.humanizeDuration(end - start)}`) /* istanbul ignore next */ log.printImgBase64(nyanCatImage, 50) } else { diff --git a/time.js b/time.js index 31d072c..3c4c5f7 100644 --- a/time.js +++ b/time.js @@ -1,3 +1,29 @@ +import * as metric from './metric.js' +import * as math from './math.js' + export const getDate = () => new Date() export const getUnixTime = Date.now + +/** + * @param {number} d duration in milliseconds + * @return {string} humanized approximation of time + */ +export const humanizeDuration = d => { + if (d < 60000) { + const p = metric.prefix(d, -1) + return math.round(p.n * 100) / 100 + p.prefix + 's' + } + d = math.floor(d / 1000) + const seconds = d % 60 + const minutes = math.floor(d / 60) % 60 + const hours = math.floor(d / 3600) % 24 + const days = math.floor(d / 86400) + if (days > 0) { + return days + 'd' + (hours > 0 ? ' ' + (minutes > 30 ? hours + 1 : hours) + 'h' : '') + } + if (hours > 0 ) { + return hours + 'h' + (minutes > 0 ? ' ' + (seconds > 30 ? minutes + 1 : minutes) + 'min' : '') + } + return minutes + 'min' + (seconds > 0 ? ' ' + seconds + 's' : '') +} diff --git a/time.test.js b/time.test.js index 6a75c99..96fa733 100644 --- a/time.test.js +++ b/time.test.js @@ -10,3 +10,19 @@ export const testTime = tc => { const r = time.getUnixTime() t.assert(math.abs(l - r) < 10, 'Times generated are roughly the same') } + +/** + * @param {t.TestCase} tc + */ +export const testHumanDuration = tc => { + t.assert(time.humanizeDuration(10) === '10ms') + t.assert(time.humanizeDuration(.1) === '100μs') + t.assert(time.humanizeDuration(61030) === '1min 1s') + t.assert(time.humanizeDuration(60030) === '1min') + t.assert(time.humanizeDuration(3600001) === '1h') + t.assert(time.humanizeDuration(3660000) === '1h 1min') + t.assert(time.humanizeDuration(3600000 * 25) === '1d 1h') + t.assert(time.humanizeDuration(3600000 * 24 * 400) === '400d') + // test round + t.assert(time.humanizeDuration(6001) === '6s') +}