From: Lady Date: Sun, 17 Jul 2022 22:18:57 +0000 (-0700) Subject: Add numeric functions and unit tests X-Git-Tag: 0.1.0~6 X-Git-Url: https://git.ladys.computer/Pisces/commitdiff_plain/5af99cb7def7f5a9b19db6a8e214920dd233113d?ds=inline Add numeric functions and unit tests --- diff --git a/numeric.js b/numeric.js index 2c1a66f..78ea64c 100644 --- a/numeric.js +++ b/numeric.js @@ -7,5 +7,638 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . -/** 2^53 − 1. */ -export const MAX_SAFE_INTEGER = Number((1n << 53n) - 1n); +import { sameValue, toPrimitive } from "./value.js"; + +export const { + /** + * ln(10). + * + * ※ This is an alias for Math.LN10. + */ + LN10, + + /** + * ln(2). + * + * ※ This is an alias for Math.LN2. + */ + LN2, + + /** + * log10(ℇ). + * + * ※ This is an alias for Math.LOG10E. + */ + LOG10E: LOG10ℇ, + + /** + * log2(ℇ). + * + * ※ This is an alias for Math.LOG2E. + */ + LOG2E: LOG2ℇ, + + /** + * sqrt(.5). + * + * ※ This is an alias for Math.SQRT1_2. + */ + SQRT1_2: RECIPROCAL_SQRT2, + + /** + * sqrt(2). + * + * ※ This is an alias for Math.SQRT2. + */ + SQRT2, + + /** + * Returns the arccos of the provided value. + * + * ※ This is an alias for Math.acos. + * + * ☡ This function does not allow big·int arguments. + */ + acos: arccos, + + /** + * Returns the arccosh of the provided value. + * + * ※ This is an alias for Math.acosh. + * + * ☡ This function does not allow big·int arguments. + */ + acosh: arccosh, + + /** + * Returns the arcsin of the provided value. + * + * ※ This is an alias for Math.asin. + * + * ☡ This function does not allow big·int arguments. + */ + asin: arcsin, + + /** + * Returns the arcsinh of the provided value. + * + * ※ This is an alias for Math.asinh. + * + * ☡ This function does not allow big·int arguments. + */ + asinh: arcsinh, + + /** + * Returns the arctan of the provided value. + * + * ※ This is an alias for Math.atan. + * + * ☡ This function does not allow big·int arguments. + */ + atan: arctan, + + /** + * Returns the arctanh of the provided value. + * + * ※ This is an alias for Math.atanh. + * + * ☡ This function does not allow big·int arguments. + */ + atanh: arctanh, + + /** + * Returns the cube root of the provided value. + * + * ※ This is an alias for Math.cbrt. + * + * ☡ This function does not allow big·int arguments. + */ + cbrt, + + /** + * Returns the ceiling of the provided value. + * + * ※ This is an alias for Math.ceil. + * + * ☡ This function does not allow big·int arguments. + */ + ceil, + + /** + * Returns the cos of the provided value. + * + * ※ This is an alias for Math.cos. + * + * ☡ This function does not allow big·int arguments. + */ + cos, + + /** + * Returns the cosh of the provided value. + * + * ※ This is an alias for Math.cosh. + * + * ☡ This function does not allow big·int arguments. + */ + cosh, + + /** + * Returns the Euler number raised to the provided value. + * + * ※ This is an alias for Math.exp. + * + * ☡ This function does not allow big·int arguments. + */ + exp, + + /** + * Returns the Euler number raised to the provided value, minus one. + * + * ※ This is an alias for Math.expm1. + * + * ☡ This function does not allow big·int arguments. + */ + expm1, + + /** + * Returns the floor of the provided value. + * + * ※ This is an alias for Math.floor. + * + * ☡ This function does not allow big·int arguments. + */ + floor, + + /** + * Returns the square root of the sum of the squares of the provided + * arguments. + * + * ※ This is an alias for Math.hypot. + * + * ☡ This function does not allow big·int arguments. + */ + hypot, + + /** + * Returns the ln of the provided value. + * + * ※ This is an alias for Math.log. + * + * ☡ This function does not allow big·int arguments. + */ + log: ln, + + /** + * Returns the log10 of the provided value. + * + * ※ This is an alias for Math.log10. + * + * ☡ This function does not allow big·int arguments. + */ + log10, + + /** + * Returns the ln of one plus the provided value. + * + * ※ This is an alias for Math.log1p. + * + * ☡ This function does not allow big·int arguments. + */ + log1p: ln1p, + + /** + * Returns the log2 of the provided value. + * + * ※ This is an alias for Math.log2. + * + * ☡ This function does not allow big·int arguments. + */ + log2, + + /** + * Returns a pseudo·random value in the range [0, 1). + * + * ※ This is an alias for Math.random. + */ + random: rand, + + /** + * Returns the round of the provided value. + * + * ※ This is an alias for Math.round. + * + * ☡ This function does not allow big·int arguments. + */ + round, + + /** + * Returns the sinh of the provided value. + * + * ※ This is an alias for Math.sinh. + * + * ☡ This function does not allow big·int arguments. + */ + sinh, + + /** + * Returns the square root of the provided value. + * + * ※ This is an alias for Math.sqrt. + * + * ☡ This function does not allow big·int arguments. + */ + sqrt, + + /** + * Returns the tan of the provided value. + * + * ※ This is an alias for Math.tan. + * + * ☡ This function does not allow big·int arguments. + */ + tan, + + /** + * Returns the tanh of the provided value. + * + * ※ This is an alias for Math.tanh. + * + * ☡ This function does not allow big·int arguments. + */ + tanh, + + /** + * Returns the trunc of the provided value. + * + * ※ This is an alias for Math.trunc. + * + * ☡ This function does not allow big·int arguments. + */ + trunc, + + /** + * The mathematical constant π. + * + * ※ This is an alias for Math.PI. + */ + PI: Π, + + /** + * The Euler number. + * + * ※ This is an alias for Math.E. + */ + E: ℇ, +} = Math; + +export const { + /** + * The largest number value less than infinity. + * + * ※ This is an alias for Number.MAX_VALUE. + */ + MAX_VALUE: MAXIMUM_NUMBER, + + /** + * 2**53 - 1. + * + * ※ This is an alias for Number.MAX_SAFE_INTEGER. + */ + MAX_SAFE_INTEGER: MAXIMUM_SAFE_INTEGRAL_NUMBER, + + /** + * The smallest number value greater than negative infinity. + * + * ※ This is an alias for Number.MIN_VALUE. + */ + MIN_VALUE: MINIMUM_NUMBER, + + /** + * -(2**53 - 1). + * + * ※ This is an alias for Number.MIN_SAFE_INTEGER. + */ + MIN_SAFE_INTEGER: MINIMUM_SAFE_INTEGRAL_NUMBER, + + /** + * Negative infinity. + * + * ※ This is an alias for Number.NEGATIVE_INFINITY. + */ + NEGATIVE_INFINITY, + + /** + * Nan. + * + * ※ This is an alias for Number.NaN. + */ + NaN: NAN, + + /** + * Positive infinity. + * + * ※ This is an alias for Number.POSITIVE_INFINITY. + */ + POSITIVE_INFINITY, + + /** + * The difference between 1 and the smallest number greater than 1. + * + * ※ This is an alias for Number.EPSILON. + */ + EPSILON: Ε, + + /** + * Returns whether the provided value is a finite number. + * + * ※ This is an alias for Number.isFinite. + */ + isFinite: isFiniteNumber, + + /** + * Returns whether the provided value is an integral number. + * + * ※ This is an alias for Number.isInteger. + */ + isInteger: isIntegralNumber, + + /** + * Returns whether the provided value is nan. + * + * ※ This is an alias for Number.isNaN. + */ + isNaN: isNan, + + /** + * Returns whether the provided value is a safe integral number. + * + * ※ This is an alias for Number.isSafeInteger. + */ + isSafeInteger: isSafeIntegralNumber, +} = Number; + +/** + * Returns the magnitude (absolute value) of the provided value. + * + * ※ Unlike Math.abs, this function can take big·int arguments. + */ +export const abs = ($) => { + const n = toNumeric($); + return typeof n === "bigint" + ? n < 0n ? -n : n + : isNan(n) + ? NAN + : sameValue(n, -0) + ? 0 + : sameValue(n, NEGATIVE_INFINITY) + ? POSITIVE_INFINITY + : n < 0 + ? -n + : n; +}; + +export const { + /** + * Returns the arctangent of the dividend of the provided values. + * + * ※ Unlike Math.atan2, this function can take big·int arguments. + * However, the result will always be a number. + */ + atan2, + + /** + * Returns the number of leading zeroes in the 32‐bit representation of + * the provided value. + * + * ※ Unlike Math.clz32, this function accepts either number or big·int + * values. + */ + clz32, + + /** + * Returns the 32‐bit float which best approximate the provided + * value. + * + * ※ Unlike Math.fround, this function can take big·int arguments. + * However, the result will always be a number. + */ + toFloat32, +} = (() => { + const { atan2, fround, clz32 } = Math; + return { + atan2: (y, x) => atan2(toNumber(y), toNumber(x)), + clz32: ($) => { + const n = toNumeric($); + return clz32( + typeof n === "bigint" ? toNumber(toUintN(32, n)) : n, + ); + }, + toFloat32: ($) => fround(toNumber($)), + }; +})(); + +/** + * Returns the highest value of the provided arguments, or negative + * infinity if no argument is provided. + * + * ※ Unlike Math.max, this function accepts either number or big·int + * values. All values must be of the same type, or this function will + * throw an error. + * + * ☡ If no argument is supplied, the result will be a number, not a + * big·int. + */ +export const max = (...$s) => { + let highest = undefined; + for (let i = 0; i < $s.length; ++i) { + // Iterate over all the numbers. + const number = toNumeric($s[i]); + if (highest === undefined) { + // The current number is the first one. + if (isNan(number)) { + // The current number is nan. + return NAN; + } else { + // The current number is not nan. + highest = number; + } + } else { + if (typeof highest !== typeof number) { + // The type of the current number and the lowest number don’t + // match. + throw new TypeError("Piscēs: Type mismatch."); + } else if (isNan(number)) { + // The current number is nan. + return NAN; + } else if (sameValue(number, 0) && sameValue(highest, -0)) { + // The current number is +0 and the highest number is -0. + highest = 0; + } else if (highest === undefined || number > highest) { + // The current number is greater than the highest number. + highest = number; + } else { + // The current number is less than or equal to the lowest + // number. + /* do nothing */ + } + } + } + return highest ?? NEGATIVE_INFINITY; +}; + +/** + * Returns the lowest value of the provided arguments, or positive + * infinity if no argument is provided. + * + * ※ Unlike Math.min, this function accepts either number or big·int + * values. All values must be of the same type, or this function will + * throw an error. + * + * ☡ If no argument is supplied, the result will be a number, not a + * big·int. + */ +export const min = (...$s) => { + let lowest = undefined; + for (let i = 0; i < $s.length; ++i) { + // Iterate over all the numbers. + const number = toNumeric($s[i]); + if (lowest === undefined) { + // The current number is the first one. + if (isNan(number)) { + // The current number is nan. + return NAN; + } else { + // The current number is not nan. + lowest = number; + } + } else { + // The current number is not the first one. + if (typeof lowest !== typeof number) { + // The type of the current number and the lowest number don’t + // match. + throw new TypeError("Piscēs: Type mismatch."); + } else if (isNan(number)) { + // The current number is nan. + return NAN; + } else if (sameValue(number, -0) && sameValue(lowest, 0)) { + // The current number is -0 and the lowest number is +0. + lowest = -0; + } else if (number < lowest) { + // The current number is less than the lowest number. + lowest = number; + } else { + // The current number is greater than or equal to the lowest + // number. + /* do nothing */ + } + } + } + return lowest ?? POSITIVE_INFINITY; +}; + +/** + * Returns a unit value with the same sign as the provided value, or + * the provided value itself if it is not a number or (potentially + * signed) zero. + * + * For big·ints, the return value of this function is 0n if the + * provided value is 0n, -1n if the provided value is negative, and +1n + * otherwise. + * + * For numbers, the return value is nan, -0, or +0 if the provided + * value is nan, -0, or +0, respectively, and -1 if the provided value + * is negative and +1 if the provided value is positive otherwise. Note + * that positive and negative infinity will return +1 and -1 + * respectively. + * + * ※ Unlike Math.sign, this function accepts either number or big·int + * values. + */ +export const sgn = ($) => { + const n = toNumeric($); + return typeof n === "bigint" + ? n === 0n ? 0n : n < 0n ? -1n : 1n + : isNan(n) || n === 0 + ? n + : //deno-lint-ignore no-compare-neg-zero + n < -0 + ? -1 + : 1; +}; + +/** + * Returns the result of converting the provided value to a big·int. + * + * ※ This method is safe to use with numbers. + * + * ※ This is effectively an alias for BigInt. + */ +export const { toBigInt } = (() => { + const makeBigInt = BigInt; + return { toBigInt: ($) => makeBigInt($) }; +})(); + +export const { + /** + * Returns the result of converting the provided value to fit within + * the provided number of bits as a signed integer. + * + * ※ Unlike BigInt.asIntN, this function accepts both big·int and + * number values. + * + * ☡ The first argument, the number of bits, must be a number. + */ + toIntN, + + /** + * Returns the result of converting the provided value to fit within + * the provided number of bits as an unsigned integer. + * + * ※ Unlike BigInt.asUintN, this function accepts both big·int and + * number values. + * + * ☡ The first argument, the number of bits, must be a number. + */ + toUintN, +} = (() => { + const { asIntN, asUintN } = BigInt; + return { + toIntN: (n, $) => { + const prim = toPrimitive($); + const big·int = toBigInt(prim); + const intN = asIntN(n, big·int); + return typeof prim === "bigint" ? intN : toNumber(intN); + }, + toUintN: (n, $) => { + const prim = toPrimitive($); + const big·int = toBigInt(prim); + const uintN = asUintN(n, big·int); + return typeof prim === "bigint" ? uintN : toNumber(uintN); + }, + }; +})(); + +/** + * Returns the result of converting the provided value to a number. + * + * ※ This method is safe to use with big·ints. + * + * ※ This is effectively a nonconstructible version of the Number + * constructor. + */ +export const { toNumber } = (() => { + const makeNumber = Number; + return { toNumber: ($) => makeNumber($) }; +})(); + +/** + * Returns the result of converting the provided value to a number or + * big·int. + * + * ※ If the result of converting the provided value to a primitive is + * not a big·int, this function will return a number. + */ +export const toNumeric = ($) => { + const primValue = toPrimitive($, "number"); + return typeof primValue === "bigint" ? primValue : +primValue; +}; diff --git a/numeric.test.js b/numeric.test.js new file mode 100644 index 0000000..e82e04b --- /dev/null +++ b/numeric.test.js @@ -0,0 +1,192 @@ +// ♓🌟 Piscēs ∷ numeric.test.js +// ==================================================================== +// +// Copyright © 2022 Lady [@ Lady’s Computer]. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at . + +import { + assertStrictEquals, + assertThrows, + describe, + it, +} from "./dev-deps.js"; +import { + abs, + atan2, + clz32, + max, + min, + sgn, + toBigInt, + toFloat32, + toIntN, + toNumber, + toNumeric, + toUintN, +} from "./numeric.js"; + +describe("abs", () => { + it("[[Call]] returns the absolute value", () => { + assertStrictEquals(abs(-1), 1); + }); + + it("[[Call]] works with big·ints", () => { + const bn = BigInt(Number.MAX_SAFE_INTEGER) + 2n; + assertStrictEquals(abs(-bn), bn); + }); +}); + +describe("atan2", () => { + it("[[Call]] returns the atan2", () => { + assertStrictEquals(atan2(6, 9), Math.atan2(6, 9)); + }); + + it("[[Call]] works with big·ints", () => { + assertStrictEquals(atan2(6n, 9n), Math.atan2(6, 9)); + }); +}); + +describe("clz32", () => { + it("[[Call]] returns the clz32", () => { + assertStrictEquals(clz32(1 << 28), 3); + }); + + it("[[Call]] works with big·ints", () => { + assertStrictEquals(clz32(1n << 28n), 3); + }); +}); + +describe("max", () => { + it("[[Call]] returns the largest number", () => { + assertStrictEquals(max(1, -6, 92, -Infinity, 0), 92); + }); + + it("[[Call]] returns the largest big·int", () => { + assertStrictEquals(max(1n, -6n, 92n, 0n), 92n); + }); + + it("[[Call]] returns nan if any argument is nan", () => { + assertStrictEquals(max(0, NaN, 1), NaN); + }); + + it("[[Call]] returns -Infinity when called with no arguments", () => { + assertStrictEquals(max(), -Infinity); + }); + + it("[[Call]] throws if both big·int and number arguments are provided", () => { + assertThrows(() => max(-Infinity, 0n)); + }); +}); + +describe("min", () => { + it("[[Call]] returns the largest number", () => { + assertStrictEquals(min(1, -6, 92, Infinity, 0), -6); + }); + + it("[[Call]] returns the largest big·int", () => { + assertStrictEquals(min(1n, -6n, 92n, 0n), -6n); + }); + + it("[[Call]] returns nan if any argument is nan", () => { + assertStrictEquals(min(0, NaN, 1), NaN); + }); + + it("[[Call]] returns Infinity when called with no arguments", () => { + assertStrictEquals(min(), Infinity); + }); + + it("[[Call]] throws if both big·int and number arguments are provided", () => { + assertThrows(() => min(Infinity, 0n)); + }); +}); + +describe("sgn", () => { + it("[[Call]] returns the sign", () => { + assertStrictEquals(sgn(Infinity), 1); + assertStrictEquals(sgn(-Infinity), -1); + assertStrictEquals(sgn(0), 0); + assertStrictEquals(sgn(-0), -0); + assertStrictEquals(sgn(NaN), NaN); + }); + + it("[[Call]] works with big·ints", () => { + assertStrictEquals(sgn(0n), 0n); + assertStrictEquals(sgn(92n), 1n); + assertStrictEquals(sgn(-92n), -1n); + }); +}); + +describe("toBigInt", () => { + it("[[Call]] converts to a big·int", () => { + assertStrictEquals(toBigInt(2), 2n); + }); +}); + +describe("toFloat32", () => { + it("[[Call]] returns the 32‐bit floating‐point representation", () => { + assertStrictEquals( + toFloat32(562949953421313), + Math.fround(562949953421313), + ); + }); + + it("[[Call]] works with big·ints", () => { + assertStrictEquals( + toFloat32(562949953421313n), + Math.fround(562949953421313), + ); + }); +}); + +describe("toIntN", () => { + it("[[Call]] converts to an int·n", () => { + assertStrictEquals(toIntN(2, 7n), -1n); + }); + + it("[[Call]] works with numbers", () => { + assertStrictEquals(toIntN(2, 7), -1); + }); +}); + +describe("toNumber", () => { + it("[[Call]] converts to a number", () => { + assertStrictEquals(toNumber(2n), 2); + }); +}); + +describe("toNumeric", () => { + it("[[Call]] returns a big·int argument", () => { + assertStrictEquals(toNumeric(231n), 231n); + }); + + it("[[Call]] converts to a numeric", () => { + assertStrictEquals(toNumeric("231"), 231); + }); + + it("[[Call]] prefers `valueOf`", () => { + assertStrictEquals( + toNumeric({ + toString() { + return 0; + }, + valueOf() { + return 231n; + }, + }), + 231n, + ); + }); +}); + +describe("toUintN", () => { + it("[[Call]] converts to an int·n", () => { + assertStrictEquals(toUintN(2, 7n), 3n); + }); + + it("[[Call]] works with numbers", () => { + assertStrictEquals(toUintN(2, 7), 3); + }); +});