pimath 0.2.13 → 0.2.15
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/pimath.js +856 -1882
- package/dist/pimath.js.map +1 -1
- package/package.json +2 -2
- package/src/algebra/equation.ts +562 -558
- package/src/algebra/equationSolver.ts +489 -489
- package/src/algebra/factor.ts +396 -392
- package/src/algebra/linearSystem.ts +446 -442
- package/src/algebra/matrix.ts +515 -511
- package/src/algebra/monom.ts +932 -928
- package/src/algebra/polyFactor.ts +724 -720
- package/src/algebra/polynom.ts +1233 -1229
- package/src/coefficients/fraction.ts +907 -748
- package/src/coefficients/root.ts +500 -359
- package/src/geometry/TupleN.ts +129 -129
- package/src/geometry/circle.ts +455 -455
- package/src/geometry/line.ts +658 -654
- package/src/geometry/point.ts +105 -105
- package/src/geometry/sphere3.ts +2 -2
- package/src/geometry/triangle.ts +488 -488
- package/src/geometry/vector.ts +230 -226
- package/src/numeric.ts +136 -51
- package/src/pimath.interface.ts +2 -0
- package/types/algebra/equation.d.ts +1 -0
- package/types/algebra/equation.d.ts.map +1 -1
- package/types/algebra/equationSolver.d.ts.map +1 -1
- package/types/algebra/factor.d.ts +1 -0
- package/types/algebra/factor.d.ts.map +1 -1
- package/types/algebra/linearSystem.d.ts +1 -0
- package/types/algebra/linearSystem.d.ts.map +1 -1
- package/types/algebra/matrix.d.ts +6 -5
- package/types/algebra/matrix.d.ts.map +1 -1
- package/types/algebra/monom.d.ts +1 -0
- package/types/algebra/monom.d.ts.map +1 -1
- package/types/algebra/polyFactor.d.ts +1 -0
- package/types/algebra/polyFactor.d.ts.map +1 -1
- package/types/algebra/polynom.d.ts +1 -0
- package/types/algebra/polynom.d.ts.map +1 -1
- package/types/coefficients/fraction.d.ts +62 -53
- package/types/coefficients/fraction.d.ts.map +1 -1
- package/types/coefficients/root.d.ts +3 -0
- package/types/coefficients/root.d.ts.map +1 -1
- package/types/geometry/TupleN.d.ts +1 -1
- package/types/geometry/TupleN.d.ts.map +1 -1
- package/types/geometry/circle.d.ts.map +1 -1
- package/types/geometry/line.d.ts +1 -0
- package/types/geometry/line.d.ts.map +1 -1
- package/types/geometry/point.d.ts.map +1 -1
- package/types/geometry/sphere3.d.ts +2 -2
- package/types/geometry/sphere3.d.ts.map +1 -1
- package/types/geometry/triangle.d.ts.map +1 -1
- package/types/geometry/vector.d.ts +1 -0
- package/types/geometry/vector.d.ts.map +1 -1
- package/types/index.d.ts +3 -3
- package/types/numeric.d.ts +4 -4
- package/types/numeric.d.ts.map +1 -1
- package/types/pimath.interface.d.ts +1 -0
- package/types/pimath.interface.d.ts.map +1 -1
- package/types/primes.data.d.ts +2 -0
- package/types/primes.data.d.ts.map +1 -0
package/src/coefficients/root.ts
CHANGED
|
@@ -1,360 +1,501 @@
|
|
|
1
|
-
import {Fraction} from "./fraction"
|
|
2
|
-
import type {IExpression, InputValue, IPiMathObject} from "../pimath.interface"
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this.#
|
|
15
|
-
this.#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.index =
|
|
60
|
-
this.
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
return this
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return this
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return this
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return this
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return this
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
this.
|
|
303
|
-
this
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
this.
|
|
342
|
-
this.radical
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
this.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return this
|
|
359
|
-
}
|
|
1
|
+
import {Fraction} from "./fraction"
|
|
2
|
+
import type {IExpression, InputValue, IPiMathObject} from "../pimath.interface"
|
|
3
|
+
import {Numeric} from "../numeric"
|
|
4
|
+
import {InvalidArgumentError, MathError, ParseError} from "../errors"
|
|
5
|
+
|
|
6
|
+
export class Root implements IPiMathObject<Root>, IExpression<Root> {
|
|
7
|
+
#factor: Fraction
|
|
8
|
+
#index: number
|
|
9
|
+
#radical: Fraction
|
|
10
|
+
#withSign = false
|
|
11
|
+
|
|
12
|
+
constructor(value?: InputValue<Root | Fraction>) {
|
|
13
|
+
this.#index = 2
|
|
14
|
+
this.#factor = new Fraction().zero()
|
|
15
|
+
this.#radical = new Fraction().zero()
|
|
16
|
+
|
|
17
|
+
// Test `!== undefined`, not truthiness: 0 and "" are legitimate inputs,
|
|
18
|
+
// and a NaN must be rejected by parse (boundary strictness), not silently
|
|
19
|
+
// swallowed into a zero root.
|
|
20
|
+
if (value !== undefined) {
|
|
21
|
+
this.parse(value)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
return this
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Router: inspect the input type and delegate to the matching builder.
|
|
29
|
+
// A same-type (Root) input goes through copy(); every other form has its
|
|
30
|
+
// own from*** / #fromScalar. Construction logic is never inlined here.
|
|
31
|
+
parse(value: InputValue<Root | Fraction>): this {
|
|
32
|
+
// should be able to parse "display" roots.
|
|
33
|
+
// sqrt12 or sqrt(12) or sqrt(12/5) => [2, 12 or 12/5, 1]
|
|
34
|
+
// root(n)10 or root(n)(10) or root(n)(10/3) => [n, 10 or 10/3, 1]
|
|
35
|
+
// 7sqrt12 or 7sqrt(12) or 7sqrt(12/5) => [2, 12 or 12/5, 7]
|
|
36
|
+
// 7root(n)10 or 7root(n)(10) or 7root(n)(10/3) => [n, 10 or 10/3, 7]
|
|
37
|
+
|
|
38
|
+
if (value instanceof Root) {
|
|
39
|
+
return this.copy(value)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (value instanceof Fraction) {
|
|
43
|
+
return this.fromFraction(value)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof value === "string") {
|
|
47
|
+
return this.fromString(value)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Remaining scalar (number): build the factor at Root's boundary.
|
|
51
|
+
return this.#fromScalar(value)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Copy the mathematical state of another Root (index + radical + factor).
|
|
55
|
+
// Like Fraction.copy, it deliberately does NOT transfer the display state
|
|
56
|
+
// (#withSign): parse() routes a Root input here, so the receiver keeps its
|
|
57
|
+
// own formatting. Use clone() for a full duplicate.
|
|
58
|
+
copy(value: Root): this {
|
|
59
|
+
this.index = value.index
|
|
60
|
+
this.radical = value.radical.clone()
|
|
61
|
+
this.factor = value.factor.clone()
|
|
62
|
+
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fromFraction(value: Fraction): this {
|
|
67
|
+
this.index = 2
|
|
68
|
+
this.factor = value.clone()
|
|
69
|
+
this.radical.one()
|
|
70
|
+
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fromString(value: string): this {
|
|
75
|
+
if (value.includes('sqrt')) {
|
|
76
|
+
return this.#parse_sqrt(value)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (value.includes('root')) {
|
|
80
|
+
return this.#parse_root(value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Plain fraction string: no radical, delegate to the scalar builder.
|
|
84
|
+
return this.#fromScalar(value)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Shared scalar builder (number or plain fraction string) → factor·√[2]{1}.
|
|
88
|
+
// Strictness is enforced at Root's boundary: Fraction keeps a permissive
|
|
89
|
+
// NaN state (its invalid()), but a Root refuses to build on an invalid
|
|
90
|
+
// factor, so a malformed input throws instead of yielding a NaN root.
|
|
91
|
+
#fromScalar(value: string | number): this {
|
|
92
|
+
const factor = new Fraction(value)
|
|
93
|
+
if (factor.isNaN()) {
|
|
94
|
+
throw new ParseError(`Cannot parse "${value}" as a root or fraction`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.index = 2
|
|
98
|
+
this.factor = factor
|
|
99
|
+
this.radical.one()
|
|
100
|
+
|
|
101
|
+
return this
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
clone(): Root {
|
|
105
|
+
const R = new Root().from(this.index, this.radical, this.factor)
|
|
106
|
+
R.#withSign = this.#withSign
|
|
107
|
+
return R
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get tex(): string {
|
|
111
|
+
// b \sqrt{}
|
|
112
|
+
// b
|
|
113
|
+
// \sqrt{}
|
|
114
|
+
// -\sqrt{}
|
|
115
|
+
|
|
116
|
+
// Force the plus sign.
|
|
117
|
+
const plus = this.#withSign && this.factor.isPositive() ? '+' : ''
|
|
118
|
+
const space = (plus && this.#factor.numerator===1) ? ' ' : ''
|
|
119
|
+
|
|
120
|
+
if (this.isZero()) return `${plus}0`
|
|
121
|
+
|
|
122
|
+
const den = this.#makeTeXLine(this.#factor.denominator, this.#radical.denominator)
|
|
123
|
+
const num = this.#makeTeXLine(this.#factor.numerator, this.#radical.numerator) ?? '1'
|
|
124
|
+
|
|
125
|
+
if (den === null) return `${plus}${space}${num}`
|
|
126
|
+
|
|
127
|
+
return `${plus}${space}\\frac{ ${num} }{ ${den} }`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get display(): string {
|
|
131
|
+
// force the '+' sign if asked
|
|
132
|
+
const plus = this.#withSign && this.factor.isPositive() ? '+' : ''
|
|
133
|
+
|
|
134
|
+
if (this.isZero()) return `${plus}0`
|
|
135
|
+
|
|
136
|
+
const den = this.#makeDisplayLine(this.#factor.denominator, this.#radical.denominator, true)
|
|
137
|
+
const num = this.#makeDisplayLine(this.#factor.numerator, this.#radical.numerator, den !== null) ?? '1'
|
|
138
|
+
|
|
139
|
+
if (den === null) return `${plus}${num}`
|
|
140
|
+
|
|
141
|
+
return `${plus}${num}/${den}`
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
add(value: InputValue<Root>): this {
|
|
145
|
+
return this.#addSub(value, false)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
divide(value: InputValue<Root>): this {
|
|
149
|
+
return this.multiply(new Root(value).inverse())
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get factor(): Fraction {
|
|
153
|
+
return this.#factor
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
set factor(value: Fraction) {
|
|
157
|
+
this.#factor = value
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
from(index: number, radical: InputValue<Fraction>, factor?: InputValue<Fraction>): this {
|
|
161
|
+
// set the index (validates: strictly positive integer)
|
|
162
|
+
this.index = index
|
|
163
|
+
|
|
164
|
+
// set the radical; reject an invalid (NaN) primitive at Root's boundary (A6).
|
|
165
|
+
const rad = new Fraction(radical)
|
|
166
|
+
if (rad.isNaN()) {
|
|
167
|
+
throw new ParseError(`Cannot build a root from an invalid radical: "${radical}"`)
|
|
168
|
+
}
|
|
169
|
+
this.radical = rad
|
|
170
|
+
|
|
171
|
+
// set the factor if provided; 0 is a valid factor, only `undefined` means omitted.
|
|
172
|
+
if (factor === undefined) {
|
|
173
|
+
this.factor = new Fraction().one()
|
|
174
|
+
} else {
|
|
175
|
+
const f = new Fraction(factor)
|
|
176
|
+
if (f.isNaN()) {
|
|
177
|
+
throw new ParseError(`Cannot build a root from an invalid factor: "${factor}"`)
|
|
178
|
+
}
|
|
179
|
+
this.factor = f
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return this
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* convert to root(index)(radical), without factor
|
|
187
|
+
*/
|
|
188
|
+
group(): this {
|
|
189
|
+
this.radical.multiply(this.factor.pow(this.index))
|
|
190
|
+
this.factor.one()
|
|
191
|
+
|
|
192
|
+
return this
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
get index(): number {
|
|
196
|
+
return this.#index
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
set index(value: number) {
|
|
200
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
201
|
+
throw new InvalidArgumentError("Index must be a strictly positive integer.")
|
|
202
|
+
}
|
|
203
|
+
this.#index = value
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
get indexAsPow(): Fraction {
|
|
207
|
+
return new Fraction(this.index).inverse()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
inverse(): this {
|
|
211
|
+
this.factor.inverse()
|
|
212
|
+
this.radical.inverse()
|
|
213
|
+
return this
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
isEqual(root: Root): boolean {
|
|
217
|
+
if (this.isZero() && root.isZero()) {
|
|
218
|
+
return true
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Even roots of a negative radical are not real; they compare to nothing.
|
|
222
|
+
if (!this.#isReal() || !root.#isReal()) {
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (this.#valueSign() !== root.#valueSign()) {
|
|
227
|
+
return false
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Compare |value| exactly by raising to a common multiple L of both
|
|
231
|
+
// indices: (|f| * |radical|^(1/i))^L = |f|^L * |radical|^(L/i), an exact
|
|
232
|
+
// Fraction (no floating point). The radical sign is handled above.
|
|
233
|
+
const L = Numeric.lcm(this.index, root.index)
|
|
234
|
+
|
|
235
|
+
const a = this.factor.clone().abs().pow(L)
|
|
236
|
+
.multiply(this.radical.clone().abs().pow(L / this.index))
|
|
237
|
+
const b = root.factor.clone().abs().pow(L)
|
|
238
|
+
.multiply(root.radical.clone().abs().pow(L / root.index))
|
|
239
|
+
|
|
240
|
+
return a.isEqual(b)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#isReal(): boolean {
|
|
244
|
+
return !(this.radical.isNegative() && this.index % 2 === 0)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#valueSign(): 1 | -1 {
|
|
248
|
+
const radicalSign = this.index % 2 === 1 ? this.radical.sign() : 1
|
|
249
|
+
return (this.factor.sign() * radicalSign) as 1 | -1
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
isOne(): boolean {
|
|
253
|
+
return this.factor.isOne() && this.radical.isOne()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
isRational(): boolean {
|
|
257
|
+
const reduced = this.clone().reduce()
|
|
258
|
+
|
|
259
|
+
if (reduced.radical.isOne()) return reduced.factor.exact
|
|
260
|
+
|
|
261
|
+
if (reduced.index === 1) return reduced.factor.exact && reduced.radical.exact
|
|
262
|
+
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
isZero(): boolean {
|
|
267
|
+
return this.factor.isZero() || this.radical.isZero()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
multiply(value: InputValue<Root>): this {
|
|
271
|
+
// Compute on a clone and commit only on success, so a failed multiply
|
|
272
|
+
// leaves the receiver untouched (same atomic discipline as add).
|
|
273
|
+
const a = this.clone()
|
|
274
|
+
const rt = new Root(value)
|
|
275
|
+
|
|
276
|
+
if (a.index !== rt.index && !a.radical.isEqual(rt.radical)) {
|
|
277
|
+
throw new MathError('Multiply can only be done if radical or index as equals.')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
a.factor.multiply(rt.factor)
|
|
281
|
+
|
|
282
|
+
if (a.index === rt.index) {
|
|
283
|
+
a.radical.multiply(rt.radical)
|
|
284
|
+
} else {
|
|
285
|
+
// same radical, different index: combine the fractional exponents.
|
|
286
|
+
const F = a.indexAsPow.add(rt.indexAsPow).reduce()
|
|
287
|
+
a.index = F.denominator
|
|
288
|
+
a.radical = a.radical.pow(F.numerator)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return this.parse(a)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
one(): this {
|
|
295
|
+
this.radical.one()
|
|
296
|
+
this.factor.one()
|
|
297
|
+
|
|
298
|
+
return this
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
opposite(): this {
|
|
302
|
+
this.factor.opposite()
|
|
303
|
+
return this
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
pow(value: number): this {
|
|
307
|
+
this.factor.pow(value)
|
|
308
|
+
|
|
309
|
+
const g = Numeric.gcd(this.index, value)
|
|
310
|
+
this.index = this.index / g
|
|
311
|
+
this.radical.pow(value / g)
|
|
312
|
+
|
|
313
|
+
return this
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
get radical(): Fraction {
|
|
317
|
+
return this.#radical
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
set radical(value: Fraction) {
|
|
321
|
+
this.#radical = value
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
reduce(): this {
|
|
325
|
+
// 1. extract roots
|
|
326
|
+
// 2. avoid roots at denominator.
|
|
327
|
+
|
|
328
|
+
// Remove the root from the denominator:
|
|
329
|
+
// √[n]{num/den} = √[n]{num · den^(n-1)} / den
|
|
330
|
+
if (this.radical.hasFractionalPart()) {
|
|
331
|
+
const num = this.radical.numerator
|
|
332
|
+
const den = this.radical.denominator
|
|
333
|
+
|
|
334
|
+
this.radical = new Fraction(num * den ** (this.index - 1))
|
|
335
|
+
this.factor.divide(den)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Extract the greatest root from.
|
|
339
|
+
const greatest = Numeric.greatestPower(this.radical.value, this.index)
|
|
340
|
+
|
|
341
|
+
this.factor.multiply(Numeric.nthRoot(greatest, this.index))
|
|
342
|
+
this.radical.divide(greatest)
|
|
343
|
+
|
|
344
|
+
return this
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
root(value: number): this {
|
|
348
|
+
this.group()
|
|
349
|
+
this.index = this.index * value
|
|
350
|
+
return this
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
sqrt(): this {
|
|
354
|
+
return this.root(2)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
subtract(value: InputValue<Root>): this {
|
|
358
|
+
return this.#addSub(value, true)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Shared, atomic add/subtract: reduce both operands to a common radical,
|
|
362
|
+
// then combine the factors. subtract no longer routes through
|
|
363
|
+
// add(opposite()), so the error message names both operations instead of
|
|
364
|
+
// leaking "Add". A failed op leaves the receiver untouched (clones + parse).
|
|
365
|
+
#addSub(value: InputValue<Root>, isSubtract: boolean): this {
|
|
366
|
+
const a = this.clone().reduce()
|
|
367
|
+
const b = new Root(value).reduce()
|
|
368
|
+
|
|
369
|
+
if (a.index !== b.index || !a.radical.isEqual(b.radical)) {
|
|
370
|
+
throw new MathError("Roots must share the same index and radical to be added or subtracted")
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (isSubtract) {
|
|
374
|
+
a.factor.subtract(b.factor)
|
|
375
|
+
} else {
|
|
376
|
+
a.factor.add(b.factor)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return this.parse(a)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
get value(): number {
|
|
383
|
+
const radical = this.radical.value
|
|
384
|
+
|
|
385
|
+
if (radical < 0) {
|
|
386
|
+
// Even root of a negative is not real.
|
|
387
|
+
if (this.index % 2 === 0) {
|
|
388
|
+
return NaN
|
|
389
|
+
}
|
|
390
|
+
// Odd root of a negative is real and negative.
|
|
391
|
+
return Numeric.numberCorrection(this.factor.value * -Math.pow(-radical, 1 / this.index))
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return Numeric.numberCorrection(this.factor.value * Math.pow(radical, 1 / this.index))
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
get withSign(): this {
|
|
398
|
+
this.#withSign = true
|
|
399
|
+
return this
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
get withoutSign(): this {
|
|
403
|
+
this.#withSign = false
|
|
404
|
+
return this
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
zero(): this {
|
|
408
|
+
this.radical.zero()
|
|
409
|
+
this.factor.zero()
|
|
410
|
+
return this
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
#makeDisplayLine(a: number, b: number, wrap: boolean): string | null {
|
|
414
|
+
const rad = this.#index === 2 ? `sqrt` : `root(${this.#index})`
|
|
415
|
+
return this.#makeLine(a, b, {radical: v => `${rad}(${v})`, separator: '', wrap})
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
#makeTeXLine(a: number, b: number): string | null {
|
|
419
|
+
const rad = this.#index === 2 ? `\\sqrt` : `\\sqrt[ ${this.#index} ]`
|
|
420
|
+
return this.#makeLine(a, b, {radical: v => `${rad}{ ${v} }`, separator: ' '})
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Shared skeleton for both display and TeX lines: applies the factor rule,
|
|
424
|
+
// drops empty parts, optionally wraps a two-part result and joins. The
|
|
425
|
+
// caller supplies the radical formatting and separator, so this core stays
|
|
426
|
+
// agnostic of the output format.
|
|
427
|
+
#makeLine(a: number, b: number, options: {
|
|
428
|
+
radical: (radicand: number) => string
|
|
429
|
+
separator: string
|
|
430
|
+
wrap?: boolean
|
|
431
|
+
}): string | null {
|
|
432
|
+
const factorPart = (a === 1 || a === 0) ? null : a === -1 ? '-' : a.toString()
|
|
433
|
+
const radicalPart = (b !== 1 && b !== 0) ? options.radical(b) : null
|
|
434
|
+
|
|
435
|
+
const formatted: string[] = [factorPart, radicalPart].filter(x => x !== null)
|
|
436
|
+
|
|
437
|
+
if (formatted.length === 0) return null
|
|
438
|
+
|
|
439
|
+
if (options.wrap && formatted.length === 2) return `(${formatted.join(options.separator)})`
|
|
440
|
+
|
|
441
|
+
return formatted.join(options.separator)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
#parse_root(value: string): this {
|
|
445
|
+
// Format: [factor]root([index])[radical] or [factor]root([index])([radical])
|
|
446
|
+
const match = /^(.*?)root\((\d+)\)\(?([^)]+)/.exec(value)
|
|
447
|
+
if (!match) throw new ParseError(`Invalid root format: "${value}"`)
|
|
448
|
+
|
|
449
|
+
const [, factor, index, radical] = match
|
|
450
|
+
this.index = +index
|
|
451
|
+
this.radical = new Fraction(radical)
|
|
452
|
+
this.factor = factor === '' ? new Fraction().one() : new Fraction(factor.trim())
|
|
453
|
+
|
|
454
|
+
return this
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
#parse_sqrt(value: string): this {
|
|
458
|
+
// sqrt binds the immediately following integer; a fractional radical must
|
|
459
|
+
// be parenthesised. Leading N or N/D and a trailing /D are ordinary factor
|
|
460
|
+
// arithmetic (operator precedence): 12sqrt2/7 == 12/7sqrt(2) == (12/7)√2.
|
|
461
|
+
|
|
462
|
+
// Round-trip of display's fractional output: (Nsqrt(R))/D
|
|
463
|
+
const wrapped = /^\((.+)\)\/(\d+)$/.exec(value)
|
|
464
|
+
if (wrapped) {
|
|
465
|
+
this.#parse_sqrt(wrapped[1])
|
|
466
|
+
this.factor.divide(+wrapped[2])
|
|
467
|
+
return this
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const re = /^([+-]?\d*(?:\/\d+)?)sqrt(?:\((-?\d+(?:\/\d+)?)\)|(\d+))(?:\/(\d+))?$/
|
|
471
|
+
const match = re.exec(value)
|
|
472
|
+
if (!match) throw new ParseError(`Invalid sqrt format: "${value}"`)
|
|
473
|
+
|
|
474
|
+
// Alternation / optional groups are undefined at runtime; `.at()` is typed
|
|
475
|
+
// `string | undefined`, unlike indexed access which TS types as `string`.
|
|
476
|
+
const rawFactor = match[1]
|
|
477
|
+
const radicalParen = match.at(2)
|
|
478
|
+
const radicalBare = match.at(3)
|
|
479
|
+
const denominator = match.at(4)
|
|
480
|
+
|
|
481
|
+
this.index = 2
|
|
482
|
+
this.radical = new Fraction(radicalParen ?? radicalBare ?? '')
|
|
483
|
+
|
|
484
|
+
let factor: Fraction
|
|
485
|
+
if (rawFactor === '' || rawFactor === '+' || rawFactor === '-') {
|
|
486
|
+
factor = new Fraction(rawFactor === '-' ? -1 : 1)
|
|
487
|
+
} else if (/^[+-]?\d+(?:\/\d+)?$/.test(rawFactor)) {
|
|
488
|
+
factor = new Fraction(rawFactor)
|
|
489
|
+
} else {
|
|
490
|
+
throw new ParseError(`Invalid sqrt format: "${value}"`)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (denominator !== undefined) {
|
|
494
|
+
factor.divide(+denominator)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
this.factor = factor
|
|
498
|
+
|
|
499
|
+
return this
|
|
500
|
+
}
|
|
360
501
|
}
|