ts-data-forge
Version:
[](https://www.npmjs.com/package/ts-data-forge) [](https://www.npmjs.com/package/ts-data-forge) [ • 11 kB
JavaScript
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