Document nicestNumberBetween()
This commit is contained in:
parent
5ee13a20a0
commit
7899f8a965
3 changed files with 179 additions and 173 deletions
158
theatre/shared/src/utils/niceNumberUtils.test.ts
Normal file
158
theatre/shared/src/utils/niceNumberUtils.test.ts
Normal file
|
@ -0,0 +1,158 @@
|
|||
import {
|
||||
getLastMultipleOf,
|
||||
numberOfDecimals,
|
||||
nicestFloatBetween,
|
||||
nicestIntegerBetween,
|
||||
nicestNumberBetween,
|
||||
toPrecision,
|
||||
} from './niceNumberUtils'
|
||||
import type {$IntentionalAny} from './types'
|
||||
|
||||
const example = <Args extends $IntentionalAny[], Return>(
|
||||
fn: (...args: Args) => Return,
|
||||
args: Args,
|
||||
expectation: Return,
|
||||
opts: Partial<{skip: boolean; debug: boolean}> = {},
|
||||
) => {
|
||||
;(opts.skip ? it.skip : it)(
|
||||
`${fn.name}(${args.join(', ')}) => ${expectation}`,
|
||||
() => {
|
||||
// @ts-expect-error @ignore
|
||||
if (opts.debug) global.dbg = true
|
||||
const val = fn(...args)
|
||||
// @ts-expect-error @ignore
|
||||
global.dbg = false
|
||||
expect(val).toEqual(expectation)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
describe(`niceNumberUtils()`, () => {
|
||||
describe(`nicestNumberBetween()`, () => {
|
||||
example(nicestNumberBetween, [0.1, 1.1], 1)
|
||||
example(nicestNumberBetween, [0.1111111123, 0.2943439448], 0.25)
|
||||
example(nicestNumberBetween, [0.19, 0.23], 0.2)
|
||||
example(nicestNumberBetween, [-0.19, 0.23], 0)
|
||||
example(nicestNumberBetween, [-0.19, -0.02], -0.1, {debug: false})
|
||||
example(nicestNumberBetween, [-0.19, -0.022], -0.1, {debug: false})
|
||||
example(nicestNumberBetween, [-0.19, -0.022234324], -0.1, {
|
||||
debug: false,
|
||||
})
|
||||
example(nicestNumberBetween, [-0.19, 0.0222222], 0)
|
||||
example(nicestNumberBetween, [-0.19, 0.02], 0)
|
||||
example(
|
||||
nicestNumberBetween,
|
||||
[22304.2398427391, 22304.2398427393],
|
||||
22304.2398427392,
|
||||
)
|
||||
example(nicestNumberBetween, [22304.2398427391, 22304.4], 22304.25)
|
||||
example(nicestNumberBetween, [902, 901], 902)
|
||||
example(nicestNumberBetween, [-10, -5], -10)
|
||||
example(nicestNumberBetween, [-5, -10], -10)
|
||||
example(nicestNumberBetween, [-10, -5], -10)
|
||||
example(
|
||||
nicestNumberBetween,
|
||||
[-0.00876370109231405, -2.909374013346118e-50],
|
||||
0,
|
||||
{debug: false},
|
||||
)
|
||||
example(
|
||||
nicestNumberBetween,
|
||||
[0.059449443526800295, 0.06682093143783596],
|
||||
0.06,
|
||||
{debug: false},
|
||||
)
|
||||
const getRandomNumber = () => {
|
||||
const sign = Math.random() > 0.5 ? 1 : -1
|
||||
return (
|
||||
(Math.pow(Math.random(), Math.random()) /
|
||||
Math.pow(10, Math.random() * 100)) *
|
||||
sign
|
||||
)
|
||||
}
|
||||
test(`nicestNumberBetween() => fuzzy`, () => {
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
const from = toPrecision(getRandomNumber())
|
||||
const to = toPrecision(getRandomNumber())
|
||||
|
||||
const result = nicestNumberBetween(from, to)
|
||||
if (from < to) {
|
||||
if (result < from || result > to) {
|
||||
throw new Error(`Invalid: ${from} ${to} ${result}`)
|
||||
}
|
||||
} else {
|
||||
if (result > from || result < to) {
|
||||
throw new Error(`Invalid: ${to} ${from} ${result}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
describe(`nicestIntegerBetween`, () => {
|
||||
example(nicestIntegerBetween, [-1, 6], 0, {})
|
||||
example(nicestIntegerBetween, [0, 6], 0, {})
|
||||
example(nicestIntegerBetween, [-1, 0], 0, {})
|
||||
example(nicestIntegerBetween, [-1850, -1740], -1750, {})
|
||||
example(nicestIntegerBetween, [1, 6], 5, {})
|
||||
example(nicestIntegerBetween, [1, 5], 5)
|
||||
example(nicestIntegerBetween, [1, 2], 2)
|
||||
example(nicestIntegerBetween, [1, 10], 10)
|
||||
example(nicestIntegerBetween, [1, 12], 10)
|
||||
example(nicestIntegerBetween, [11, 15], 15)
|
||||
example(nicestIntegerBetween, [101, 102], 102, {debug: true})
|
||||
example(nicestIntegerBetween, [11, 14, false], 12)
|
||||
example(nicestIntegerBetween, [11, 14, true], 12.5)
|
||||
example(nicestIntegerBetween, [11, 12], 12)
|
||||
example(nicestIntegerBetween, [11, 12], 12, {})
|
||||
example(nicestIntegerBetween, [10, 90], 50)
|
||||
example(nicestIntegerBetween, [10, 100], 100)
|
||||
example(nicestIntegerBetween, [10, 110], 100)
|
||||
example(nicestIntegerBetween, [9, 100], 10)
|
||||
example(nicestIntegerBetween, [9, 1100], 10)
|
||||
example(nicestIntegerBetween, [9, 699], 10)
|
||||
example(nicestIntegerBetween, [9, 400], 10)
|
||||
example(nicestIntegerBetween, [9, 199], 10)
|
||||
example(nicestIntegerBetween, [9, 1199], 10)
|
||||
example(nicestIntegerBetween, [1921, 1998], 1950)
|
||||
example(nicestIntegerBetween, [1921, 2020], 2000)
|
||||
example(nicestIntegerBetween, [1601, 1998], 1750)
|
||||
example(nicestIntegerBetween, [1919, 1921], 1920)
|
||||
example(nicestIntegerBetween, [1919, 1919], 1919)
|
||||
example(nicestIntegerBetween, [3901, 3902], 3902)
|
||||
example(nicestIntegerBetween, [901, 902], 902)
|
||||
})
|
||||
describe(`nicestFloatBetween()`, () => {
|
||||
example(nicestFloatBetween, [0.19, 0.2122], 0.2)
|
||||
example(nicestFloatBetween, [0.19, 0.31], 0.25)
|
||||
example(nicestFloatBetween, [0.19, 0.41], 0.25)
|
||||
example(nicestFloatBetween, [0.19, 1.9], 0.5)
|
||||
})
|
||||
describe(`numberOfDecimals()`, () => {
|
||||
example(numberOfDecimals, [1.1], 1)
|
||||
example(numberOfDecimals, [1.12], 2)
|
||||
example(numberOfDecimals, [1], 0)
|
||||
example(numberOfDecimals, [10], 0)
|
||||
example(numberOfDecimals, [0.1 + 0.2], 1)
|
||||
example(numberOfDecimals, [0.12399993], 8)
|
||||
example(numberOfDecimals, [0.12399993], 8)
|
||||
example(numberOfDecimals, [0.123999931], 9)
|
||||
example(numberOfDecimals, [0.1239999312], 10)
|
||||
example(numberOfDecimals, [0.12399993121], 10)
|
||||
})
|
||||
|
||||
describe(`toPrecision()`, () => {
|
||||
example(toPrecision, [1.1], 1.1)
|
||||
example(toPrecision, [0.1 + 0.2], 0.3)
|
||||
example(toPrecision, [0.3 - 0.3], 0)
|
||||
example(toPrecision, [0.4 - 0.1], 0.3)
|
||||
example(toPrecision, [1.4 - 0.1], 1.3)
|
||||
})
|
||||
|
||||
describe(`getLastMultipleOf()`, () => {
|
||||
example(getLastMultipleOf, [1, 10], 0)
|
||||
example(getLastMultipleOf, [11, 10], 10)
|
||||
example(getLastMultipleOf, [11, 5], 10)
|
||||
example(getLastMultipleOf, [11, 2], 10)
|
||||
example(getLastMultipleOf, [4, 2], 4)
|
||||
})
|
||||
})
|
|
@ -2,38 +2,39 @@ import padEnd from 'lodash-es/padEnd'
|
|||
import logger from '@theatre/shared/logger'
|
||||
|
||||
/**
|
||||
* Returns the _roundest_ number `c`, such that `a <= c <= b`. This is useful
|
||||
* when a number value is beinged "nudged" by a user, and we want to avoid setting
|
||||
* it to weird value like `101.1239293814314`, when we know that the user probably just meant `100`.
|
||||
* Returns the _aesthetically pleasing_ (aka "nicest") number `c`, such that `a <= c <= b`.
|
||||
* This is useful when a numeric value is being "nudged" by the user (e.g. dragged via mouse pointer),
|
||||
* and we want to avoid setting it to weird value like `101.1239293814314`, when we know that the user
|
||||
* probably just meant `100`.
|
||||
*
|
||||
* Examples
|
||||
* ```ts
|
||||
* roundestNumberBetween(0.1111111123, 0.2943439448) // 0.25
|
||||
* roundestNumberBetween(0.19, 0.23) // 0.2
|
||||
* roundestNumberBetween(1921, 1998) // 1950
|
||||
* roundestNumberBetween(10, 110) // 100
|
||||
* // There are many more examples at `./numberRoundingUtils.test.ts`
|
||||
* nicestNumberBetween(0.1111111123, 0.2943439448) // 0.25
|
||||
* nicestNumberBetween(0.19, 0.23) // 0.2
|
||||
* nicestNumberBetween(1921, 1998) // 1950
|
||||
* nicestNumberBetween(10, 110) // 100
|
||||
* // There are more examples at `./niceNumberUtils.test.ts`
|
||||
* ```
|
||||
*/
|
||||
export function roundestNumberBetween(_a: number, _b: number): number {
|
||||
export function nicestNumberBetween(_a: number, _b: number): number {
|
||||
if (_b < _a) {
|
||||
return roundestNumberBetween(_b, _a)
|
||||
return nicestNumberBetween(_b, _a)
|
||||
}
|
||||
|
||||
if (_a < 0 && _b < 0) {
|
||||
return noMinusZero(roundestNumberBetween(-_b, -_a) * -1)
|
||||
return noMinusZero(nicestNumberBetween(-_b, -_a) * -1)
|
||||
}
|
||||
|
||||
if (_a <= 0 && _b >= 0) return 0
|
||||
|
||||
const aCeiling = Math.ceil(_a)
|
||||
if (aCeiling <= _b) {
|
||||
return roundestIntegerBetween(aCeiling, Math.floor(_b))
|
||||
return nicestIntegerBetween(aCeiling, Math.floor(_b))
|
||||
} else {
|
||||
const [a, b] = [_a, _b]
|
||||
const integer = Math.floor(a)
|
||||
|
||||
return integer + roundestFloat(a - integer, b - integer)
|
||||
return integer + nicestFloatBetween(a - integer, b - integer)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +42,7 @@ export function roundestNumberBetween(_a: number, _b: number): number {
|
|||
const halvesAndQuartiles = [5, 2.5, 7.5]
|
||||
const multipliersWithoutQuartiles = [5, 2, 4, 6, 8, 1, 3, 7, 9]
|
||||
|
||||
export function roundestIntegerBetween(
|
||||
export function nicestIntegerBetween(
|
||||
_a: number,
|
||||
_b: number,
|
||||
decimalsAllowed: boolean = true,
|
||||
|
@ -138,9 +139,11 @@ export const stringifyNumber = (n: number): string => {
|
|||
}
|
||||
|
||||
/**
|
||||
* The float-specific version of {@link nicestNumberBetween}.
|
||||
*
|
||||
* it is expected that both args are 0 \< arg \< 1
|
||||
*/
|
||||
export const roundestFloat = (a: number, b: number): number => {
|
||||
export const nicestFloatBetween = (a: number, b: number): number => {
|
||||
const inString = {
|
||||
a: stringifyNumber(a),
|
||||
b: stringifyNumber(b),
|
||||
|
@ -173,14 +176,15 @@ export const roundestFloat = (a: number, b: number): number => {
|
|||
b: padEnd(withoutInteger.b, maxNumberOfDecimals, '0'),
|
||||
}
|
||||
|
||||
const roundestInt = roundestIntegerBetween(
|
||||
const mostAestheticInt = nicestIntegerBetween(
|
||||
parseInt(withPaddedDecimals.a, 10) * Math.pow(10, maxNumberOfLeadingZeros),
|
||||
parseInt(withPaddedDecimals.b, 10) * Math.pow(10, maxNumberOfLeadingZeros),
|
||||
true,
|
||||
)
|
||||
|
||||
return toPrecision(
|
||||
roundestInt / Math.pow(10, maxNumberOfLeadingZeros + maxNumberOfDecimals),
|
||||
mostAestheticInt /
|
||||
Math.pow(10, maxNumberOfLeadingZeros + maxNumberOfDecimals),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
import {
|
||||
getLastMultipleOf,
|
||||
numberOfDecimals,
|
||||
roundestFloat,
|
||||
roundestIntegerBetween,
|
||||
roundestNumberBetween,
|
||||
toPrecision,
|
||||
} from './numberRoundingUtils'
|
||||
import type {$IntentionalAny} from './types'
|
||||
|
||||
const example = <Args extends $IntentionalAny[], Return>(
|
||||
fn: (...args: Args) => Return,
|
||||
args: Args,
|
||||
expectation: Return,
|
||||
opts: Partial<{skip: boolean; debug: boolean}> = {},
|
||||
) => {
|
||||
;(opts.skip ? it.skip : it)(
|
||||
`${fn.name}(${args.join(', ')}) => ${expectation}`,
|
||||
() => {
|
||||
// @ts-expect-error @ignore
|
||||
if (opts.debug) global.dbg = true
|
||||
const val = fn(...args)
|
||||
// @ts-expect-error @ignore
|
||||
global.dbg = false
|
||||
expect(val).toEqual(expectation)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
describe(`numberRoundingUtils()`, () => {
|
||||
describe(`roundestNumberBetween()`, () => {
|
||||
example(roundestNumberBetween, [0.1, 1.1], 1)
|
||||
example(roundestNumberBetween, [0.1111111123, 0.2943439448], 0.25)
|
||||
example(roundestNumberBetween, [0.19, 0.23], 0.2)
|
||||
example(roundestNumberBetween, [-0.19, 0.23], 0)
|
||||
example(roundestNumberBetween, [-0.19, -0.02], -0.1, {debug: false})
|
||||
example(roundestNumberBetween, [-0.19, -0.022], -0.1, {debug: false})
|
||||
example(roundestNumberBetween, [-0.19, -0.022234324], -0.1, {debug: false})
|
||||
example(roundestNumberBetween, [-0.19, 0.0222222], 0)
|
||||
example(roundestNumberBetween, [-0.19, 0.02], 0)
|
||||
example(
|
||||
roundestNumberBetween,
|
||||
[22304.2398427391, 22304.2398427393],
|
||||
22304.2398427392,
|
||||
)
|
||||
example(roundestNumberBetween, [22304.2398427391, 22304.4], 22304.25)
|
||||
example(roundestNumberBetween, [902, 901], 902)
|
||||
example(roundestNumberBetween, [-10, -5], -10)
|
||||
example(roundestNumberBetween, [-5, -10], -10)
|
||||
example(roundestNumberBetween, [-10, -5], -10)
|
||||
example(
|
||||
roundestNumberBetween,
|
||||
[-0.00876370109231405, -2.909374013346118e-50],
|
||||
0,
|
||||
{debug: false},
|
||||
)
|
||||
example(
|
||||
roundestNumberBetween,
|
||||
[0.059449443526800295, 0.06682093143783596],
|
||||
0.06,
|
||||
{debug: false},
|
||||
)
|
||||
const getRandomNumber = () => {
|
||||
const sign = Math.random() > 0.5 ? 1 : -1
|
||||
return (
|
||||
(Math.pow(Math.random(), Math.random()) /
|
||||
Math.pow(10, Math.random() * 100)) *
|
||||
sign
|
||||
)
|
||||
}
|
||||
test(`roundestNumberBetween() => fuzzy`, () => {
|
||||
for (let i = 0; i < 2000; i++) {
|
||||
const from = toPrecision(getRandomNumber())
|
||||
const to = toPrecision(getRandomNumber())
|
||||
|
||||
const result = roundestNumberBetween(from, to)
|
||||
if (from < to) {
|
||||
if (result < from || result > to) {
|
||||
throw new Error(`Invalid: ${from} ${to} ${result}`)
|
||||
}
|
||||
} else {
|
||||
if (result > from || result < to) {
|
||||
throw new Error(`Invalid: ${to} ${from} ${result}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
describe(`roundestIntegerBetween`, () => {
|
||||
example(roundestIntegerBetween, [-1, 6], 0, {})
|
||||
example(roundestIntegerBetween, [0, 6], 0, {})
|
||||
example(roundestIntegerBetween, [-1, 0], 0, {})
|
||||
example(roundestIntegerBetween, [-1850, -1740], -1750, {})
|
||||
example(roundestIntegerBetween, [1, 6], 5, {})
|
||||
example(roundestIntegerBetween, [1, 5], 5)
|
||||
example(roundestIntegerBetween, [1, 2], 2)
|
||||
example(roundestIntegerBetween, [1, 10], 10)
|
||||
example(roundestIntegerBetween, [1, 12], 10)
|
||||
example(roundestIntegerBetween, [11, 15], 15)
|
||||
example(roundestIntegerBetween, [101, 102], 102, {debug: true})
|
||||
example(roundestIntegerBetween, [11, 14, false], 12)
|
||||
example(roundestIntegerBetween, [11, 14, true], 12.5)
|
||||
example(roundestIntegerBetween, [11, 12], 12)
|
||||
example(roundestIntegerBetween, [11, 12], 12, {})
|
||||
example(roundestIntegerBetween, [10, 90], 50)
|
||||
example(roundestIntegerBetween, [10, 100], 100)
|
||||
example(roundestIntegerBetween, [10, 110], 100)
|
||||
example(roundestIntegerBetween, [9, 100], 10)
|
||||
example(roundestIntegerBetween, [9, 1100], 10)
|
||||
example(roundestIntegerBetween, [9, 699], 10)
|
||||
example(roundestIntegerBetween, [9, 400], 10)
|
||||
example(roundestIntegerBetween, [9, 199], 10)
|
||||
example(roundestIntegerBetween, [9, 1199], 10)
|
||||
example(roundestIntegerBetween, [1921, 1998], 1950)
|
||||
example(roundestIntegerBetween, [1921, 2020], 2000)
|
||||
example(roundestIntegerBetween, [1601, 1998], 1750)
|
||||
example(roundestIntegerBetween, [1919, 1921], 1920)
|
||||
example(roundestIntegerBetween, [1919, 1919], 1919)
|
||||
example(roundestIntegerBetween, [3901, 3902], 3902)
|
||||
example(roundestIntegerBetween, [901, 902], 902)
|
||||
})
|
||||
describe(`roundestFloat()`, () => {
|
||||
example(roundestFloat, [0.19, 0.2122], 0.2)
|
||||
example(roundestFloat, [0.19, 0.31], 0.25)
|
||||
example(roundestFloat, [0.19, 0.41], 0.25)
|
||||
example(roundestFloat, [0.19, 1.9], 0.5)
|
||||
})
|
||||
describe(`numberOfDecimals()`, () => {
|
||||
example(numberOfDecimals, [1.1], 1)
|
||||
example(numberOfDecimals, [1.12], 2)
|
||||
example(numberOfDecimals, [1], 0)
|
||||
example(numberOfDecimals, [10], 0)
|
||||
example(numberOfDecimals, [0.1 + 0.2], 1)
|
||||
example(numberOfDecimals, [0.12399993], 8)
|
||||
example(numberOfDecimals, [0.12399993], 8)
|
||||
example(numberOfDecimals, [0.123999931], 9)
|
||||
example(numberOfDecimals, [0.1239999312], 10)
|
||||
example(numberOfDecimals, [0.12399993121], 10)
|
||||
})
|
||||
|
||||
describe(`toPrecision()`, () => {
|
||||
example(toPrecision, [1.1], 1.1)
|
||||
example(toPrecision, [0.1 + 0.2], 0.3)
|
||||
example(toPrecision, [0.3 - 0.3], 0)
|
||||
example(toPrecision, [0.4 - 0.1], 0.3)
|
||||
example(toPrecision, [1.4 - 0.1], 1.3)
|
||||
})
|
||||
|
||||
describe(`getLastMultipleOf()`, () => {
|
||||
example(getLastMultipleOf, [1, 10], 0)
|
||||
example(getLastMultipleOf, [11, 10], 10)
|
||||
example(getLastMultipleOf, [11, 5], 10)
|
||||
example(getLastMultipleOf, [11, 2], 10)
|
||||
example(getLastMultipleOf, [4, 2], 4)
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue