]> Lady’s Gitweb - Pisces/blob - numeric.js
Minor refactors to numeric.js
[Pisces] / numeric.js
1 // SPDX-FileCopyrightText: 2022, 2023, 2025 Lady <https://www.ladys.computer/about/#lady>
2 // SPDX-License-Identifier: MPL-2.0
3 /**
4 * ⁌ ♓🧩 Piscēs ∷ numeric.js
5 *
6 * Copyright © 2022–2023, 2025 Lady [@ Ladys Computer].
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
11 */
12
13 import { call, createArrowFunction } from "./function.js";
14 import { defineOwnDataProperty } from "./object.js";
15 import {
16 stringCatenate,
17 stringPadEnd,
18 stringRepeat,
19 substring,
20 toString,
21 } from "./string.js";
22 import {
23 NAN,
24 NEGATIVE_INFINITY,
25 POSITIVE_INFINITY,
26 sameValue,
27 toPrimitive,
28 UNDEFINED,
29 } from "./value.js";
30
31 const PISCĒS = "♓🧩 Piscēs";
32
33 /**
34 * Returns the magnitude (absolute value) of the provided value.
35 *
36 * ※ Unlike `Math.abs´, this function can take big·int arguments.
37 */
38 export const abs = ($) => {
39 const n = toNumeric($);
40 return typeof n === "bigint"
41 ? n < 0n ? -n : n
42 : isNan(n)
43 ? NAN
44 : sameValue(n, -0)
45 ? 0
46 : sameValue(n, NEGATIVE_INFINITY)
47 ? POSITIVE_INFINITY
48 : n < 0
49 ? -n
50 : n;
51 };
52
53 /**
54 * Returns the arccos of the provided value.
55 *
56 * ※ This function is effectively an alias for `Math.acos´.
57 *
58 * ☡ This function does not allow big·int arguments.
59 */
60 export const arccos = createArrowFunction(
61 Math.acos,
62 { name: "arccos" },
63 );
64
65 /**
66 * Returns the arccosh of the provided value.
67 *
68 * ※ This function is effectively an alias for `Math.acosh´.
69 *
70 * ☡ This function does not allow big·int arguments.
71 */
72 export const arccosh = createArrowFunction(
73 Math.acosh,
74 { name: "arccosh" },
75 );
76
77 /**
78 * Returns the arcsin of the provided value.
79 *
80 * ※ This function is effectively an alias for `Math.asin´.
81 *
82 * ☡ This function does not allow big·int arguments.
83 */
84 export const arcsin = createArrowFunction(
85 Math.asin,
86 { name: "arcsin" },
87 );
88
89 /**
90 * Returns the arcsinh of the provided value.
91 *
92 * ※ This function is effectively an alias for `Math.asinh´.
93 *
94 * ☡ This function does not allow big·int arguments.
95 */
96 export const arcsinh = createArrowFunction(
97 Math.asinh,
98 { name: "arcsinh" },
99 );
100
101 /**
102 * Returns the arctan of the provided value.
103 *
104 * ※ This function is effectively an alias for `Math.atan´.
105 *
106 * ☡ This function does not allow big·int arguments.
107 */
108 export const arctan = createArrowFunction(
109 Math.atan,
110 { name: "arctan" },
111 );
112
113 export const {
114 /**
115 * Returns the arctangent of the dividend of the provided values.
116 *
117 * ※ Unlike `Math.atan2´, this function can take big·int arguments.
118 * However, the result will always be a number.
119 */
120 arctan2,
121
122 /**
123 * Returns the number of leading zeroes in the 32‐bit representation
124 * of the provided value.
125 *
126 * ※ Unlike `Math.clz32´, this function accepts either number or
127 * big·int values.
128 */
129 clz32,
130
131 /**
132 * Returns the 32‐bit float which best approximate the provided
133 * value.
134 *
135 * ※ Unlike `Math.fround´, this function can take big·int arguments.
136 * However, the result will always be a number.
137 */
138 toFloat32,
139 } = (() => {
140 const { atan2, fround, clz32 } = Math;
141 return {
142 arctan2: (y, x) => atan2(toNumber(y), toNumber(x)),
143 clz32: ($) => {
144 const n = toNumeric($);
145 return clz32(
146 typeof n === "bigint"
147 ? toNumber(toUnsignedIntegralNumeric(32, n))
148 : n,
149 );
150 },
151 toFloat32: ($) => fround(toNumber($)),
152 };
153 })();
154
155 /**
156 * Returns the arctanh of the provided value.
157 *
158 * ※ This function is effectively an alias for `Math.atanh´.
159 *
160 * ☡ This function does not allow big·int arguments.
161 */
162 export const arctanh = createArrowFunction(
163 Math.atanh,
164 { name: "arctanh" },
165 );
166
167 /**
168 * Returns the cube root of the provided value.
169 *
170 * ※ This function is effectively an alias for `Math.cbrt´.
171 *
172 * ☡ This function does not allow big·int arguments.
173 */
174 export const cbrt = createArrowFunction(Math.cbrt);
175
176 /**
177 * Returns the ceiling of the provided value.
178 *
179 * ※ This function is effectively an alias for `Math.ceil´.
180 *
181 * ☡ This function does not allow big·int arguments.
182 */
183 export const ceil = createArrowFunction(Math.ceil);
184
185 /**
186 * Returns the cos of the provided value.
187 *
188 * ※ This function is effectively an alias for `Math.cos´.
189 *
190 * ☡ This function does not allow big·int arguments.
191 */
192 export const cos = createArrowFunction(Math.cos);
193
194 /**
195 * Returns the cosh of the provided value.
196 *
197 * ※ This function is effectively an alias for `Math.cosh´.
198 *
199 * ☡ This function does not allow big·int arguments.
200 */
201 export const cosh = createArrowFunction(Math.cosh);
202
203 /**
204 * Returns the Euler number raised to the provided value.
205 *
206 * ※ This function is effectively an alias for `Math.exp´.
207 *
208 * ☡ This function does not allow big·int arguments.
209 */
210 export const exp = createArrowFunction(Math.exp);
211
212 /**
213 * Returns the Euler number raised to the provided value, minus one.
214 *
215 * ※ This function is effectively an alias for `Math.expm1´.
216 *
217 * ☡ This function does not allow big·int arguments.
218 */
219 export const expm1 = createArrowFunction(Math.expm1);
220
221 /**
222 * Returns the floor of the provided value.
223 *
224 * ※ This function is effectively an alias for `Math.floor´.
225 *
226 * ☡ This function does not allow big·int arguments.
227 */
228 export const floor = createArrowFunction(Math.floor);
229
230 /**
231 * Returns the square root of the sum of the squares of the provided
232 * arguments.
233 *
234 * ※ This function is effectively an alias for `Math.hypot´.
235 *
236 * ☡ This function does not allow big·int arguments.
237 */
238 export const hypot = createArrowFunction(Math.hypot);
239
240 /**
241 * Returns whether the provided value is a finite number.
242 *
243 * ※ This function is effectively an alias for `Number.isFinite´.
244 */
245 export const isFiniteNumber = createArrowFunction(
246 Number.isFinite,
247 { name: "isFiniteNumber" },
248 );
249
250 /**
251 * Returns whether the provided value is an integral number.
252 *
253 * ※ This function is effectively an alias for `Number.isInteger´.
254 */
255 export const isIntegralNumber = createArrowFunction(
256 Number.isInteger,
257 { name: "isIntegralNumber" },
258 );
259
260 /**
261 * Returns whether the provided value is nan.
262 *
263 * ※ This function is effectively an alias for `Number.isNaN´.
264 */
265 export const isNan = createArrowFunction(
266 Number.isNaN,
267 { name: "isNan" },
268 );
269
270 /**
271 * Returns whether the provided value is a safe integral number.
272 *
273 * ※ This function is effectively an alias for `Number.isSafeInteger´.
274 */
275 export const isSafeIntegralNumber = createArrowFunction(
276 Number.isSafeInteger,
277 { name: "isSafeIntegralNumber" },
278 );
279
280 /**
281 * Returns the ln of the provided value.
282 *
283 * ※ This function is effectively an alias for `Math.log´.
284 *
285 * ☡ This function does not allow big·int arguments.
286 */
287 export const ln = createArrowFunction(Math.log, { name: "ln" });
288
289 /**
290 * Returns the ln of one plus the provided value.
291 *
292 * ※ This function is effectively an alias for `Math.log1p´.
293 *
294 * ☡ This function does not allow big·int arguments.
295 */
296 export const ln1p = createArrowFunction(Math.log1p, { name: "ln1p" });
297
298 /**
299 * Returns the log10 of the provided value.
300 *
301 * ※ This function is effectively an alias for `Math.log10´.
302 *
303 * ☡ This function does not allow big·int arguments.
304 */
305 export const log10 = createArrowFunction(Math.log10);
306
307 /**
308 * Returns the log2 of the provided value.
309 *
310 * ※ This function is effectively an alias for `Math.log2´.
311 *
312 * ☡ This function does not allow big·int arguments.
313 */
314 export const log2 = createArrowFunction(Math.log2);
315
316 /**
317 * Returns the highest value of the provided arguments, or negative
318 * infinity if no argument is provided.
319 *
320 * ※ Unlike `Math.max´, this function accepts either number or big·int
321 * values. All values must be of the same type, or this function will
322 * throw an error.
323 *
324 * ☡ If no argument is supplied, the result will be a number, not a
325 * big·int.
326 */
327 export const max = Object.defineProperties((...$s) => {
328 let highest = UNDEFINED;
329 for (let i = 0; i < $s.length; ++i) {
330 // Iterate over all the numbers.
331 const number = toNumeric($s[i]);
332 if (highest === UNDEFINED) {
333 // The current number is the first one.
334 if (isNan(number)) {
335 // The current number is nan.
336 return NAN;
337 } else {
338 // The current number is not nan.
339 highest = number;
340 }
341 } else {
342 if (typeof highest !== typeof number) {
343 // The type of the current number and the lowest number don¦t
344 // match.
345 throw new TypeError(`${PISCĒS}: Type mismatch.`);
346 } else if (isNan(number)) {
347 // The current number is nan.
348 return NAN;
349 } else if (sameValue(number, 0) && sameValue(highest, -0)) {
350 // The current number is +0 and the highest number is -0.
351 highest = 0;
352 } else if (number > highest) {
353 // The current number is greater than the highest number.
354 highest = number;
355 } else {
356 // The current number is less than or equal to the lowest
357 // number.
358 /* do nothing */
359 }
360 }
361 }
362 return highest ?? NEGATIVE_INFINITY;
363 }, {
364 name: defineOwnDataProperty(Object.create(null), "value", "max"),
365 length: defineOwnDataProperty(Object.create(null), "value", 2),
366 });
367
368 /**
369 * Returns the lowest value of the provided arguments, or positive
370 * infinity if no argument is provided.
371 *
372 * ※ Unlike `Math.min´, this function accepts either number or big·int
373 * values. All values must be of the same type, or this function will
374 * throw an error.
375 *
376 * ☡ If no argument is supplied, the result will be a number, not a
377 * big·int.
378 */
379 export const min = Object.defineProperties((...$s) => {
380 let lowest = UNDEFINED;
381 for (let i = 0; i < $s.length; ++i) {
382 // Iterate over all the numbers.
383 const number = toNumeric($s[i]);
384 if (lowest === UNDEFINED) {
385 // The current number is the first one.
386 if (isNan(number)) {
387 // The current number is nan.
388 return NAN;
389 } else {
390 // The current number is not nan.
391 lowest = number;
392 }
393 } else {
394 // The current number is not the first one.
395 if (typeof lowest !== typeof number) {
396 // The type of the current number and the lowest number don¦t
397 // match.
398 throw new TypeError(`${PISCĒS}: Type mismatch.`);
399 } else if (isNan(number)) {
400 // The current number is nan.
401 return NAN;
402 } else if (sameValue(number, -0) && sameValue(lowest, 0)) {
403 // The current number is -0 and the lowest number is +0.
404 lowest = -0;
405 } else if (number < lowest) {
406 // The current number is less than the lowest number.
407 lowest = number;
408 } else {
409 // The current number is greater than or equal to the lowest
410 // number.
411 /* do nothing */
412 }
413 }
414 }
415 return lowest ?? POSITIVE_INFINITY;
416 }, {
417 name: defineOwnDataProperty(Object.create(null), "value", "min"),
418 length: defineOwnDataProperty(Object.create(null), "value", 2),
419 });
420
421 /**
422 * Returns a pseudo·random value in the range [0, 1).
423 *
424 * ※ This function is effectively an alias for `Math.random´.
425 */
426 export const rand = createArrowFunction(
427 Math.random,
428 { name: "rand" },
429 );
430
431 /**
432 * Returns the round of the provided value.
433 *
434 * ※ This function is effectively an alias for `Math.round´.
435 *
436 * ☡ This function does not allow big·int arguments.
437 */
438 export const round = createArrowFunction(Math.round);
439
440 /**
441 * Returns a unit value with the same sign as the provided value, or
442 * the provided value itself if it is not a number or (potentially
443 * signed) zero.
444 *
445 * For big·ints, the return value of this function is 0n if the
446 * provided value is 0n, −1n if the provided value is negative, and +1n
447 * otherwise.
448 *
449 * For numbers, the return value is nan, −0, or +0 if the provided
450 * value is nan, −0, or +0, respectively, and −1 if the provided value
451 * is negative and +1 if the provided value is positive otherwise. Note
452 * that positive and negative infinity will return +1 and −1
453 * respectively.
454 *
455 * ※ Unlike `Math.sign´, this function accepts either number or
456 * big·int values.
457 */
458 export const sgn = ($) => {
459 const n = toNumeric($);
460 return typeof n === "bigint"
461 ? n === 0n ? 0n : n < 0n ? -1n : 1n
462 : isNan(n) || n === 0
463 ? n
464 //deno-lint-ignore no-compare-neg-zero
465 : n < -0
466 ? -1
467 : 1;
468 };
469
470 /**
471 * Returns the sin of the provided value.
472 *
473 * ※ This function is effectively an alias for `Math.sin´.
474 *
475 * ☡ This function does not allow big·int arguments.
476 */
477 export const sin = createArrowFunction(Math.sin);
478
479 /**
480 * Returns the sinh of the provided value.
481 *
482 * ※ This function is effectively an alias for `Math.sinh´.
483 *
484 * ☡ This function does not allow big·int arguments.
485 */
486 export const sinh = createArrowFunction(Math.sinh);
487
488 /**
489 * Returns the square root of the provided value.
490 *
491 * ※ This function is effectively an alias for `Math.sqrt´.
492 *
493 * ☡ This function does not allow big·int arguments.
494 */
495 export const sqrt = createArrowFunction(Math.sqrt);
496
497 /**
498 * Returns the tan of the provided value.
499 *
500 * ※ This function is effectively an alias for `Math.tan´.
501 *
502 * ☡ This function does not allow big·int arguments.
503 */
504 export const tan = createArrowFunction(Math.tan);
505
506 /**
507 * Returns the tanh of the provided value.
508 *
509 * ※ This function is effectively an alias for `Math.tanh´.
510 *
511 * ☡ This function does not allow big·int arguments.
512 */
513 export const tanh = createArrowFunction(Math.tanh);
514
515 /**
516 * Returns the result of converting the provided value to a big·int.
517 *
518 * ※ This method is safe to use with numbers.
519 *
520 * ※ This is effectively an alias for `BigInt´.
521 */
522 export const { toBigInt } = (() => {
523 const makeBigInt = BigInt;
524 return { toBigInt: ($) => makeBigInt($) };
525 })();
526
527 export const {
528 /**
529 * Returns the result of converting the provided value to an
530 * exponential notation string.
531 *
532 * If a second argument is provided, it gives the number of
533 * fractional digits to use in the mantissa. Otherwise, the smallest
534 * number which does not result in a reduction in precision is used.
535 *
536 * ※ This method is safe to use with big·ints.
537 */
538 toExponentialNotation,
539
540 /**
541 * Returns the result of converting the provided value to a fixed
542 * decimal notation string with the provided number of fractional
543 * digits.
544 *
545 * ※ This method is safe to use with big·ints.
546 */
547 toFixedDecimalNotation,
548 } = (() => {
549 const {
550 toExponential: numberToExponential,
551 toFixed: numberToFixed,
552 } = Number.prototype;
553 const { toString: bigintToString } = BigInt.prototype;
554 return {
555 toExponentialNotation: ($, fractionDigits) => {
556 const n = toNumeric($);
557 const f = toIntegralNumberOrInfinity(fractionDigits);
558 if (!isFiniteNumber(f) || f < 0 || f > 100) {
559 throw new RangeError(
560 `${PISCĒS}: The number of fractional digits must be a finite number between 0 and 100 inclusive; got: ${f}.`,
561 );
562 } else {
563 if (typeof n === "number") {
564 return call(
565 numberToExponential,
566 n,
567 [fractionDigits === UNDEFINED ? fractionDigits : f],
568 );
569 } else {
570 const digits = call(bigintToString, n, [10]);
571 const { length } = digits;
572 if (fractionDigits === UNDEFINED) {
573 return length === 1
574 ? `${digits[0]}e+0`
575 : `${digits[0]}.${substring(digits, 1)}e+${length - 1}`;
576 } else if (f === 0) {
577 return `${digits[0]}e+0`;
578 } else {
579 const fractionalPart = toString(
580 round(
581 +stringCatenate(
582 stringPadEnd(substring(digits, 1, f + 1), f, "0"),
583 ".",
584 digits[f + 1] || "0",
585 ),
586 ),
587 );
588 return `${digits[0]}.${fractionalPart}e+${length - 1}`;
589 }
590 }
591 }
592 },
593 toFixedDecimalNotation: ($, fractionDigits) => {
594 const f = toIntegralNumberOrInfinity(fractionDigits);
595 if (!isFiniteNumber(f) || f < 0 || f > 100) {
596 throw new RangeError(
597 `${PISCĒS}: The number of fractional digits must be a finite number between 0 and 100 inclusive; got: ${f}.`,
598 );
599 } else {
600 const n = toNumeric($);
601 if (typeof n === "number") {
602 return call(numberToFixed, n, [f]);
603 } else {
604 const digits = call(bigintToString, n, [10]);
605 return f === 0
606 ? digits
607 : `${digits}.${stringRepeat("0", f)}`;
608 }
609 }
610 },
611 };
612 })();
613
614 /**
615 * Returns the result of converting the provided number to an integral
616 * number.
617 *
618 * ※ This function will never return negative zero.
619 */
620 export const toIntegralNumber = ($) => {
621 const n = toIntegralNumberOrInfinity($);
622 return !isFiniteNumber(n) || n == 0 ? 0 : n;
623 };
624
625 /**
626 * Returns the result of converting the provided number to an integer
627 * or infinity.
628 *
629 * ※ Unlike the ToIntegerOrInfinity function defined in the Ecmascript
630 * specification, this function is safe to use with big·ints. However,
631 * the result will always be a number.
632 *
633 * ※ This function will never return negative zero.
634 */
635 export const toIntegralNumberOrInfinity = ($) => {
636 const integer = trunc(toNumber($));
637 if (isNan(integer) || integer == 0) {
638 // The provided value truncs to nan or (positive or negative) zero.
639 return 0;
640 } else if (integer == POSITIVE_INFINITY) {
641 // The provided value truncs to positive infinity.
642 return POSITIVE_INFINITY;
643 } else if (integer == NEGATIVE_INFINITY) {
644 // The provided value truncs to negative infinity.
645 return NEGATIVE_INFINITY;
646 } else {
647 // The provided value truncs to an integer.
648 return integer;
649 }
650 };
651
652 /**
653 * Returns the result of converting the provided value to a number.
654 *
655 * ※ This function is safe to use with big·ints.
656 *
657 * ※ This is effectively a nonconstructible version of the `Number´
658 * constructor.
659 */
660 export const toNumber = createArrowFunction(
661 Number,
662 { name: "toNumber" },
663 );
664
665 /**
666 * Returns the result of converting the provided value to a number or
667 * big·int.
668 *
669 * ※ If the result of converting the provided value to a primitive is
670 * not a big·int, this function will return a number.
671 */
672 export const toNumeric = ($) => {
673 const primValue = toPrimitive($, "number");
674 return typeof primValue === "bigint" ? primValue : +primValue;
675 };
676
677 export const {
678 /**
679 * Returns the result of converting the provided value to fit within
680 * the provided number of bits as a signed integer.
681 *
682 * ※ Unlike `BigInt.asIntN´, this function accepts both big·int and
683 * number values.
684 *
685 * ☡ The first argument, the number of bits, must be a number.
686 */
687 toSignedIntegralNumeric,
688
689 /**
690 * Returns the result of converting the provided value to fit within
691 * the provided number of bits as an unsigned integer.
692 *
693 * ※ Unlike `BigInt.asUintN´, this function accepts both big·int and
694 * number values.
695 *
696 * ☡ The first argument, the number of bits, must be a number.
697 */
698 toUnsignedIntegralNumeric,
699 } = (() => {
700 const { asIntN, asUintN } = BigInt;
701 return {
702 toSignedIntegralNumeric: (n, $) => {
703 const prim = toPrimitive($);
704 if (typeof prim === "bigint") {
705 // The primitive value is a big·int.
706 return asIntN(n, prim);
707 } else {
708 // The primitive value is not a big·int.
709 const int = trunc(prim);
710 if (!isFiniteNumber(int) || int == 0) {
711 // The truncated value is zero or not finite.
712 return 0;
713 } else {
714 // The truncated value is finite.
715 return toNumber(asIntN(n, toBigInt(int)));
716 }
717 }
718 },
719 toUnsignedIntegralNumeric: (n, $) => {
720 const prim = toPrimitive($);
721 if (typeof prim === "bigint") {
722 // The primitive value is a big·int.
723 return asUintN(n, prim);
724 } else {
725 // The primitive value is not a big·int.
726 const int = trunc(prim);
727 if (!isFiniteNumber(int) || int == 0) {
728 // The truncated value is zero or not finite.
729 return 0;
730 } else {
731 // The truncated value is finite.
732 return toNumber(asUintN(n, toBigInt(int)));
733 }
734 }
735 },
736 };
737 })();
738
739 /**
740 * Returns the trunc of the provided value.
741 *
742 * ※ This function is effectively an alias for `Math.trunc´.
743 *
744 * ☡ This function does not allow big·int arguments.
745 */
746 export const trunc = createArrowFunction(Math.trunc);
This page took 0.801099 seconds and 5 git commands to generate.