From: Lady Date: Tue, 20 Sep 2022 03:39:11 +0000 (-0700) Subject: Add to⸺Notation functions to numeric.js X-Git-Tag: 0.3.0~9 X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/cb5a5ceca492121141a12d921afc78dbde7fbfa1?ds=sidebyside;hp=d78461254503ec9dab25d0a7518538e9149618e2 Add to⸺Notation functions to numeric.js The default Number::toString method provides a result which is difficult to parse. Number::toExponential and Number::toFixed are better; they are now provided via toExponentialNotation and toFixedDecimalNotation. Support for big·ints has been added as well. --- diff --git a/numeric.js b/numeric.js index f16308d..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 { @@ -584,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 @@ -687,7 +782,7 @@ export const toIntegralNumberOrInfinity = ($) => { /** * 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. diff --git a/numeric.test.js b/numeric.test.js index 23dbd89..27776cd 100644 --- a/numeric.test.js +++ b/numeric.test.js @@ -23,6 +23,8 @@ import { POSITIVE_ZERO, sgn, toBigInt, + toExponentialNotation, + toFixedDecimalNotation, toFloat32, toIntegralNumber, toIntegralNumberOrInfinity, @@ -141,6 +143,53 @@ describe("toBigInt", () => { }); }); +describe("toExponentialNotation", () => { + it("[[Call]] converts to exponential notation", () => { + assertStrictEquals(toExponentialNotation(231), "2.31e+2"); + }); + + it("[[Call]] works with big·ints", () => { + assertStrictEquals( + toExponentialNotation(9007199254740993n), + "9.007199254740993e+15", + ); + }); + + it("[[Call]] respects the specified number of fractional digits", () => { + assertStrictEquals( + toExponentialNotation(.00000000642, 3), + "6.420e-9", + ); + assertStrictEquals(toExponentialNotation(.00691, 1), "6.9e-3"); + assertStrictEquals(toExponentialNotation(.00685, 1), "6.9e-3"); + assertStrictEquals(toExponentialNotation(.004199, 2), "4.20e-3"); + assertStrictEquals( + toExponentialNotation(6420000000n, 3), + "6.420e+9", + ); + assertStrictEquals(toExponentialNotation(6910n, 1), "6.9e+3"); + assertStrictEquals(toExponentialNotation(6850n, 1), "6.9e+3"); + assertStrictEquals(toExponentialNotation(4199n, 2), "4.20e+3"); + }); +}); + +describe("toFixedDecimalNotation", () => { + it("[[Call]] converts to fixed decimal notation", () => { + assertStrictEquals(toFixedDecimalNotation(69.4199, 3), "69.420"); + }); + + it("[[Call]] works with big·ints", () => { + assertStrictEquals( + toFixedDecimalNotation(9007199254740993n), + "9007199254740993", + ); + assertStrictEquals( + toFixedDecimalNotation(9007199254740993n, 2), + "9007199254740993.00", + ); + }); +}); + describe("toFloat32", () => { it("[[Call]] returns the 32‐bit floating‐point representation", () => { assertStrictEquals(