@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 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.6";
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 (point$1.equal(vertices[i], vertices[k])) {
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$1(areas) > 0;
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 number$1.equal(pair[0], pair[1], tolerance);
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