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