UNPKG

ts-data-forge

Version:

[![npm version](https://img.shields.io/npm/v/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![npm downloads](https://img.shields.io/npm/dm/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge) [![License](https://img.shields.

273 lines (270 loc) 11 kB
import { pipe } from '../functional/pipe.mjs'; import '@sindresorhus/is'; import { Num } from './num.mjs'; /** @internal */ var TsDataForgeInternals; (function (TsDataForgeInternals) { (function (RefinedNumberUtils) { const castTypeImpl = (is, typeNameInErrorMessage) => (a) => { if (!is(a)) { throw new TypeError(`Expected ${typeNameInErrorMessage}, got: ${a}`); } return a; }; const isNonZero = (n) => n !== 0; const isFnOrUndefined = (min, max) => min === undefined ? max === undefined ? undefined : (n) => n <= max : max === undefined ? (n) => min <= n : Num.isInRangeInclusive(min, max); const clampFnOrUndefined = (min, max) => min === undefined ? max === undefined ? undefined : (n) => Math.min(max, n) : max === undefined ? (n) => Math.max(min, n) : Num.clamp(min, max); /** * Factory function that creates a complete set of type-safe operations for * integer types. * * This function generates: * * - Type guards and validators * - Arithmetic operations that preserve type constraints * - Utility functions (min, max, abs, random) * - Automatic clamping for bounded types * * All operations ensure results remain within the type's constraints, using * clamping when bounds are specified. * * @example * * ```ts * const intOps = TsDataForgeInternals.RefinedNumberUtils.operatorsForInteger< * SafeInt, * number, * number * >({ * integerOrSafeInteger: 'SafeInteger', * MIN_VALUE: Number.MIN_SAFE_INTEGER, * MAX_VALUE: Number.MAX_SAFE_INTEGER, * typeNameInMessage: 'SafeInt', * } as const); * * const six = intOps.castType(6); * * const four = intOps.castType(4); * * const sum = intOps.add(six, four); * * const difference = intOps.sub(six, four); * * const product = intOps.mul(six, four); * * const quotient = intOps.div(six, intOps.castType(2)); * * const roundedClamp = intOps.clamp(1.5); * * const randomValue = intOps.random(); * * assert.isTrue(sum === 10); * * assert.isTrue(difference === 2); * * assert.isTrue(product === 24); * * assert.isTrue(quotient === 3); * * assert.isTrue(roundedClamp === 2); * * assert.isTrue(Number.isSafeInteger(randomValue)); * ``` * * @template ElementType - The integer branded type * @template MIN_VALUE - Optional minimum value for bounded types * @template MAX_VALUE - Optional maximum value for bounded types * @param config - Configuration object * @param config.integerOrSafeInteger - Whether to use Number.isInteger or * Number.isSafeInteger * @param config.nonZero - If true, excludes zero from valid values * @param config.MIN_VALUE - Minimum valid value (inclusive) * @param config.MAX_VALUE - Maximum valid value (inclusive) * @param config.typeNameInMessage - Human-readable type name for error * messages * @returns Object containing all type-safe operations for the integer type * @internal */ RefinedNumberUtils.operatorsForInteger = ({ integerOrSafeInteger, nonZero, MIN_VALUE, MAX_VALUE, typeNameInMessage, }) => { const is = (a) => (integerOrSafeInteger === 'Integer' ? Number.isInteger(a) : Number.isSafeInteger(a)) && (nonZero === true ? a !== 0 : true) && (isFnOrUndefined(MIN_VALUE, MAX_VALUE)?.(a) ?? true); const castType = castTypeImpl(is, typeNameInMessage); const clamp = pipe(clampFnOrUndefined(MIN_VALUE, MAX_VALUE)).mapNullable((cl) => (x) => castType(Math.round(cl(x)))).value; const clampOrCastFn = clamp ?? castType; const abs = (x) => // eslint-disable-next-line total-functions/no-unsafe-type-assertion Math.abs(clampOrCastFn(x)); const min_ = (...values) => clampOrCastFn(Math.min(...values)); const max_ = (...values) => clampOrCastFn(Math.max(...values)); const pow = (x, y) => clampOrCastFn(x ** y); const add = (x, y) => clampOrCastFn(x + y); const sub = (x, y) => clampOrCastFn(x - y); const mul = (x, y) => clampOrCastFn(x * y); const div = (x, y) => clampOrCastFn(Math.floor( // eslint-disable-next-line total-functions/no-partial-division x / y)); const randomImpl = (min = MIN_VALUE, max = MAX_VALUE) => min + Math.floor((Math.max(max, min) - min + 1) * Math.random()); // [-5, 5] -> floor(11 * Math.random()) + (-5) const random = (min, max) => clampOrCastFn(randomImpl(min, max)); const randomNonZero = (min, max) => { while (true) { const r = randomImpl(min, max); if (Num.isNonZero(r)) return clampOrCastFn(r); } }; return { MIN_VALUE, MAX_VALUE, is, abs, min: min_, max: max_, pow, add, sub, mul, div, random, randomNonZero, castType, clamp: // eslint-disable-next-line total-functions/no-unsafe-type-assertion clamp, }; }; /** * Factory function that creates a complete set of type-safe operations for * floating-point types. * * This function generates: * * - Type guards and validators (checking for finite values) * - Arithmetic operations that preserve type constraints * - Utility functions (min, max, abs, random) * - Automatic clamping for bounded types * * All operations ensure results remain finite and within any specified * bounds. Division by zero is prevented through type constraints. * * @example * * ```ts * const floatOps = TsDataForgeInternals.RefinedNumberUtils.operatorsForFloat< * PositiveFiniteNumber, * number, * number * >({ * nonZero: true, * MIN_VALUE: Number.MIN_VALUE, * MAX_VALUE: Number.MAX_VALUE, * typeNameInMessage: 'PositiveFiniteNumber', * } as const); * * const fortyTwo = floatOps.castType(42.5); * * const seven = floatOps.castType(7.5); * * const sum = floatOps.add(fortyTwo, seven); * * const ratio = floatOps.div(sum, floatOps.castType(10)); * * const clamped = floatOps.clamp(0); * * const boundedRandom = floatOps.random( * floatOps.castType(10), * floatOps.castType(20), * ); * * const nonZeroRandom = floatOps.randomNonZero(); * * assert.isTrue(sum === 50); * * assert.isTrue(ratio === 5); * * assert.isTrue(clamped >= Number.MIN_VALUE); * * assert.isTrue(boundedRandom >= 10 && boundedRandom <= 20); * * assert.isTrue(nonZeroRandom > 0); * ``` * * @template ElementType - The floating-point branded type * @template MIN_VALUE - Optional minimum value for bounded types * @template MAX_VALUE - Optional maximum value for bounded types * @param config - Configuration object * @param config.nonZero - If true, excludes zero from valid values * @param config.MIN_VALUE - Minimum valid value (inclusive) * @param config.MAX_VALUE - Maximum valid value (inclusive) * @param config.typeNameInMessage - Human-readable type name for error * messages * @returns Object containing all type-safe operations for the * floating-point type * @internal */ RefinedNumberUtils.operatorsForFloat = ({ nonZero, MIN_VALUE, MAX_VALUE, typeNameInMessage, }) => { const is = (a) => Number.isFinite(a) && (nonZero === true ? a !== 0 : true) && (isFnOrUndefined(MIN_VALUE, MAX_VALUE)?.(a) ?? true); const castType = castTypeImpl(is, typeNameInMessage); const clamp = pipe(clampFnOrUndefined(MIN_VALUE, MAX_VALUE)).mapNullable((cl) => (x) => castType(cl(x))).value; const clampOrCastFn = clamp ?? castType; const abs = (x) => // eslint-disable-next-line total-functions/no-unsafe-type-assertion Math.abs(x); const min_ = (...values) => clampOrCastFn(Math.min(...values)); const max_ = (...values) => clampOrCastFn(Math.max(...values)); const pow = (x, y) => clampOrCastFn(x ** y); const add = (x, y) => clampOrCastFn(x + y); const sub = (x, y) => clampOrCastFn(x - y); const mul = (x, y) => clampOrCastFn(x * y); const div = (x, y) => // eslint-disable-next-line total-functions/no-partial-division clampOrCastFn(x / y); const randomImpl = (min = MIN_VALUE, max = MAX_VALUE) => min + (Math.max(max, min) - min) * Math.random(); const random = (min, max) => clampOrCastFn(randomImpl(min, max)); const randomNonZero = (min, max) => { while (true) { const r = random(min, max); if (isNonZero(r)) return r; } }; return { MIN_VALUE, MAX_VALUE, is, abs, min: min_, max: max_, pow, add, sub, mul, div, random, randomNonZero, castType, // eslint-disable-next-line total-functions/no-unsafe-type-assertion clamp: clamp, }; }; })(TsDataForgeInternals.RefinedNumberUtils || (TsDataForgeInternals.RefinedNumberUtils = {})); })(TsDataForgeInternals || (TsDataForgeInternals = {})); export { TsDataForgeInternals }; //# sourceMappingURL=refined-number-utils.mjs.map