@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 +293 -644
- package/dist/index.js.map +1 -1
- package/package.json +14 -6
- package/dist/es/index.js +0 -1164
- package/dist/es/index.js.map +0 -1
- package/dist/shared-utils/add-library-version-to-perseus-debug.d.ts +0 -9
package/dist/index.js
CHANGED
|
@@ -1,81 +1,21 @@
|
|
|
1
|
-
|
|
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.
|
|
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
|
|
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)
|
|
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
|
-
|
|
67
|
+
ceilTo: ceilTo$2,
|
|
155
68
|
equal: equal$4,
|
|
156
|
-
|
|
69
|
+
floorTo: floorTo$2,
|
|
70
|
+
is: is$2,
|
|
157
71
|
isInteger: isInteger,
|
|
158
72
|
round: round$2,
|
|
159
73
|
roundTo: roundTo$2,
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
294
|
+
floorTo: floorTo,
|
|
295
|
+
is: is,
|
|
489
296
|
polarDegFromCart: polarDegFromCart,
|
|
490
|
-
|
|
491
|
-
|
|
297
|
+
polarRadFromCart: polarRadFromCart,
|
|
298
|
+
reflectOverLine: reflectOverLine,
|
|
299
|
+
rotateDeg: rotateDeg,
|
|
300
|
+
rotateRad: rotateRad,
|
|
492
301
|
round: round,
|
|
493
302
|
roundTo: roundTo,
|
|
494
|
-
|
|
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
|
-
|
|
331
|
+
equal: equal$1,
|
|
534
332
|
midpoint: midpoint,
|
|
535
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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 (
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
644
|
-
const areas =
|
|
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
|
|
611
|
+
return sum(areas) > 0;
|
|
650
612
|
}
|
|
651
613
|
function magnitude(v) {
|
|
652
|
-
return Math.sqrt(
|
|
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
|
|
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 =
|
|
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 =
|
|
673
|
-
const offsets =
|
|
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 =
|
|
637
|
+
const sum = _.reduce(offsets, function (memo, arg) {
|
|
682
638
|
return memo + arg;
|
|
683
639
|
}, 0);
|
|
684
|
-
return
|
|
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 (
|
|
718
|
-
const sidePairs =
|
|
719
|
-
const factors =
|
|
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 =
|
|
723
|
-
return
|
|
668
|
+
const same = _.all(factors, function (factor) {
|
|
669
|
+
return approximateEqual(factors[0], factor);
|
|
724
670
|
});
|
|
725
|
-
const congruentEnough =
|
|
726
|
-
return
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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
|
-
|
|
942
|
-
|
|
834
|
+
getQuadraticCoefficients: getQuadraticCoefficients,
|
|
835
|
+
getSinusoidCoefficients: getSinusoidCoefficients
|
|
943
836
|
});
|
|
944
837
|
|
|
945
|
-
|
|
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
|