@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
JavaScript
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 = {}));