@khanacademy/kmath 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +239 -281
- package/dist/es/index.js.map +1 -1
- package/dist/index.js +240 -281
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/dist/shared-utils/add-library-version-to-perseus-debug.d.ts +0 -9
package/dist/index.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var perseusUtils = require('@khanacademy/perseus-utils');
|
|
5
6
|
var _ = require('underscore');
|
|
6
7
|
var perseusCore = require('@khanacademy/perseus-core');
|
|
7
|
-
var kmath = require('@khanacademy/kmath');
|
|
8
8
|
var $ = require('jquery');
|
|
9
9
|
|
|
10
10
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
@@ -12,51 +12,10 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
|
|
|
12
12
|
var ___default = /*#__PURE__*/_interopDefaultLegacy(_);
|
|
13
13
|
var $__default = /*#__PURE__*/_interopDefaultLegacy($);
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Adds the given perseus library version information to the __perseus_debug__
|
|
17
|
-
* object and ensures that the object is attached to `globalThis` (`window` in
|
|
18
|
-
* browser environments).
|
|
19
|
-
*
|
|
20
|
-
* This allows each library to provide runtime version information to assist in
|
|
21
|
-
* debugging in production environments.
|
|
22
|
-
*/
|
|
23
|
-
const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
|
|
24
|
-
// If the library version is the default value, then we don't want to
|
|
25
|
-
// prefix it with a "v" to indicate that it is a version number.
|
|
26
|
-
let prefix = "v";
|
|
27
|
-
if (libraryVersion === "__lib_version__") {
|
|
28
|
-
prefix = "";
|
|
29
|
-
}
|
|
30
|
-
const formattedVersion = `${prefix}${libraryVersion}`;
|
|
31
|
-
if (typeof globalThis !== "undefined") {
|
|
32
|
-
globalThis.__perseus_debug__ = globalThis.__perseus_debug__ ?? {};
|
|
33
|
-
const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
|
|
34
|
-
if (existingVersionEntry) {
|
|
35
|
-
// If we already have an entry and it doesn't match the registered
|
|
36
|
-
// version, we morph the entry into an array and log a warning.
|
|
37
|
-
if (existingVersionEntry !== formattedVersion) {
|
|
38
|
-
// Existing entry might be an array already (oops, at least 2
|
|
39
|
-
// versions of the library already loaded!).
|
|
40
|
-
const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
|
|
41
|
-
allVersions.push(formattedVersion);
|
|
42
|
-
globalThis.__perseus_debug__[libraryName] = allVersions;
|
|
43
|
-
|
|
44
|
-
// eslint-disable-next-line no-console
|
|
45
|
-
console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
|
|
46
|
-
}
|
|
47
|
-
} else {
|
|
48
|
-
globalThis.__perseus_debug__[libraryName] = formattedVersion;
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
// eslint-disable-next-line no-console
|
|
52
|
-
console.warn(`globalThis not found found (${formattedVersion})`);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
15
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
57
16
|
const libName = "@khanacademy/kmath";
|
|
58
|
-
const libVersion = "0.4.
|
|
59
|
-
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
17
|
+
const libVersion = "0.4.7";
|
|
18
|
+
perseusUtils.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
60
19
|
|
|
61
20
|
/**
|
|
62
21
|
* Number Utils
|
|
@@ -555,6 +514,240 @@ var ray = /*#__PURE__*/Object.freeze({
|
|
|
555
514
|
equal: equal
|
|
556
515
|
});
|
|
557
516
|
|
|
517
|
+
const KhanMath = {
|
|
518
|
+
// Simplify formulas before display
|
|
519
|
+
cleanMath: function (expr) {
|
|
520
|
+
return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
|
|
521
|
+
},
|
|
522
|
+
// Bound a number by 1e-6 and 1e20 to avoid exponents after toString
|
|
523
|
+
bound: function (num) {
|
|
524
|
+
if (num === 0) {
|
|
525
|
+
return num;
|
|
526
|
+
}
|
|
527
|
+
if (num < 0) {
|
|
528
|
+
return -KhanMath.bound(-num);
|
|
529
|
+
}
|
|
530
|
+
return Math.max(1e-6, Math.min(num, 1e20));
|
|
531
|
+
},
|
|
532
|
+
factorial: function (x) {
|
|
533
|
+
if (x <= 1) {
|
|
534
|
+
return x;
|
|
535
|
+
}
|
|
536
|
+
return x * KhanMath.factorial(x - 1);
|
|
537
|
+
},
|
|
538
|
+
getGCD: function (a, b) {
|
|
539
|
+
if (arguments.length > 2) {
|
|
540
|
+
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
541
|
+
// eslint-disable-next-line prefer-rest-params
|
|
542
|
+
const rest = [].slice.call(arguments, 1);
|
|
543
|
+
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
544
|
+
return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
|
|
545
|
+
}
|
|
546
|
+
let mod;
|
|
547
|
+
a = Math.abs(a);
|
|
548
|
+
b = Math.abs(b);
|
|
549
|
+
while (b) {
|
|
550
|
+
mod = a % b;
|
|
551
|
+
a = b;
|
|
552
|
+
b = mod;
|
|
553
|
+
}
|
|
554
|
+
return a;
|
|
555
|
+
},
|
|
556
|
+
getLCM: function (a, b) {
|
|
557
|
+
if (arguments.length > 2) {
|
|
558
|
+
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
559
|
+
// eslint-disable-next-line prefer-rest-params
|
|
560
|
+
const rest = [].slice.call(arguments, 1);
|
|
561
|
+
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
562
|
+
return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
|
|
563
|
+
}
|
|
564
|
+
return Math.abs(a * b) / KhanMath.getGCD(a, b);
|
|
565
|
+
},
|
|
566
|
+
primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97],
|
|
567
|
+
isPrime: function (n) {
|
|
568
|
+
if (n <= 1) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
if (n < 101) {
|
|
572
|
+
return !!$__default["default"].grep(KhanMath.primes, function (p, i) {
|
|
573
|
+
return Math.abs(p - n) <= 0.5;
|
|
574
|
+
}).length;
|
|
575
|
+
}
|
|
576
|
+
if (n <= 1 || n > 2 && n % 2 === 0) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
|
|
580
|
+
if (n % i === 0) {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return true;
|
|
585
|
+
},
|
|
586
|
+
// @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
|
|
587
|
+
getPrimeFactorization: function (number) {
|
|
588
|
+
if (number === 1) {
|
|
589
|
+
return [];
|
|
590
|
+
}
|
|
591
|
+
if (KhanMath.isPrime(number)) {
|
|
592
|
+
return [number];
|
|
593
|
+
}
|
|
594
|
+
const maxf = Math.sqrt(number);
|
|
595
|
+
for (let f = 2; f <= maxf; f++) {
|
|
596
|
+
if (number % f === 0) {
|
|
597
|
+
return $__default["default"].merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
// Round a number to the nearest increment
|
|
602
|
+
// E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
|
|
603
|
+
// num = 45, return 60.
|
|
604
|
+
roundToNearest: function (increment, num) {
|
|
605
|
+
return Math.round(num / increment) * increment;
|
|
606
|
+
},
|
|
607
|
+
// Round a number to a certain number of decimal places
|
|
608
|
+
roundTo: function (precision, num) {
|
|
609
|
+
const factor = Math.pow(10, precision).toFixed(5);
|
|
610
|
+
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'number'. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
|
|
611
|
+
return Math.round((num * factor).toFixed(5)) / factor;
|
|
612
|
+
},
|
|
613
|
+
/**
|
|
614
|
+
* Return a string of num rounded to a fixed precision decimal places,
|
|
615
|
+
* with an approx symbol if num had to be rounded, and trailing 0s
|
|
616
|
+
*/
|
|
617
|
+
toFixedApprox: function (num, precision) {
|
|
618
|
+
// TODO(aria): Make this locale-dependent
|
|
619
|
+
const fixedStr = num.toFixed(precision);
|
|
620
|
+
if (equal$4(+fixedStr, num)) {
|
|
621
|
+
return fixedStr;
|
|
622
|
+
}
|
|
623
|
+
return "\\approx " + fixedStr;
|
|
624
|
+
},
|
|
625
|
+
/**
|
|
626
|
+
* Return a string of num rounded to precision decimal places, with an
|
|
627
|
+
* approx symbol if num had to be rounded, but no trailing 0s if it was
|
|
628
|
+
* not rounded.
|
|
629
|
+
*/
|
|
630
|
+
roundToApprox: function (num, precision) {
|
|
631
|
+
const fixed = KhanMath.roundTo(precision, num);
|
|
632
|
+
if (equal$4(fixed, num)) {
|
|
633
|
+
return String(fixed);
|
|
634
|
+
}
|
|
635
|
+
return KhanMath.toFixedApprox(num, precision);
|
|
636
|
+
},
|
|
637
|
+
// toFraction(4/8) => [1, 2]
|
|
638
|
+
// toFraction(0.666) => [333, 500]
|
|
639
|
+
// toFraction(0.666, 0.001) => [2, 3]
|
|
640
|
+
//
|
|
641
|
+
// tolerance can't be bigger than 1, sorry
|
|
642
|
+
toFraction: function (decimal, tolerance) {
|
|
643
|
+
if (tolerance == null) {
|
|
644
|
+
tolerance = Math.pow(2, -46);
|
|
645
|
+
}
|
|
646
|
+
if (decimal < 0 || decimal > 1) {
|
|
647
|
+
let fract = decimal % 1;
|
|
648
|
+
fract += fract < 0 ? 1 : 0;
|
|
649
|
+
const nd = KhanMath.toFraction(fract, tolerance);
|
|
650
|
+
nd[0] += Math.round(decimal - fract) * nd[1];
|
|
651
|
+
return nd;
|
|
652
|
+
}
|
|
653
|
+
if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
|
|
654
|
+
return [Math.round(decimal), 1];
|
|
655
|
+
}
|
|
656
|
+
let loN = 0;
|
|
657
|
+
let loD = 1;
|
|
658
|
+
let hiN = 1;
|
|
659
|
+
let hiD = 1;
|
|
660
|
+
let midN = 1;
|
|
661
|
+
let midD = 2;
|
|
662
|
+
|
|
663
|
+
// eslint-disable-next-line no-constant-condition
|
|
664
|
+
while (true) {
|
|
665
|
+
if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
|
|
666
|
+
return [midN, midD];
|
|
667
|
+
}
|
|
668
|
+
if (midN / midD < decimal) {
|
|
669
|
+
loN = midN;
|
|
670
|
+
loD = midD;
|
|
671
|
+
} else {
|
|
672
|
+
hiN = midN;
|
|
673
|
+
hiD = midD;
|
|
674
|
+
}
|
|
675
|
+
midN = loN + hiN;
|
|
676
|
+
midD = loD + hiD;
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
// Returns the format (string) of a given numeric string
|
|
680
|
+
// Note: purposively more inclusive than answer-types' predicate.forms
|
|
681
|
+
// That is, it is not necessarily true that interpreted input are numeric
|
|
682
|
+
getNumericFormat: function (text) {
|
|
683
|
+
text = $__default["default"].trim(text);
|
|
684
|
+
text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
|
|
685
|
+
if (text.match(/^[+-]?\d+$/)) {
|
|
686
|
+
return "integer";
|
|
687
|
+
}
|
|
688
|
+
if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
|
|
689
|
+
return "mixed";
|
|
690
|
+
}
|
|
691
|
+
const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
|
|
692
|
+
if (fraction) {
|
|
693
|
+
return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
|
|
694
|
+
}
|
|
695
|
+
if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
|
|
696
|
+
return "decimal";
|
|
697
|
+
}
|
|
698
|
+
if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
|
|
699
|
+
return "pi";
|
|
700
|
+
}
|
|
701
|
+
return null;
|
|
702
|
+
},
|
|
703
|
+
// Returns a string of the number in a specified format
|
|
704
|
+
toNumericString: function (number$1, format) {
|
|
705
|
+
if (number$1 == null) {
|
|
706
|
+
return "";
|
|
707
|
+
}
|
|
708
|
+
if (number$1 === 0) {
|
|
709
|
+
return "0"; // otherwise it might end up as 0% or 0pi
|
|
710
|
+
}
|
|
711
|
+
if (format === "percent") {
|
|
712
|
+
return number$1 * 100 + "%";
|
|
713
|
+
}
|
|
714
|
+
if (format === "pi") {
|
|
715
|
+
const fraction = toFraction(number$1 / Math.PI);
|
|
716
|
+
const numerator = Math.abs(fraction[0]);
|
|
717
|
+
const denominator = fraction[1];
|
|
718
|
+
if (isInteger(numerator)) {
|
|
719
|
+
const sign = number$1 < 0 ? "-" : "";
|
|
720
|
+
const pi = "\u03C0";
|
|
721
|
+
return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (___default["default"](["proper", "improper", "mixed", "fraction"]).contains(format)) {
|
|
725
|
+
const fraction = toFraction(number$1);
|
|
726
|
+
const numerator = Math.abs(fraction[0]);
|
|
727
|
+
const denominator = fraction[1];
|
|
728
|
+
const sign = number$1 < 0 ? "-" : "";
|
|
729
|
+
if (denominator === 1) {
|
|
730
|
+
return sign + numerator; // for integers, irrational, d > 1000
|
|
731
|
+
}
|
|
732
|
+
if (format === "mixed") {
|
|
733
|
+
const modulus = numerator % denominator;
|
|
734
|
+
const integer = (numerator - modulus) / denominator;
|
|
735
|
+
return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
|
|
736
|
+
} // otherwise proper, improper, or fraction
|
|
737
|
+
return sign + numerator + "/" + denominator;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// otherwise (decimal, float, long long)
|
|
741
|
+
return String(number$1);
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
function sum(array) {
|
|
745
|
+
return array.reduce(add, 0);
|
|
746
|
+
}
|
|
747
|
+
function add(a, b) {
|
|
748
|
+
return a + b;
|
|
749
|
+
}
|
|
750
|
+
|
|
558
751
|
/**
|
|
559
752
|
* A collection of geomtry-related utility functions
|
|
560
753
|
*/
|
|
@@ -606,7 +799,7 @@ function polygonSidesIntersect(vertices) {
|
|
|
606
799
|
for (let i = 0; i < vertices.length; i++) {
|
|
607
800
|
for (let k = i + 1; k < vertices.length; k++) {
|
|
608
801
|
// If any two vertices are the same point, sides overlap
|
|
609
|
-
if (
|
|
802
|
+
if (equal$2(vertices[i], vertices[k])) {
|
|
610
803
|
return true;
|
|
611
804
|
}
|
|
612
805
|
|
|
@@ -646,7 +839,7 @@ function clockwise(points) {
|
|
|
646
839
|
const p2 = segment[1];
|
|
647
840
|
return (p2[0] - p1[0]) * (p2[1] + p1[1]);
|
|
648
841
|
});
|
|
649
|
-
return
|
|
842
|
+
return sum(areas) > 0;
|
|
650
843
|
}
|
|
651
844
|
function magnitude(v) {
|
|
652
845
|
return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
|
|
@@ -723,7 +916,7 @@ function similar(coords1, coords2, tolerance) {
|
|
|
723
916
|
return perseusCore.approximateEqual(factors[0], factor);
|
|
724
917
|
});
|
|
725
918
|
const congruentEnough = ___default["default"].all(sidePairs, function (pair) {
|
|
726
|
-
return
|
|
919
|
+
return equal$4(pair[0], pair[1], tolerance);
|
|
727
920
|
});
|
|
728
921
|
if (same && congruentEnough) {
|
|
729
922
|
return true;
|
|
@@ -942,240 +1135,6 @@ var coefficients = /*#__PURE__*/Object.freeze({
|
|
|
942
1135
|
getQuadraticCoefficients: getQuadraticCoefficients
|
|
943
1136
|
});
|
|
944
1137
|
|
|
945
|
-
const KhanMath = {
|
|
946
|
-
// Simplify formulas before display
|
|
947
|
-
cleanMath: function (expr) {
|
|
948
|
-
return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
|
|
949
|
-
},
|
|
950
|
-
// Bound a number by 1e-6 and 1e20 to avoid exponents after toString
|
|
951
|
-
bound: function (num) {
|
|
952
|
-
if (num === 0) {
|
|
953
|
-
return num;
|
|
954
|
-
}
|
|
955
|
-
if (num < 0) {
|
|
956
|
-
return -KhanMath.bound(-num);
|
|
957
|
-
}
|
|
958
|
-
return Math.max(1e-6, Math.min(num, 1e20));
|
|
959
|
-
},
|
|
960
|
-
factorial: function (x) {
|
|
961
|
-
if (x <= 1) {
|
|
962
|
-
return x;
|
|
963
|
-
}
|
|
964
|
-
return x * KhanMath.factorial(x - 1);
|
|
965
|
-
},
|
|
966
|
-
getGCD: function (a, b) {
|
|
967
|
-
if (arguments.length > 2) {
|
|
968
|
-
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
969
|
-
// eslint-disable-next-line prefer-rest-params
|
|
970
|
-
const rest = [].slice.call(arguments, 1);
|
|
971
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
972
|
-
return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
|
|
973
|
-
}
|
|
974
|
-
let mod;
|
|
975
|
-
a = Math.abs(a);
|
|
976
|
-
b = Math.abs(b);
|
|
977
|
-
while (b) {
|
|
978
|
-
mod = a % b;
|
|
979
|
-
a = b;
|
|
980
|
-
b = mod;
|
|
981
|
-
}
|
|
982
|
-
return a;
|
|
983
|
-
},
|
|
984
|
-
getLCM: function (a, b) {
|
|
985
|
-
if (arguments.length > 2) {
|
|
986
|
-
// TODO(kevinb): rewrite using rest args instead of arguments
|
|
987
|
-
// eslint-disable-next-line prefer-rest-params
|
|
988
|
-
const rest = [].slice.call(arguments, 1);
|
|
989
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
990
|
-
return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
|
|
991
|
-
}
|
|
992
|
-
return Math.abs(a * b) / KhanMath.getGCD(a, b);
|
|
993
|
-
},
|
|
994
|
-
primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97],
|
|
995
|
-
isPrime: function (n) {
|
|
996
|
-
if (n <= 1) {
|
|
997
|
-
return false;
|
|
998
|
-
}
|
|
999
|
-
if (n < 101) {
|
|
1000
|
-
return !!$__default["default"].grep(KhanMath.primes, function (p, i) {
|
|
1001
|
-
return Math.abs(p - n) <= 0.5;
|
|
1002
|
-
}).length;
|
|
1003
|
-
}
|
|
1004
|
-
if (n <= 1 || n > 2 && n % 2 === 0) {
|
|
1005
|
-
return false;
|
|
1006
|
-
}
|
|
1007
|
-
for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
|
|
1008
|
-
if (n % i === 0) {
|
|
1009
|
-
return false;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
return true;
|
|
1013
|
-
},
|
|
1014
|
-
// @ts-expect-error - TS2366 - Function lacks ending return statement and return type does not include 'undefined'.
|
|
1015
|
-
getPrimeFactorization: function (number) {
|
|
1016
|
-
if (number === 1) {
|
|
1017
|
-
return [];
|
|
1018
|
-
}
|
|
1019
|
-
if (KhanMath.isPrime(number)) {
|
|
1020
|
-
return [number];
|
|
1021
|
-
}
|
|
1022
|
-
const maxf = Math.sqrt(number);
|
|
1023
|
-
for (let f = 2; f <= maxf; f++) {
|
|
1024
|
-
if (number % f === 0) {
|
|
1025
|
-
return $__default["default"].merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
},
|
|
1029
|
-
// Round a number to the nearest increment
|
|
1030
|
-
// E.g., if increment = 30 and num = 40, return 30. if increment = 30 and
|
|
1031
|
-
// num = 45, return 60.
|
|
1032
|
-
roundToNearest: function (increment, num) {
|
|
1033
|
-
return Math.round(num / increment) * increment;
|
|
1034
|
-
},
|
|
1035
|
-
// Round a number to a certain number of decimal places
|
|
1036
|
-
roundTo: function (precision, num) {
|
|
1037
|
-
const factor = Math.pow(10, precision).toFixed(5);
|
|
1038
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'number'. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type. | TS2363 - The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.
|
|
1039
|
-
return Math.round((num * factor).toFixed(5)) / factor;
|
|
1040
|
-
},
|
|
1041
|
-
/**
|
|
1042
|
-
* Return a string of num rounded to a fixed precision decimal places,
|
|
1043
|
-
* with an approx symbol if num had to be rounded, and trailing 0s
|
|
1044
|
-
*/
|
|
1045
|
-
toFixedApprox: function (num, precision) {
|
|
1046
|
-
// TODO(aria): Make this locale-dependent
|
|
1047
|
-
const fixedStr = num.toFixed(precision);
|
|
1048
|
-
if (kmath.number.equal(+fixedStr, num)) {
|
|
1049
|
-
return fixedStr;
|
|
1050
|
-
}
|
|
1051
|
-
return "\\approx " + fixedStr;
|
|
1052
|
-
},
|
|
1053
|
-
/**
|
|
1054
|
-
* Return a string of num rounded to precision decimal places, with an
|
|
1055
|
-
* approx symbol if num had to be rounded, but no trailing 0s if it was
|
|
1056
|
-
* not rounded.
|
|
1057
|
-
*/
|
|
1058
|
-
roundToApprox: function (num, precision) {
|
|
1059
|
-
const fixed = KhanMath.roundTo(precision, num);
|
|
1060
|
-
if (kmath.number.equal(fixed, num)) {
|
|
1061
|
-
return String(fixed);
|
|
1062
|
-
}
|
|
1063
|
-
return KhanMath.toFixedApprox(num, precision);
|
|
1064
|
-
},
|
|
1065
|
-
// toFraction(4/8) => [1, 2]
|
|
1066
|
-
// toFraction(0.666) => [333, 500]
|
|
1067
|
-
// toFraction(0.666, 0.001) => [2, 3]
|
|
1068
|
-
//
|
|
1069
|
-
// tolerance can't be bigger than 1, sorry
|
|
1070
|
-
toFraction: function (decimal, tolerance) {
|
|
1071
|
-
if (tolerance == null) {
|
|
1072
|
-
tolerance = Math.pow(2, -46);
|
|
1073
|
-
}
|
|
1074
|
-
if (decimal < 0 || decimal > 1) {
|
|
1075
|
-
let fract = decimal % 1;
|
|
1076
|
-
fract += fract < 0 ? 1 : 0;
|
|
1077
|
-
const nd = KhanMath.toFraction(fract, tolerance);
|
|
1078
|
-
nd[0] += Math.round(decimal - fract) * nd[1];
|
|
1079
|
-
return nd;
|
|
1080
|
-
}
|
|
1081
|
-
if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
|
|
1082
|
-
return [Math.round(decimal), 1];
|
|
1083
|
-
}
|
|
1084
|
-
let loN = 0;
|
|
1085
|
-
let loD = 1;
|
|
1086
|
-
let hiN = 1;
|
|
1087
|
-
let hiD = 1;
|
|
1088
|
-
let midN = 1;
|
|
1089
|
-
let midD = 2;
|
|
1090
|
-
|
|
1091
|
-
// eslint-disable-next-line no-constant-condition
|
|
1092
|
-
while (true) {
|
|
1093
|
-
if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
|
|
1094
|
-
return [midN, midD];
|
|
1095
|
-
}
|
|
1096
|
-
if (midN / midD < decimal) {
|
|
1097
|
-
loN = midN;
|
|
1098
|
-
loD = midD;
|
|
1099
|
-
} else {
|
|
1100
|
-
hiN = midN;
|
|
1101
|
-
hiD = midD;
|
|
1102
|
-
}
|
|
1103
|
-
midN = loN + hiN;
|
|
1104
|
-
midD = loD + hiD;
|
|
1105
|
-
}
|
|
1106
|
-
},
|
|
1107
|
-
// Returns the format (string) of a given numeric string
|
|
1108
|
-
// Note: purposively more inclusive than answer-types' predicate.forms
|
|
1109
|
-
// That is, it is not necessarily true that interpreted input are numeric
|
|
1110
|
-
getNumericFormat: function (text) {
|
|
1111
|
-
text = $__default["default"].trim(text);
|
|
1112
|
-
text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
|
|
1113
|
-
if (text.match(/^[+-]?\d+$/)) {
|
|
1114
|
-
return "integer";
|
|
1115
|
-
}
|
|
1116
|
-
if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
|
|
1117
|
-
return "mixed";
|
|
1118
|
-
}
|
|
1119
|
-
const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
|
|
1120
|
-
if (fraction) {
|
|
1121
|
-
return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
|
|
1122
|
-
}
|
|
1123
|
-
if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
|
|
1124
|
-
return "decimal";
|
|
1125
|
-
}
|
|
1126
|
-
if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
|
|
1127
|
-
return "pi";
|
|
1128
|
-
}
|
|
1129
|
-
return null;
|
|
1130
|
-
},
|
|
1131
|
-
// Returns a string of the number in a specified format
|
|
1132
|
-
toNumericString: function (number, format) {
|
|
1133
|
-
if (number == null) {
|
|
1134
|
-
return "";
|
|
1135
|
-
}
|
|
1136
|
-
if (number === 0) {
|
|
1137
|
-
return "0"; // otherwise it might end up as 0% or 0pi
|
|
1138
|
-
}
|
|
1139
|
-
if (format === "percent") {
|
|
1140
|
-
return number * 100 + "%";
|
|
1141
|
-
}
|
|
1142
|
-
if (format === "pi") {
|
|
1143
|
-
const fraction = kmath.number.toFraction(number / Math.PI);
|
|
1144
|
-
const numerator = Math.abs(fraction[0]);
|
|
1145
|
-
const denominator = fraction[1];
|
|
1146
|
-
if (kmath.number.isInteger(numerator)) {
|
|
1147
|
-
const sign = number < 0 ? "-" : "";
|
|
1148
|
-
const pi = "\u03C0";
|
|
1149
|
-
return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
if (___default["default"](["proper", "improper", "mixed", "fraction"]).contains(format)) {
|
|
1153
|
-
const fraction = kmath.number.toFraction(number);
|
|
1154
|
-
const numerator = Math.abs(fraction[0]);
|
|
1155
|
-
const denominator = fraction[1];
|
|
1156
|
-
const sign = number < 0 ? "-" : "";
|
|
1157
|
-
if (denominator === 1) {
|
|
1158
|
-
return sign + numerator; // for integers, irrational, d > 1000
|
|
1159
|
-
}
|
|
1160
|
-
if (format === "mixed") {
|
|
1161
|
-
const modulus = numerator % denominator;
|
|
1162
|
-
const integer = (numerator - modulus) / denominator;
|
|
1163
|
-
return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
|
|
1164
|
-
} // otherwise proper, improper, or fraction
|
|
1165
|
-
return sign + numerator + "/" + denominator;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
// otherwise (decimal, float, long long)
|
|
1169
|
-
return String(number);
|
|
1170
|
-
}
|
|
1171
|
-
};
|
|
1172
|
-
function sum(array) {
|
|
1173
|
-
return array.reduce(add, 0);
|
|
1174
|
-
}
|
|
1175
|
-
function add(a, b) {
|
|
1176
|
-
return a + b;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
1138
|
exports.KhanMath = KhanMath;
|
|
1180
1139
|
exports.angles = angles;
|
|
1181
1140
|
exports.coefficients = coefficients;
|