X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/8fcb8f6d937fadd7aa8016f8ba33004d1bd79bf0..6e6d4e3261c1c943fe44fa9e381bcf8bf1441fd6:/numeric.js?ds=inline diff --git a/numeric.js b/numeric.js index 44c7cd5..4a066f7 100644 --- a/numeric.js +++ b/numeric.js @@ -7,6 +7,14 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . +import { call } from "./function.js"; +import { + stringCatenate, + stringPadEnd, + stringRepeat, + substring, + toString, +} from "./string.js"; import { sameValue, toPrimitive } from "./value.js"; export const { @@ -377,6 +385,12 @@ export const { isSafeInteger: isSafeIntegralNumber, } = Number; +/** Positive zero. */ +export const POSITIVE_ZERO = 0; + +/** Negative zero. */ +export const NEGATIVE_ZERO = -0; + /** * Returns the magnitude (absolute value) of the provided value. * @@ -560,10 +574,10 @@ export const sgn = ($) => { ? n === 0n ? 0n : n < 0n ? -1n : 1n : isNan(n) || n === 0 ? n - : //deno-lint-ignore no-compare-neg-zero - n < -0 - ? -1 - : 1; + //deno-lint-ignore no-compare-neg-zero + : n < -0 + ? -1 + : 1; }; /** @@ -578,6 +592,93 @@ export const { toBigInt } = (() => { return { toBigInt: ($) => makeBigInt($) }; })(); +export const { + /** + * Returns the result of converting the provided value to an + * exponential notation string. + * + * If a second argument is provided, it gives the number of + * fractional digits to use in the mantissa. Otherwise, the smallest + * number which does not result in a reduction in precision is used. + * + * ※ This method is safe to use with big·ints. + */ + toExponentialNotation, + + /** + * Returns the result of converting the provided value to a fixed + * decimal notation string with the provided number of fractional + * digits. + * + * ※ This method is safe to use with big·ints. + */ + toFixedDecimalNotation, +} = (() => { + const { + toExponential: numberToExponential, + toFixed: numberToFixed, + } = Number.prototype; + const { toString: bigintToString } = BigInt.prototype; + return { + toExponentialNotation: ($, fractionDigits) => { + const n = toNumeric($); + const f = toIntegralNumberOrInfinity(fractionDigits); + if (!isFiniteNumber(f) || f < 0 || f > 100) { + throw new RangeError( + `Piscēs: The number of fractional digits must be a finite number between 0 and 100 inclusive; got: ${f}.`, + ); + } else { + if (typeof n === "number") { + return call( + numberToExponential, + n, + [fractionDigits === undefined ? fractionDigits : f], + ); + } else { + const digits = call(bigintToString, n, [10]); + const { length } = digits; + if (fractionDigits === undefined) { + return length === 1 + ? `${digits[0]}e+0` + : `${digits[0]}.${substring(digits, 1)}e+${length - 1}`; + } else if (f === 0) { + return `${digits[0]}e+0`; + } else { + const fractionalPart = toString( + round( + +stringCatenate( + stringPadEnd(substring(digits, 1, f + 1), f, "0"), + ".", + digits[f + 1] || "0", + ), + ), + ); + return `${digits[0]}.${fractionalPart}e+${length - 1}`; + } + } + } + }, + toFixedDecimalNotation: ($, fractionDigits) => { + const f = toIntegralNumberOrInfinity(fractionDigits); + if (!isFiniteNumber(f) || f < 0 || f > 100) { + throw new RangeError( + `Piscēs: The number of fractional digits must be a finite number between 0 and 100 inclusive; got: ${f}.`, + ); + } else { + const n = toNumeric($); + if (typeof n === "number") { + return call(numberToFixed, n, [f]); + } else { + const digits = call(bigintToString, n, [10]); + return f === 0 + ? digits + : `${digits}.${stringRepeat("0", f)}`; + } + } + }, + }; +})(); + export const { /** * Returns the result of converting the provided value to fit within @@ -605,27 +706,64 @@ export const { return { toIntN: (n, $) => { const prim = toPrimitive($); - const big·int = toBigInt(prim); - const intN = asIntN(n, big·int); - return typeof prim === "bigint" ? intN : toNumber(intN); + if (typeof prim === "bigint") { + // The primitive value is a big·int. + return asIntN(n, prim); + } else { + // The primitive value is not a big·int. + const int = trunc(prim); + if (!isFiniteNumber(int) || int == 0) { + // The truncated value is zero or not finite. + return 0; + } else { + // The truncated value is finite. + return toNumber(asIntN(n, toBigInt(int))); + } + } }, toUintN: (n, $) => { const prim = toPrimitive($); - const big·int = toBigInt(prim); - const uintN = asUintN(n, big·int); - return typeof prim === "bigint" ? uintN : toNumber(uintN); + if (typeof prim === "bigint") { + // The primitive value is a big·int. + return asUintN(n, prim); + } else { + // The primitive value is not a big·int. + const int = trunc(prim); + if (!isFiniteNumber(int) || int == 0) { + // The truncated value is zero or not finite. + return 0; + } else { + // The truncated value is finite. + return toNumber(asUintN(n, toBigInt(int))); + } + } }, }; })(); +/** + * Returns the result of converting the provided number to an integral + * number. + * + * ※ This function will never return negative zero. + */ +export const toIntegralNumber = ($) => { + const n = toIntegralNumberOrInfinity($); + return !isFiniteNumber(n) || n == 0 ? 0 : n; +}; + /** * Returns the result of converting the provided number to an integer * or infinity. * - * ☡ This function does not allow big·int arguments. + * ※ Unlike the ToIntegerOrInfinity function defined in the Ecmascript + * specification, this function is safe to use with big·ints. However, + * the result will always be a number. + * + * ※ This function will never return negative zero. */ -export const toIntegerOrInfinity = ($) => { - const integer = trunc($); +export const toIntegralNumberOrInfinity = ($) => { + const integer = trunc(toNumber($)); if (isNan(integer) || integer == 0) { // The provided value truncs to nan or (positive or negative) zero. return 0; @@ -644,7 +782,7 @@ export const toIntegerOrInfinity = ($) => { /** * Returns the result of converting the provided value to a number. * - * ※ This method is safe to use with big·ints. + * ※ This function is safe to use with big·ints. * * ※ This is effectively a nonconstructible version of the Number * constructor.