@khanacademy/kmath 0.4.6 → 1.0.0

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
@@ -1,81 +1,21 @@
1
- 'use strict';
1
+ import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
2
+ import _ from 'underscore';
3
+ import { approximateEqual, approximateDeepEqual } from '@khanacademy/perseus-core';
4
+ import $ from 'jquery';
2
5
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var _ = require('underscore');
6
- var perseusCore = require('@khanacademy/perseus-core');
7
- var kmath = require('@khanacademy/kmath');
8
- var $ = require('jquery');
9
-
10
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
-
12
- var ___default = /*#__PURE__*/_interopDefaultLegacy(_);
13
- var $__default = /*#__PURE__*/_interopDefaultLegacy($);
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
- // This file is processed by a Rollup plugin (replace) to inject the production
57
6
  const libName = "@khanacademy/kmath";
58
- const libVersion = "0.4.6";
7
+ const libVersion = "1.0.0";
59
8
  addLibraryVersionToPerseusDebug(libName, libVersion);
60
9
 
61
- /**
62
- * Number Utils
63
- * A number is a js-number, e.g. 5.12
64
- */
65
10
  const DEFAULT_TOLERANCE = 1e-9;
66
-
67
- // TODO: Should this just be Number.Epsilon
68
11
  const EPSILON = Math.pow(2, -42);
69
12
  function is$2(x) {
70
- return ___default["default"].isNumber(x) && !___default["default"].isNaN(x);
13
+ return _.isNumber(x) && !_.isNaN(x);
71
14
  }
72
15
  function equal$4(x, y, tolerance) {
73
- // Checking for undefined makes this function behave nicely
74
- // with vectors of different lengths that are _.zip'd together
75
16
  if (x == null || y == null) {
76
17
  return x === y;
77
18
  }
78
- // We check === here so that +/-Infinity comparisons work correctly
79
19
  if (x === y) {
80
20
  return true;
81
21
  }
@@ -84,21 +24,16 @@ function equal$4(x, y, tolerance) {
84
24
  }
85
25
  return Math.abs(x - y) < tolerance;
86
26
  }
87
- function sign$1(x, tolerance) /* Should be: 0 | 1 | -1 */{
27
+ function sign$1(x, tolerance) {
88
28
  return equal$4(x, 0, tolerance) ? 0 : Math.abs(x) / x;
89
29
  }
90
30
  function isInteger(num, tolerance) {
91
31
  return equal$4(Math.round(num), num, tolerance);
92
32
  }
93
-
94
- // Round a number to a certain number of decimal places
95
33
  function round$2(num, precision) {
96
34
  const factor = Math.pow(10, precision);
97
35
  return Math.round(num * factor) / factor;
98
36
  }
99
-
100
- // Round num to the nearest multiple of increment
101
- // i.e. roundTo(83, 5) -> 85
102
37
  function roundTo$2(num, increment) {
103
38
  return Math.round(num / increment) * increment;
104
39
  }
@@ -108,26 +43,7 @@ function floorTo$2(num, increment) {
108
43
  function ceilTo$2(num, increment) {
109
44
  return Math.ceil(num / increment) * increment;
110
45
  }
111
-
112
- /**
113
- * toFraction
114
- *
115
- * Returns a [numerator, denominator] array rational representation
116
- * of `decimal`
117
- *
118
- * See http://en.wikipedia.org/wiki/Continued_fraction for implementation
119
- * details
120
- *
121
- * toFraction(4/8) => [1, 2]
122
- * toFraction(0.66) => [33, 50]
123
- * toFraction(0.66, 0.01) => [2/3]
124
- * toFraction(283 + 1/3) => [850, 3]
125
- */
126
- function toFraction(decimal) {
127
- let tolerance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : EPSILON;
128
- let maxDenominator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1000;
129
- // Initialize everything to compute successive terms of
130
- // continued-fraction approximations via recurrence relation
46
+ function toFraction(decimal, tolerance = EPSILON, maxDenominator = 1000) {
131
47
  let n = [1, 0];
132
48
  let d = [0, 1];
133
49
  let a = Math.floor(decimal);
@@ -141,9 +57,6 @@ function toFraction(decimal) {
141
57
  a = Math.floor(1 / rem);
142
58
  rem = 1 / rem - a;
143
59
  }
144
-
145
- // We failed to find a nice rational representation,
146
- // so return an irrational "fraction"
147
60
  return [decimal, 1];
148
61
  }
149
62
 
@@ -151,31 +64,24 @@ var number = /*#__PURE__*/Object.freeze({
151
64
  __proto__: null,
152
65
  DEFAULT_TOLERANCE: DEFAULT_TOLERANCE,
153
66
  EPSILON: EPSILON,
154
- is: is$2,
67
+ ceilTo: ceilTo$2,
155
68
  equal: equal$4,
156
- sign: sign$1,
69
+ floorTo: floorTo$2,
70
+ is: is$2,
157
71
  isInteger: isInteger,
158
72
  round: round$2,
159
73
  roundTo: roundTo$2,
160
- floorTo: floorTo$2,
161
- ceilTo: ceilTo$2,
74
+ sign: sign$1,
162
75
  toFraction: toFraction
163
76
  });
164
77
 
165
- /**
166
- * Vector Utils
167
- * A vector is an array of numbers e.g. [0, 3, 4].
168
- */
169
78
  function arraySum(array) {
170
79
  return array.reduce((memo, arg) => memo + arg, 0);
171
80
  }
172
81
  function arrayProduct(array) {
173
82
  return array.reduce((memo, arg) => memo * arg, 1);
174
83
  }
175
- function zip() {
176
- for (var _len = arguments.length, arrays = new Array(_len), _key = 0; _key < _len; _key++) {
177
- arrays[_key] = arguments[_key];
178
- }
84
+ function zip(...arrays) {
179
85
  const n = Math.min(...arrays.map(a => a.length));
180
86
  const results = [];
181
87
  for (let i = 0; i < n; i++) {
@@ -186,16 +92,6 @@ function zip() {
186
92
  function map(pair, f) {
187
93
  return [f(pair[0], 0), f(pair[1], 1)];
188
94
  }
189
-
190
- /**
191
- * Checks if the given vector contains only numbers and, optionally, is of the
192
- * right dimension (length).
193
- *
194
- * is([1, 2, 3]) -> true
195
- * is([1, "Hello", 3]) -> false
196
- * is([1, 2, 3], 1) -> false
197
- */
198
-
199
95
  function is$1(vec, dimension) {
200
96
  if (!Array.isArray(vec)) {
201
97
  return false;
@@ -205,46 +101,30 @@ function is$1(vec, dimension) {
205
101
  }
206
102
  return vec.every(is$2);
207
103
  }
208
-
209
- // Normalize to a unit vector
210
104
  function normalize(v) {
211
105
  return scale(v, 1 / length(v));
212
106
  }
213
-
214
- // Length/magnitude of a vector
215
107
  function length(v) {
216
108
  return Math.sqrt(dot(v, v));
217
109
  }
218
- // Dot product of two vectors
219
110
  function dot(a, b) {
220
111
  const zipped = zip(a, b);
221
112
  const multiplied = zipped.map(arrayProduct);
222
113
  return arraySum(multiplied);
223
114
  }
224
-
225
- /* vector-add multiple [x, y] coords/vectors
226
- *
227
- * add([1, 2], [3, 4]) -> [4, 6]
228
- */
229
- function add$1() {
230
- const zipped = zip(...arguments);
231
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
115
+ function add$1(...vecs) {
116
+ const zipped = zip(...vecs);
232
117
  return zipped.map(arraySum);
233
118
  }
234
119
  function subtract(v1, v2) {
235
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
236
120
  return zip(v1, v2).map(dim => dim[0] - dim[1]);
237
121
  }
238
122
  function negate(v) {
239
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
240
123
  return v.map(x => {
241
124
  return -x;
242
125
  });
243
126
  }
244
-
245
- // Scale a vector
246
127
  function scale(v1, scalar) {
247
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
248
128
  return v1.map(x => {
249
129
  return x * scalar;
250
130
  });
@@ -253,9 +133,6 @@ function equal$3(v1, v2, tolerance) {
253
133
  return v1.length === v2.length && zip(v1, v2).every(pair => equal$4(pair[0], pair[1], tolerance));
254
134
  }
255
135
  function codirectional(v1, v2, tolerance) {
256
- // The origin is trivially codirectional with all other vectors.
257
- // This gives nice semantics for codirectionality between points when
258
- // comparing their difference vectors.
259
136
  if (equal$4(length(v1), 0, tolerance) || equal$4(length(v2), 0, tolerance)) {
260
137
  return true;
261
138
  }
@@ -266,48 +143,24 @@ function codirectional(v1, v2, tolerance) {
266
143
  function collinear$1(v1, v2, tolerance) {
267
144
  return codirectional(v1, v2, tolerance) || codirectional(v1, negate(v2), tolerance);
268
145
  }
269
-
270
- // TODO(jeremy) These coordinate conversion functions really only handle 2D points (ie. [number, number])
271
-
272
- // Convert a cartesian coordinate into a radian polar coordinate
273
146
  function polarRadFromCart$1(v) {
274
147
  const radius = length(v);
275
148
  let theta = Math.atan2(v[1], v[0]);
276
-
277
- // Convert angle range from [-pi, pi] to [0, 2pi]
278
149
  if (theta < 0) {
279
150
  theta += 2 * Math.PI;
280
151
  }
281
152
  return [radius, theta];
282
153
  }
283
-
284
- // Converts a cartesian coordinate into a degree polar coordinate
285
- function polarDegFromCart$1(v) /* TODO: convert to tuple/Point */{
154
+ function polarDegFromCart$1(v) {
286
155
  const polar = polarRadFromCart$1(v);
287
156
  return [polar[0], polar[1] * 180 / Math.PI];
288
157
  }
289
-
290
- /* Convert a polar coordinate into a cartesian coordinate
291
- *
292
- * Examples:
293
- * cartFromPolarRad(5, Math.PI)
294
- */
295
- function cartFromPolarRad$1(radius) /* TODO: convert to tuple/Point */{
296
- let theta = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
158
+ function cartFromPolarRad$1(radius, theta = 0) {
297
159
  return [radius * Math.cos(theta), radius * Math.sin(theta)];
298
160
  }
299
-
300
- /* Convert a polar coordinate into a cartesian coordinate
301
- *
302
- * Examples:
303
- * cartFromPolarDeg(5, 30)
304
- */
305
- function cartFromPolarDeg$1(radius) {
306
- let theta = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
161
+ function cartFromPolarDeg$1(radius, theta = 0) {
307
162
  return cartFromPolarRad$1(radius, theta * Math.PI / 180);
308
163
  }
309
-
310
- // Rotate vector
311
164
  function rotateRad$1(v, theta) {
312
165
  const polar = polarRadFromCart$1(v);
313
166
  const angle = polar[1] + theta;
@@ -318,80 +171,59 @@ function rotateDeg$1(v, theta) {
318
171
  const angle = polar[1] + theta;
319
172
  return cartFromPolarDeg$1(polar[0], angle);
320
173
  }
321
-
322
- // Angle between two vectors
323
174
  function angleRad(v1, v2) {
324
175
  return Math.acos(dot(v1, v2) / (length(v1) * length(v2)));
325
176
  }
326
177
  function angleDeg(v1, v2) {
327
178
  return angleRad(v1, v2) * 180 / Math.PI;
328
179
  }
329
-
330
- // Vector projection of v1 onto v2
331
180
  function projection(v1, v2) {
332
181
  const scalar = dot(v1, v2) / dot(v2, v2);
333
182
  return scale(v2, scalar);
334
183
  }
335
-
336
- // Round each number to a certain number of decimal places
337
184
  function round$1(vec, precision) {
338
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
339
185
  return vec.map((elem, i) => round$2(elem, precision[i] || precision));
340
186
  }
341
-
342
- // Round each number to the nearest increment
343
-
344
187
  function roundTo$1(vec, increment) {
345
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
346
188
  return vec.map((elem, i) => roundTo$2(elem, increment[i] || increment));
347
189
  }
348
190
  function floorTo$1(vec, increment) {
349
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
350
191
  return vec.map((elem, i) => floorTo$2(elem, increment[i] || increment));
351
192
  }
352
193
  function ceilTo$1(vec, increment) {
353
- // @ts-expect-error - TS2322 - Type 'number[]' is not assignable to type 'V'.
354
194
  return vec.map((elem, i) => ceilTo$2(elem, increment[i] || increment));
355
195
  }
356
196
 
357
197
  var vector$1 = /*#__PURE__*/Object.freeze({
358
198
  __proto__: null,
359
- zip: zip,
360
- map: map,
361
- is: is$1,
362
- normalize: normalize,
363
- length: length,
364
- dot: dot,
365
199
  add: add$1,
366
- subtract: subtract,
367
- negate: negate,
368
- scale: scale,
369
- equal: equal$3,
200
+ angleDeg: angleDeg,
201
+ angleRad: angleRad,
202
+ cartFromPolarDeg: cartFromPolarDeg$1,
203
+ cartFromPolarRad: cartFromPolarRad$1,
204
+ ceilTo: ceilTo$1,
370
205
  codirectional: codirectional,
371
206
  collinear: collinear$1,
372
- polarRadFromCart: polarRadFromCart$1,
207
+ dot: dot,
208
+ equal: equal$3,
209
+ floorTo: floorTo$1,
210
+ is: is$1,
211
+ length: length,
212
+ map: map,
213
+ negate: negate,
214
+ normalize: normalize,
373
215
  polarDegFromCart: polarDegFromCart$1,
374
- cartFromPolarRad: cartFromPolarRad$1,
375
- cartFromPolarDeg: cartFromPolarDeg$1,
376
- rotateRad: rotateRad$1,
377
- rotateDeg: rotateDeg$1,
378
- angleRad: angleRad,
379
- angleDeg: angleDeg,
216
+ polarRadFromCart: polarRadFromCart$1,
380
217
  projection: projection,
218
+ rotateDeg: rotateDeg$1,
219
+ rotateRad: rotateRad$1,
381
220
  round: round$1,
382
221
  roundTo: roundTo$1,
383
- floorTo: floorTo$1,
384
- ceilTo: ceilTo$1
222
+ scale: scale,
223
+ subtract: subtract,
224
+ zip: zip
385
225
  });
386
226
 
387
- /**
388
- * Point Utils
389
- * A point is an array of two numbers e.g. [0, 0].
390
- */
391
-
392
- // A point, in 2D, 3D, or nD space.
393
-
394
- // Rotate point (around origin unless a center is specified)
395
227
  function rotateRad(point, theta, center) {
396
228
  if (center === undefined) {
397
229
  return rotateRad$1(point, theta);
@@ -406,13 +238,9 @@ function rotateDeg(point, theta, center) {
406
238
  return add$1(center, rotateDeg$1(subtract(point, center), theta));
407
239
  }
408
240
  }
409
-
410
- // Distance between two points
411
241
  function distanceToPoint$1(point1, point2) {
412
242
  return length(subtract(point1, point2));
413
243
  }
414
-
415
- // Distance between point and line
416
244
  function distanceToLine(point, line) {
417
245
  const lv = subtract(line[1], line[0]);
418
246
  const pv = subtract(point, line[0]);
@@ -420,8 +248,6 @@ function distanceToLine(point, line) {
420
248
  const distancePv = subtract(projectedPv, pv);
421
249
  return length(distancePv);
422
250
  }
423
-
424
- // Reflect point over line
425
251
  function reflectOverLine(point, line) {
426
252
  const lv = subtract(line[1], line[0]);
427
253
  const pv = subtract(point, line[0]);
@@ -429,17 +255,7 @@ function reflectOverLine(point, line) {
429
255
  const reflectedPv = subtract(scale(projectedPv, 2), pv);
430
256
  return add$1(line[0], reflectedPv);
431
257
  }
432
-
433
- /**
434
- * Compares two points, returning -1, 0, or 1, for use with
435
- * Array.prototype.sort
436
- *
437
- * Note: This technically doesn't satisfy the total-ordering
438
- * requirements of Array.prototype.sort unless equalityTolerance
439
- * is 0. In some cases very close points that compare within a
440
- * few equalityTolerances could appear in the wrong order.
441
- */
442
- function compare(point1, point2, equalityTolerance) /* TODO: convert to -1 | 0 | 1 type */{
258
+ function compare(point1, point2, equalityTolerance) {
443
259
  if (point1.length !== point2.length) {
444
260
  return point1.length - point2.length;
445
261
  }
@@ -450,23 +266,15 @@ function compare(point1, point2, equalityTolerance) /* TODO: convert to -1 | 0 |
450
266
  }
451
267
  return 0;
452
268
  }
453
-
454
- // Check if a value is a point
455
269
  const is = is$1;
456
-
457
- // Add and subtract vector(s)
458
270
  const addVector = add$1;
459
271
  const addVectors = add$1;
460
272
  const subtractVector = subtract;
461
273
  const equal$2 = equal$3;
462
-
463
- // Convert from cartesian to polar and back
464
274
  const polarRadFromCart = polarRadFromCart$1;
465
275
  const polarDegFromCart = polarDegFromCart$1;
466
276
  const cartFromPolarRad = cartFromPolarRad$1;
467
277
  const cartFromPolarDeg = cartFromPolarDeg$1;
468
-
469
- // Rounding
470
278
  const round = round$1;
471
279
  const roundTo = roundTo$1;
472
280
  const floorTo = floorTo$1;
@@ -474,31 +282,27 @@ const ceilTo = ceilTo$1;
474
282
 
475
283
  var point = /*#__PURE__*/Object.freeze({
476
284
  __proto__: null,
477
- rotateRad: rotateRad,
478
- rotateDeg: rotateDeg,
479
- distanceToPoint: distanceToPoint$1,
480
- distanceToLine: distanceToLine,
481
- reflectOverLine: reflectOverLine,
482
- compare: compare,
483
- is: is,
484
285
  addVector: addVector,
485
286
  addVectors: addVectors,
486
- subtractVector: subtractVector,
287
+ cartFromPolarDeg: cartFromPolarDeg,
288
+ cartFromPolarRad: cartFromPolarRad,
289
+ ceilTo: ceilTo,
290
+ compare: compare,
291
+ distanceToLine: distanceToLine,
292
+ distanceToPoint: distanceToPoint$1,
487
293
  equal: equal$2,
488
- polarRadFromCart: polarRadFromCart,
294
+ floorTo: floorTo,
295
+ is: is,
489
296
  polarDegFromCart: polarDegFromCart,
490
- cartFromPolarRad: cartFromPolarRad,
491
- cartFromPolarDeg: cartFromPolarDeg,
297
+ polarRadFromCart: polarRadFromCart,
298
+ reflectOverLine: reflectOverLine,
299
+ rotateDeg: rotateDeg,
300
+ rotateRad: rotateRad,
492
301
  round: round,
493
302
  roundTo: roundTo,
494
- floorTo: floorTo,
495
- ceilTo: ceilTo
303
+ subtractVector: subtractVector
496
304
  });
497
305
 
498
- /**
499
- * Line Utils
500
- * A line is an array of two points e.g. [[-5, 0], [5, 0]].
501
- */
502
306
  function distanceToPoint(line, point$1) {
503
307
  return distanceToLine(point$1, line);
504
308
  }
@@ -509,20 +313,14 @@ function midpoint(line) {
509
313
  return [(line[0][0] + line[1][0]) / 2, (line[0][1] + line[1][1]) / 2];
510
314
  }
511
315
  function equal$1(line1, line2, tolerance) {
512
- // TODO: A nicer implementation might just check collinearity of
513
- // vectors using underscore magick
514
- // Compare the directions of the lines
515
316
  const v1 = subtract(line1[1], line1[0]);
516
317
  const v2 = subtract(line2[1], line2[0]);
517
318
  if (!collinear$1(v1, v2, tolerance)) {
518
319
  return false;
519
320
  }
520
- // If the start point is the same for the two lines, then they are the same
521
321
  if (equal$2(line1[0], line2[0])) {
522
322
  return true;
523
323
  }
524
- // Make sure that the direction to get from line1 to
525
- // line2 is the same as the direction of the lines
526
324
  const line1ToLine2Vector = subtract(line2[0], line1[0]);
527
325
  return collinear$1(v1, line1ToLine2Vector, tolerance);
528
326
  }
@@ -530,19 +328,12 @@ function equal$1(line1, line2, tolerance) {
530
328
  var line = /*#__PURE__*/Object.freeze({
531
329
  __proto__: null,
532
330
  distanceToPoint: distanceToPoint,
533
- reflectPoint: reflectPoint,
331
+ equal: equal$1,
534
332
  midpoint: midpoint,
535
- equal: equal$1
333
+ reflectPoint: reflectPoint
536
334
  });
537
335
 
538
- /**
539
- * Ray Utils
540
- * A ray (→) is an array of an endpoint and another point along the ray.
541
- * For example, [[0, 0], [1, 0]] is the ray starting at the origin and
542
- * traveling along the positive x-axis.
543
- */
544
336
  function equal(ray1, ray2, tolerance) {
545
- // Compare the directions of the rays
546
337
  const v1 = subtract(ray1[1], ray1[0]);
547
338
  const v2 = subtract(ray2[1], ray2[0]);
548
339
  const sameOrigin = equal$2(ray1[0], ray2[0]);
@@ -555,39 +346,221 @@ var ray = /*#__PURE__*/Object.freeze({
555
346
  equal: equal
556
347
  });
557
348
 
558
- /**
559
- * A collection of geomtry-related utility functions
560
- */
561
-
562
- // This should really be a readonly tuple of [number, number]
349
+ const KhanMath = {
350
+ cleanMath: function (expr) {
351
+ return typeof expr === "string" ? expr.replace(/\+\s*-/g, "- ").replace(/-\s*-/g, "+ ").replace(/\^1/g, "") : expr;
352
+ },
353
+ bound: function (num) {
354
+ if (num === 0) {
355
+ return num;
356
+ }
357
+ if (num < 0) {
358
+ return -KhanMath.bound(-num);
359
+ }
360
+ return Math.max(1e-6, Math.min(num, 1e20));
361
+ },
362
+ factorial: function (x) {
363
+ if (x <= 1) {
364
+ return x;
365
+ }
366
+ return x * KhanMath.factorial(x - 1);
367
+ },
368
+ getGCD: function (a, b) {
369
+ if (arguments.length > 2) {
370
+ const rest = [].slice.call(arguments, 1);
371
+ return KhanMath.getGCD(a, KhanMath.getGCD(...rest));
372
+ }
373
+ let mod;
374
+ a = Math.abs(a);
375
+ b = Math.abs(b);
376
+ while (b) {
377
+ mod = a % b;
378
+ a = b;
379
+ b = mod;
380
+ }
381
+ return a;
382
+ },
383
+ getLCM: function (a, b) {
384
+ if (arguments.length > 2) {
385
+ const rest = [].slice.call(arguments, 1);
386
+ return KhanMath.getLCM(a, KhanMath.getLCM(...rest));
387
+ }
388
+ return Math.abs(a * b) / KhanMath.getGCD(a, b);
389
+ },
390
+ 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],
391
+ isPrime: function (n) {
392
+ if (n <= 1) {
393
+ return false;
394
+ }
395
+ if (n < 101) {
396
+ return !!$.grep(KhanMath.primes, function (p, i) {
397
+ return Math.abs(p - n) <= 0.5;
398
+ }).length;
399
+ }
400
+ if (n <= 1 || n > 2 && n % 2 === 0) {
401
+ return false;
402
+ }
403
+ for (let i = 3, sqrt = Math.sqrt(n); i <= sqrt; i += 2) {
404
+ if (n % i === 0) {
405
+ return false;
406
+ }
407
+ }
408
+ return true;
409
+ },
410
+ getPrimeFactorization: function (number) {
411
+ if (number === 1) {
412
+ return [];
413
+ }
414
+ if (KhanMath.isPrime(number)) {
415
+ return [number];
416
+ }
417
+ const maxf = Math.sqrt(number);
418
+ for (let f = 2; f <= maxf; f++) {
419
+ if (number % f === 0) {
420
+ return $.merge(KhanMath.getPrimeFactorization(f), KhanMath.getPrimeFactorization(number / f));
421
+ }
422
+ }
423
+ },
424
+ roundToNearest: function (increment, num) {
425
+ return Math.round(num / increment) * increment;
426
+ },
427
+ roundTo: function (precision, num) {
428
+ const factor = Math.pow(10, precision).toFixed(5);
429
+ return Math.round((num * factor).toFixed(5)) / factor;
430
+ },
431
+ toFixedApprox: function (num, precision) {
432
+ const fixedStr = num.toFixed(precision);
433
+ if (equal$4(+fixedStr, num)) {
434
+ return fixedStr;
435
+ }
436
+ return "\\approx " + fixedStr;
437
+ },
438
+ roundToApprox: function (num, precision) {
439
+ const fixed = KhanMath.roundTo(precision, num);
440
+ if (equal$4(fixed, num)) {
441
+ return String(fixed);
442
+ }
443
+ return KhanMath.toFixedApprox(num, precision);
444
+ },
445
+ toFraction: function (decimal, tolerance) {
446
+ if (tolerance == null) {
447
+ tolerance = Math.pow(2, -46);
448
+ }
449
+ if (decimal < 0 || decimal > 1) {
450
+ let fract = decimal % 1;
451
+ fract += fract < 0 ? 1 : 0;
452
+ const nd = KhanMath.toFraction(fract, tolerance);
453
+ nd[0] += Math.round(decimal - fract) * nd[1];
454
+ return nd;
455
+ }
456
+ if (Math.abs(Math.round(Number(decimal)) - decimal) <= tolerance) {
457
+ return [Math.round(decimal), 1];
458
+ }
459
+ let loN = 0;
460
+ let loD = 1;
461
+ let hiN = 1;
462
+ let hiD = 1;
463
+ let midN = 1;
464
+ let midD = 2;
465
+ while (true) {
466
+ if (Math.abs(Number(midN / midD) - decimal) <= tolerance) {
467
+ return [midN, midD];
468
+ }
469
+ if (midN / midD < decimal) {
470
+ loN = midN;
471
+ loD = midD;
472
+ } else {
473
+ hiN = midN;
474
+ hiD = midD;
475
+ }
476
+ midN = loN + hiN;
477
+ midD = loD + hiD;
478
+ }
479
+ },
480
+ getNumericFormat: function (text) {
481
+ text = $.trim(text);
482
+ text = text.replace(/\u2212/, "-").replace(/([+-])\s+/g, "$1");
483
+ if (text.match(/^[+-]?\d+$/)) {
484
+ return "integer";
485
+ }
486
+ if (text.match(/^[+-]?\d+\s+\d+\s*\/\s*\d+$/)) {
487
+ return "mixed";
488
+ }
489
+ const fraction = text.match(/^[+-]?(\d+)\s*\/\s*(\d+)$/);
490
+ if (fraction) {
491
+ return parseFloat(fraction[1]) > parseFloat(fraction[2]) ? "improper" : "proper";
492
+ }
493
+ if (text.replace(/[,. ]/g, "").match(/^\d+$/)) {
494
+ return "decimal";
495
+ }
496
+ if (text.match(/(pi?|\u03c0|t(?:au)?|\u03c4|pau)/)) {
497
+ return "pi";
498
+ }
499
+ return null;
500
+ },
501
+ toNumericString: function (number$1, format) {
502
+ if (number$1 == null) {
503
+ return "";
504
+ }
505
+ if (number$1 === 0) {
506
+ return "0";
507
+ }
508
+ if (format === "percent") {
509
+ return number$1 * 100 + "%";
510
+ }
511
+ if (format === "pi") {
512
+ const fraction = toFraction(number$1 / Math.PI);
513
+ const numerator = Math.abs(fraction[0]);
514
+ const denominator = fraction[1];
515
+ if (isInteger(numerator)) {
516
+ const sign = number$1 < 0 ? "-" : "";
517
+ const pi = "\u03C0";
518
+ return sign + (numerator === 1 ? "" : numerator) + pi + (denominator === 1 ? "" : "/" + denominator);
519
+ }
520
+ }
521
+ if (_(["proper", "improper", "mixed", "fraction"]).contains(format)) {
522
+ const fraction = toFraction(number$1);
523
+ const numerator = Math.abs(fraction[0]);
524
+ const denominator = fraction[1];
525
+ const sign = number$1 < 0 ? "-" : "";
526
+ if (denominator === 1) {
527
+ return sign + numerator;
528
+ }
529
+ if (format === "mixed") {
530
+ const modulus = numerator % denominator;
531
+ const integer = (numerator - modulus) / denominator;
532
+ return sign + (integer ? integer + " " : "") + modulus + "/" + denominator;
533
+ }
534
+ return sign + numerator + "/" + denominator;
535
+ }
536
+ return String(number$1);
537
+ }
538
+ };
539
+ function sum(array) {
540
+ return array.reduce(add, 0);
541
+ }
542
+ function add(a, b) {
543
+ return a + b;
544
+ }
563
545
 
564
- // Given a number, return whether it is positive (1), negative (-1), or zero (0)
565
546
  function sign(val) {
566
- if (perseusCore.approximateEqual(val, 0)) {
547
+ if (approximateEqual(val, 0)) {
567
548
  return 0;
568
549
  }
569
550
  return val > 0 ? 1 : -1;
570
551
  }
571
-
572
- // Determine whether three points are collinear (0), for a clockwise turn (negative),
573
- // or counterclockwise turn (positive)
574
552
  function ccw(a, b, c) {
575
553
  return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
576
554
  }
577
555
  function collinear(a, b, c) {
578
- return perseusCore.approximateEqual(ccw(a, b, c), 0);
556
+ return approximateEqual(ccw(a, b, c), 0);
579
557
  }
580
-
581
- // Given rect bounding points A and B, whether point C is inside the rect
582
558
  function pointInRect(a, b, c) {
583
559
  return c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) && c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]);
584
560
  }
585
-
586
- // Whether line segment AB intersects line segment CD
587
- // http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
588
561
  function intersects(ab, cd) {
589
562
  const triplets = [[ab[0], ab[1], cd[0]], [ab[0], ab[1], cd[1]], [cd[0], cd[1], ab[0]], [cd[0], cd[1], ab[1]]];
590
- const orientations = ___default["default"].map(triplets, function (triplet) {
563
+ const orientations = _.map(triplets, function (triplet) {
591
564
  return sign(ccw(...triplet));
592
565
  });
593
566
  if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
@@ -600,21 +573,14 @@ function intersects(ab, cd) {
600
573
  }
601
574
  return false;
602
575
  }
603
-
604
- // Whether any two sides of a polygon intersect each other
605
576
  function polygonSidesIntersect(vertices) {
606
577
  for (let i = 0; i < vertices.length; i++) {
607
578
  for (let k = i + 1; k < vertices.length; k++) {
608
- // If any two vertices are the same point, sides overlap
609
- if (kmath.point.equal(vertices[i], vertices[k])) {
579
+ if (equal$2(vertices[i], vertices[k])) {
610
580
  return true;
611
581
  }
612
-
613
- // Find the other end of the sides starting at vertices i and k
614
582
  const iNext = (i + 1) % vertices.length;
615
583
  const kNext = (k + 1) % vertices.length;
616
-
617
- // Adjacent sides always intersect (at the vertex); skip those
618
584
  if (iNext === k || kNext === i) {
619
585
  continue;
620
586
  }
@@ -628,65 +594,53 @@ function polygonSidesIntersect(vertices) {
628
594
  return false;
629
595
  }
630
596
  function vector(a, b) {
631
- return ___default["default"].map(___default["default"].zip(a, b), function (pair) {
597
+ return _.map(_.zip(a, b), function (pair) {
632
598
  return pair[0] - pair[1];
633
599
  });
634
600
  }
635
601
  function reverseVector(vector) {
636
602
  return [-vector[0], -vector[1]];
637
603
  }
638
-
639
- // Returns whether connecting the given sequence of `points` forms a clockwise
640
- // path (assuming a closed loop, where the last point connects back to the
641
- // first).
642
604
  function clockwise(points) {
643
- const segments = ___default["default"].zip(points, points.slice(1).concat(points.slice(0, 1)));
644
- const areas = ___default["default"].map(segments, function (segment) {
605
+ const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
606
+ const areas = _.map(segments, function (segment) {
645
607
  const p1 = segment[0];
646
608
  const p2 = segment[1];
647
609
  return (p2[0] - p1[0]) * (p2[1] + p1[1]);
648
610
  });
649
- return kmath.sum(areas) > 0;
611
+ return sum(areas) > 0;
650
612
  }
651
613
  function magnitude(v) {
652
- return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
653
- // @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
614
+ return Math.sqrt(_.reduce(v, function (memo, el) {
654
615
  return memo + Math.pow(el, 2);
655
616
  }, 0));
656
617
  }
657
618
  function dotProduct(a, b) {
658
- return ___default["default"].reduce(___default["default"].zip(a, b), function (memo, pair) {
619
+ return _.reduce(_.zip(a, b), function (memo, pair) {
659
620
  return memo + pair[0] * pair[1];
660
621
  }, 0);
661
622
  }
662
623
  function sideLengths(coords) {
663
- const segments = ___default["default"].zip(coords, rotate(coords));
624
+ const segments = _.zip(coords, rotate(coords));
664
625
  return segments.map(function (segment) {
665
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
666
626
  return magnitude(vector(...segment));
667
627
  });
668
628
  }
669
-
670
- // Based on http://math.stackexchange.com/a/151149
671
629
  function angleMeasures(coords) {
672
- const triplets = ___default["default"].zip(rotate(coords, -1), coords, rotate(coords, 1));
673
- const offsets = ___default["default"].map(triplets, function (triplet) {
630
+ const triplets = _.zip(rotate(coords, -1), coords, rotate(coords, 1));
631
+ const offsets = _.map(triplets, function (triplet) {
674
632
  const p = vector(triplet[1], triplet[0]);
675
633
  const q = vector(triplet[2], triplet[1]);
676
- // @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'Coord'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
677
634
  const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
678
- // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
679
635
  return sign(ccw(...triplet)) > 0 ? raw : -raw;
680
636
  });
681
- const sum = ___default["default"].reduce(offsets, function (memo, arg) {
637
+ const sum = _.reduce(offsets, function (memo, arg) {
682
638
  return memo + arg;
683
639
  }, 0);
684
- return ___default["default"].map(offsets, function (offset) {
640
+ return _.map(offsets, function (offset) {
685
641
  return sum > 0 ? Math.PI - offset : Math.PI + offset;
686
642
  });
687
643
  }
688
-
689
- // Whether two polygons are similar (or if specified, congruent)
690
644
  function similar(coords1, coords2, tolerance) {
691
645
  if (coords1.length !== coords2.length) {
692
646
  return false;
@@ -699,31 +653,23 @@ function similar(coords1, coords2, tolerance) {
699
653
  for (let i = 0; i < 2 * n; i++) {
700
654
  let angles = angles2.slice();
701
655
  let sides = sides2.slice();
702
-
703
- // Reverse angles and sides to allow matching reflected polygons
704
656
  if (i >= n) {
705
657
  angles.reverse();
706
658
  sides.reverse();
707
- // Since sides are calculated from two coordinates,
708
- // simply reversing results in an off by one error
709
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
710
659
  sides = rotate(sides, 1);
711
660
  }
712
-
713
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
714
661
  angles = rotate(angles, i);
715
- // @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
716
662
  sides = rotate(sides, i);
717
- if (perseusCore.approximateDeepEqual(angles1, angles)) {
718
- const sidePairs = ___default["default"].zip(sides1, sides);
719
- const factors = ___default["default"].map(sidePairs, function (pair) {
663
+ if (approximateDeepEqual(angles1, angles)) {
664
+ const sidePairs = _.zip(sides1, sides);
665
+ const factors = _.map(sidePairs, function (pair) {
720
666
  return pair[0] / pair[1];
721
667
  });
722
- const same = ___default["default"].all(factors, function (factor) {
723
- return perseusCore.approximateEqual(factors[0], factor);
668
+ const same = _.all(factors, function (factor) {
669
+ return approximateEqual(factors[0], factor);
724
670
  });
725
- const congruentEnough = ___default["default"].all(sidePairs, function (pair) {
726
- return kmath.number.equal(pair[0], pair[1], tolerance);
671
+ const congruentEnough = _.all(sidePairs, function (pair) {
672
+ return equal$4(pair[0], pair[1], tolerance);
727
673
  });
728
674
  if (same && congruentEnough) {
729
675
  return true;
@@ -732,32 +678,21 @@ function similar(coords1, coords2, tolerance) {
732
678
  }
733
679
  return false;
734
680
  }
735
-
736
- // Given triangle with sides ABC return angle opposite side C in degrees
737
681
  function lawOfCosines(a, b, c) {
738
682
  return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
739
683
  }
740
- function canonicalSineCoefficients(_ref) {
741
- let [amplitude, angularFrequency, phase, verticalOffset] = _ref;
742
- // For a curve of the form f(x) = a * Sin(b * x - c) + d,
743
- // this function ensures that a, b > 0, and c is its
744
- // smallest possible positive value.
745
-
746
- // Guarantee a > 0
684
+ function canonicalSineCoefficients([amplitude, angularFrequency, phase, verticalOffset]) {
747
685
  if (amplitude < 0) {
748
686
  amplitude *= -1;
749
687
  angularFrequency *= -1;
750
688
  phase *= -1;
751
689
  }
752
690
  const period = 2 * Math.PI;
753
- // Guarantee b > 0
754
691
  if (angularFrequency < 0) {
755
692
  angularFrequency *= -1;
756
693
  phase *= -1;
757
694
  phase += period / 2;
758
695
  }
759
-
760
- // Guarantee c is smallest possible positive value
761
696
  while (phase > 0) {
762
697
  phase -= period;
763
698
  }
@@ -766,26 +701,19 @@ function canonicalSineCoefficients(_ref) {
766
701
  }
767
702
  return [amplitude, angularFrequency, phase, verticalOffset];
768
703
  }
769
-
770
- // e.g. rotate([1, 2, 3]) -> [2, 3, 1]
771
704
  function rotate(array, n) {
772
705
  n = typeof n === "undefined" ? 1 : n % array.length;
773
706
  return array.slice(n).concat(array.slice(0, n));
774
707
  }
775
708
  function getLineEquation(first, second) {
776
- if (perseusCore.approximateEqual(first[0], second[0])) {
709
+ if (approximateEqual(first[0], second[0])) {
777
710
  return "x = " + first[0].toFixed(3);
778
711
  }
779
712
  const m = (second[1] - first[1]) / (second[0] - first[0]);
780
713
  const b = first[1] - m * first[0];
781
714
  return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
782
715
  }
783
-
784
- // Stolen from the wikipedia article
785
- // http://en.wikipedia.org/wiki/Line-line_intersection
786
- function getLineIntersection(
787
- // TODO(LP-10725): update these to be 2-tuples
788
- firstPoints, secondPoints) {
716
+ function getLineIntersection(firstPoints, secondPoints) {
789
717
  const x1 = firstPoints[0][0];
790
718
  const y1 = firstPoints[0][1];
791
719
  const x2 = firstPoints[1][0];
@@ -796,7 +724,6 @@ firstPoints, secondPoints) {
796
724
  const y4 = secondPoints[1][1];
797
725
  const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
798
726
  if (Math.abs(determinant) < 1e-9) {
799
- // Lines are parallel
800
727
  return null;
801
728
  }
802
729
  const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
@@ -814,43 +741,35 @@ function getLineIntersectionString(firstPoints, secondPoints) {
814
741
 
815
742
  var geometry = /*#__PURE__*/Object.freeze({
816
743
  __proto__: null,
817
- sign: sign,
744
+ angleMeasures: angleMeasures,
745
+ canonicalSineCoefficients: canonicalSineCoefficients,
818
746
  ccw: ccw,
747
+ clockwise: clockwise,
819
748
  collinear: collinear,
749
+ getLineEquation: getLineEquation,
750
+ getLineIntersection: getLineIntersection,
751
+ getLineIntersectionString: getLineIntersectionString,
820
752
  intersects: intersects,
753
+ lawOfCosines: lawOfCosines,
754
+ magnitude: magnitude,
821
755
  polygonSidesIntersect: polygonSidesIntersect,
822
- vector: vector,
823
756
  reverseVector: reverseVector,
824
- clockwise: clockwise,
825
- magnitude: magnitude,
826
- angleMeasures: angleMeasures,
827
- similar: similar,
828
- lawOfCosines: lawOfCosines,
829
- canonicalSineCoefficients: canonicalSineCoefficients,
830
757
  rotate: rotate,
831
- getLineEquation: getLineEquation,
832
- getLineIntersection: getLineIntersection,
833
- getLineIntersectionString: getLineIntersectionString
758
+ sign: sign,
759
+ similar: similar,
760
+ vector: vector
834
761
  });
835
762
 
836
- // This file contains helper functions for working with angles.
837
763
  function convertDegreesToRadians(degrees) {
838
764
  return degrees / 180 * Math.PI;
839
765
  }
840
766
  function convertRadiansToDegrees(radians) {
841
767
  const degree = radians / Math.PI * 180;
842
- // Account for floating point errors.
843
768
  return Number(degree.toPrecision(15));
844
769
  }
845
-
846
- // Returns a value between -180 and 180, inclusive. The angle is measured
847
- // between the positive x-axis and the given vector.
848
- function calculateAngleInDegrees(_ref) {
849
- let [x, y] = _ref;
770
+ function calculateAngleInDegrees([x, y]) {
850
771
  return Math.atan2(y, x) * 180 / Math.PI;
851
772
  }
852
-
853
- // Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
854
773
  function polar(r, th) {
855
774
  if (typeof r === "number") {
856
775
  r = [r, r];
@@ -858,9 +777,6 @@ function polar(r, th) {
858
777
  th = th * Math.PI / 180;
859
778
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
860
779
  }
861
- // This function calculates the angle between two points and an optional vertex.
862
- // If the vertex is not provided, the angle is measured between the two points.
863
- // This does not account for reflex angles or clockwise position.
864
780
  const getAngleFromVertex = (point, vertex) => {
865
781
  const x = point[0] - vertex[0];
866
782
  const y = point[1] - vertex[1];
@@ -869,25 +785,11 @@ const getAngleFromVertex = (point, vertex) => {
869
785
  }
870
786
  return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
871
787
  };
872
-
873
- // This function calculates the clockwise angle between three points,
874
- // and is used to generate the labels and equation strings of the
875
- // current angle for the interactive graph.
876
- const getClockwiseAngle = function (coords) {
877
- let allowReflexAngles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
788
+ const getClockwiseAngle = (coords, allowReflexAngles = false) => {
878
789
  const coordsCopy = [...coords];
879
- // The coords are saved as [point1, vertex, point2] in the interactive graph
880
790
  const areClockwise = clockwise([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
881
-
882
- // We may need to reverse the coordinates if we allow
883
- // reflex angles and the points are not in clockwise order.
884
791
  const shouldReverseCoords = !areClockwise || allowReflexAngles;
885
-
886
- // Reverse the coordinates accordingly to ensure the angle is calculated correctly
887
792
  const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
888
-
889
- // Calculate the angles between the two points and get the difference
890
- // between the two angles to get the clockwise angle.
891
793
  const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
892
794
  const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
893
795
  const angle = (startAngle + 360 - endAngle) % 360;
@@ -896,38 +798,29 @@ const getClockwiseAngle = function (coords) {
896
798
 
897
799
  var angles = /*#__PURE__*/Object.freeze({
898
800
  __proto__: null,
801
+ calculateAngleInDegrees: calculateAngleInDegrees,
899
802
  convertDegreesToRadians: convertDegreesToRadians,
900
803
  convertRadiansToDegrees: convertRadiansToDegrees,
901
- calculateAngleInDegrees: calculateAngleInDegrees,
902
- polar: polar,
903
804
  getAngleFromVertex: getAngleFromVertex,
904
- getClockwiseAngle: getClockwiseAngle
805
+ getClockwiseAngle: getClockwiseAngle,
806
+ polar: polar
905
807
  });
906
808
 
907
- // TODO: there's another, very similar getSinusoidCoefficients function
908
- // they should probably be merged
909
809
  function getSinusoidCoefficients(coords) {
910
- // It's assumed that p1 is the root and p2 is the first peak
911
810
  const p1 = coords[0];
912
811
  const p2 = coords[1];
913
-
914
- // Resulting coefficients are canonical for this sine curve
915
812
  const amplitude = p2[1] - p1[1];
916
813
  const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
917
814
  const phase = p1[0] * angularFrequency;
918
815
  const verticalOffset = p1[1];
919
816
  return [amplitude, angularFrequency, phase, verticalOffset];
920
817
  }
921
- // TODO: there's another, very similar getQuadraticCoefficients function
922
- // they should probably be merged
923
818
  function getQuadraticCoefficients(coords) {
924
819
  const p1 = coords[0];
925
820
  const p2 = coords[1];
926
821
  const p3 = coords[2];
927
822
  const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
928
823
  if (denom === 0) {
929
- // Many of the callers assume that the return value is always defined.
930
- // @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
931
824
  return;
932
825
  }
933
826
  const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
@@ -938,253 +831,9 @@ function getQuadraticCoefficients(coords) {
938
831
 
939
832
  var coefficients = /*#__PURE__*/Object.freeze({
940
833
  __proto__: null,
941
- getSinusoidCoefficients: getSinusoidCoefficients,
942
- getQuadraticCoefficients: getQuadraticCoefficients
834
+ getQuadraticCoefficients: getQuadraticCoefficients,
835
+ getSinusoidCoefficients: getSinusoidCoefficients
943
836
  });
944
837
 
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
- exports.KhanMath = KhanMath;
1180
- exports.angles = angles;
1181
- exports.coefficients = coefficients;
1182
- exports.geometry = geometry;
1183
- exports.libVersion = libVersion;
1184
- exports.line = line;
1185
- exports.number = number;
1186
- exports.point = point;
1187
- exports.ray = ray;
1188
- exports.sum = sum;
1189
- exports.vector = vector$1;
838
+ export { KhanMath, angles, coefficients, geometry, libVersion, line, number, point, ray, sum, vector$1 as vector };
1190
839
  //# sourceMappingURL=index.js.map