UNPKG

@i-xi-dev/base64

Version:

A JavaScript Base64 encoder and decoder, implements Forgiving base64 defined in WHATWG Infra Standard.

261 lines (260 loc) 10.9 kB
import { clampNumber, isEvenInteger, isNegativeNumber, isNonNegativeNumber, isNonPositiveNumber, isNumber, isOddInteger, isPositiveNumber, normalizeNumber, ZERO, } from "./number.js"; import { NumberRange } from "./number_range.js"; import { Radix } from "./radix.js"; import { RoundingMode } from "./rounding_mode.js"; function _toSafeIntegerRange(range) { const [min, max] = NumberRange.resolve(range); return [ (min < Number.MIN_SAFE_INTEGER) ? Number.MIN_SAFE_INTEGER : Math.ceil(min), (max > Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Math.floor(max), ]; } function _resolveRoundingMode(roundingMode) { if (Object.values(RoundingMode).includes(roundingMode)) { return roundingMode; } return RoundingMode.TRUNCATE; } const _RESOLVED_MARKER = Symbol(); export var SafeInteger; (function (SafeInteger) { /** * Determines whether the `test` is a positive safe integer. * * @param test - The value to be tested * @returns Whether the `test` is a positive safe integer. */ function isPositiveSafeInteger(test) { return Number.isSafeInteger(test) && isPositiveNumber(test); } SafeInteger.isPositiveSafeInteger = isPositiveSafeInteger; /** * Determines whether the `test` is a non-negative safe integer. * * @param test - The value to be tested * @returns Whether the `test` is a non-negative safe integer. */ function isNonNegativeSafeInteger(test) { return Number.isSafeInteger(test) && isNonNegativeNumber(test); } SafeInteger.isNonNegativeSafeInteger = isNonNegativeSafeInteger; function isNonPositiveSafeInteger(test) { return Number.isSafeInteger(test) && isNonPositiveNumber(test); } SafeInteger.isNonPositiveSafeInteger = isNonPositiveSafeInteger; function isNegativeSafeInteger(test) { return Number.isSafeInteger(test) && isNegativeNumber(test); } SafeInteger.isNegativeSafeInteger = isNegativeSafeInteger; function isOddSafeInteger(test) { return Number.isSafeInteger(test) && isOddInteger(test); } SafeInteger.isOddSafeInteger = isOddSafeInteger; function isEvenSafeInteger(test) { return Number.isSafeInteger(test) && isEvenInteger(test); } SafeInteger.isEvenSafeInteger = isEvenSafeInteger; function roundToSafeInteger(source, roundingMode) { if (Number.isFinite(source) !== true) { throw new TypeError("source"); } if (source > Number.MAX_SAFE_INTEGER) { throw new RangeError("source"); } else if (source < Number.MIN_SAFE_INTEGER) { throw new RangeError("source"); } const integralPart = normalizeNumber(Math.trunc(source)); const integralPartIsEven = isEvenInteger(integralPart); // safe-integerであることは明らかなのでisEvenIntegerを使用する if (typeof roundingMode !== "symbol") { throw new TypeError("roundingMode"); } else if (Object.values(RoundingMode).includes(roundingMode) !== true) { throw new RangeError("roundingMode"); } if (Number.isInteger(source)) { return normalizeNumber(source); } const nearestP = normalizeNumber(Math.ceil(source)); const nearestN = normalizeNumber(Math.floor(source)); const sourceIsNegative = isNegativeNumber(source); const nearestPH = nearestP - 0.5; const nearestNH = nearestN + 0.5; const halfUp = () => { return (source >= nearestPH) ? nearestP : nearestN; }; const halfDown = () => { return (source <= nearestNH) ? nearestN : nearestP; }; switch (roundingMode) { case RoundingMode.UP: return nearestP; case RoundingMode.DOWN: return nearestN; case RoundingMode.TOWARD_ZERO: return integralPart; case RoundingMode.AWAY_FROM_ZERO: return sourceIsNegative ? nearestN : nearestP; case RoundingMode.HALF_UP: return halfUp(); case RoundingMode.HALF_DOWN: return halfDown(); case RoundingMode.HALF_TOWARD_ZERO: return sourceIsNegative ? halfUp() : halfDown(); case RoundingMode.HALF_AWAY_FROM_ZERO: return sourceIsNegative ? halfDown() : halfUp(); case RoundingMode.HALF_TO_EVEN: if (sourceIsNegative) { if (source === nearestPH) { return integralPartIsEven ? integralPart : nearestN; } return halfDown(); } if (source === nearestNH) { return integralPartIsEven ? integralPart : nearestP; } return halfUp(); default: return ZERO; } } SafeInteger.roundToSafeInteger = roundToSafeInteger; let FromOptions; (function (FromOptions) { function resolve(options = {}) { if (_RESOLVED_MARKER in options) { return options; } const strict = options?.strict === true; let fallback = ZERO; if (isNumber(options?.fallback)) { if (Number.isFinite(options.fallback)) { fallback = options.fallback; } else if ((options.fallback) >= Number.MAX_SAFE_INTEGER) { fallback = Number.MAX_SAFE_INTEGER; } else if ((options.fallback) <= Number.MIN_SAFE_INTEGER) { fallback = Number.MIN_SAFE_INTEGER; } } const roundingMode = _resolveRoundingMode(options?.roundingMode); const clampRange = _toSafeIntegerRange(options?.clampRange); return Object.freeze({ [_RESOLVED_MARKER]: true, strict, fallback, roundingMode, clampRange, }); } FromOptions.resolve = resolve; })(FromOptions = SafeInteger.FromOptions || (SafeInteger.FromOptions = {})); /* | null | | | | | | | | undefined | NaN | "" | +∞ | -∞ | > MAX | < MIN | --------------------|-----------|-------|-------|-------|-------|-------|-------| fromNumber(strict) | Error | Error | N/A | Error | Error | Error | Error | fromNumber(!strict) | 0 | 0 | N/A | MAX | MIN | MAX | MIN | fromBigInt(strict) | Error | N/A | N/A | N/A | N/A | Error | Error | fromBigInt(!strict) | 0 | N/A | N/A | N/A | N/A | MAX | MIN | fromString(strict) | Error | N/A | Error | N/A | N/A | Error | Error | fromString(!strict) | 0 | N/A | 0 | N/A | N/A | MAX | MIN | */ function fromNumber(source, options) { const resolvedOptions = FromOptions.resolve(options); let adjusted = source; if (resolvedOptions.strict === true) { if (typeof adjusted !== "number") { throw new TypeError("source"); } if (Number.isSafeInteger(adjusted) !== true) { throw new RangeError("source"); } } else { if (Number.isFinite(adjusted)) { if (adjusted > Number.MAX_SAFE_INTEGER) { adjusted = Number.MAX_SAFE_INTEGER; } else if (adjusted < Number.MIN_SAFE_INTEGER) { adjusted = Number.MIN_SAFE_INTEGER; } } else { if (typeof (adjusted ?? Number.NaN) !== "number") { // number,null,undefined のいずれでもない場合 throw new TypeError("source"); } else if (adjusted === Number.POSITIVE_INFINITY) { adjusted = Number.MAX_SAFE_INTEGER; } else if (adjusted === Number.NEGATIVE_INFINITY) { adjusted = Number.MIN_SAFE_INTEGER; } else { adjusted = resolvedOptions.fallback; } } } if (Number.isSafeInteger(adjusted)) { return clampNumber(adjusted, resolvedOptions.clampRange); // clampRangeはsafe-integerなのでclampToSafeIntegerを使用する } const rounded = roundToSafeInteger(adjusted, resolvedOptions.roundingMode); return clampNumber(rounded, resolvedOptions.clampRange); // clampRangeはsafe-integerなのでclampToSafeIntegerを使用する } SafeInteger.fromNumber = fromNumber; function fromBigInt(source, options) { if (typeof (source ?? 0n) !== "bigint") { // bigint,null,undefined のいずれでもない場合 throw new TypeError("source"); } // ignore options.roundingMode, strict, fallback return fromNumber(Number(source), options); } SafeInteger.fromBigInt = fromBigInt; function fromString(source, options) { if (typeof (source ?? "") !== "string") { // string,null,undefined のいずれでもない場合 throw new TypeError("source"); } let adjusted = source; let pattern; if (options?.strict === true) { pattern = /^[\-+]?(?:[0-9]|[1-9][0-9]+)(?:.0+)?$/; if ((adjusted === null) || (adjusted === undefined)) { throw new TypeError("source"); } } else { pattern = /^[\-+]?(?:[0-9]+)(?:.[0-9]+)?$/; //XXX ".1"を0.1として扱うか? if (typeof adjusted === "string") { adjusted = adjusted.trim(); } if ((adjusted ?? "") === "") { // stringかつ"",null,undefined のいずれかの場合 return fromNumber(Number.NaN, options); } } if (pattern.test(adjusted)) { return fromNumber(Number.parseFloat(adjusted), options); } throw new RangeError("source"); } SafeInteger.fromString = fromString; //XXX export function from() function toBigInt(source) { if (Number.isSafeInteger(source)) { return BigInt(source); } throw new TypeError("source"); } SafeInteger.toBigInt = toBigInt; function toString(source) { if (Number.isSafeInteger(source)) { return normalizeNumber(source).toString(Radix.DECIMAL); } throw new TypeError("source"); } SafeInteger.toString = toString; })(SafeInteger || (SafeInteger = {}));