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