Passed
Push — main ( d18637...0ef6dc )
by Dylan
05:09
created

Rat.primeFactorizationString   A

Complexity

Conditions 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
rs 10
c 0
b 0
f 0
ccs 4
cts 4
cp 1
cc 3
crap 3
1 4
import {EPSILON} from './config'
2 4
import {gcd, primeFactors} from './bigint'
3 4
import {rationalApproximation, continuedFraction} from './SternBrocotTree'
4
5
/**
6
 * @class Rational Number
7
 * @name Rat
8
 */
9 4
export class Rat {
10
  n: bigint
11
  d: bigint
12
13
  /**
14
   * Initialize a rational number.
15
   */
16
  constructor(numerator: bigint|number=0n, denominator: bigint|number=1n) {
17 483
    this.n = BigInt(numerator)
18 483
    this.d = BigInt(denominator)
19 483
    this.normalize()
20
  }
21
22
  /**
23
   * The decimal approximation.
24
   */
25
  valueOf(): number {
26 4233
    return Number(this.n) / Number(this.d)
27
  }
28
29
  /**
30
   * The text representation.
31
   */
32
  toString(): string {
33 71
    return this.n.toString() + ( this.d === 1n ? '' : '/' + this.d.toString() )
34
  }
35
36
  /**
37
   * Returns a text profile of the number in various formats and it's value after common transformations.
38
   */
39
  public get profile(): string {
40 4
    const p = [`Rat: ${this.toString()} (≈${+this})`]
41 4
    p.push(`Mixed: ${this.mixedFractionString()}`)
42 4
    p.push(`Continued: ${this.continuedFractionString()}`)
43 4
    p.push(`Factorization: ${this.primeFactorizationString()}`)
44 4
    p.push(`Egyptian: ${this.egyptianFractionString()}`)
45 4
    p.push(`Babylonian: ${this.babylonianFractionString()}`)
46 4
    p.push(`psin(t): ${this.psin().toString()}`)
47 4
    p.push(`pcos(t): ${this.pcos().toString()}`)
48 4
    p.push(`ptan(t): ${this.ptan().toString()}`)
49 4
    return p.join('\n')
50
  }
51
52
  /**
53
   * Clone this.
54
   */
55
  clone(): Rat {
56 68
    return new Rat(this.n, this.d)
57
  }
58
59
  /**
60
   * Normalize the numerator and denominator by factoring out the common denominators.
61
   */
62
  normalize(): void {
63
64
    // normalize 0/1, 1/0, 0/0
65 620
    if (this.n === 0n) {
66 56
      if (this.d !== 0n) {
67 53
        this.d = 1n
68
      }
69 56
      return
70
    }
71 564
    if (this.d === 0n) {
72 17
      this.n = this.n > 0n ? 1n : -1n
73 17
      return
74
    }
75
    
76
    // normalize 1/1
77 547
    if (this.n === this.d) {
78 121
      this.n = this.d = 1n
79 121
      return
80
    }
81
82
    // remove negative denominator
83 426
    if (this.d < 0n) {
84 7
      this.n = -this.n
85 7
      this.d = -this.d
86
    }
87
    
88
    // reduce numerator and denomitator by the greatest common divisor
89 426
    const divisor = gcd(this.n, this.d)
90 426
    this.n /= divisor
91 426
    this.d /= divisor
92
93
  }
94
95
  /**
96
   * Add this to that.
97
   */
98
  add(that: Rat): Rat {
99 74
    const r = new Rat(this.n * that.d + that.n * this.d, this.d * that.d)
100 74
    r.normalize()
101 74
    return r
102
  }
103
104
  /**
105
   * Subtract this from that.
106
   */
107
  sub(that: Rat): Rat {
108 38
    return this.add(that.neg())
109
  }
110
111
  /**
112
   * Multiply that by this.
113
   */
114
  mul(that: Rat): Rat {
115 18
    const r = new Rat(this.n * that.n, this.d * that.d)
116 18
    r.normalize()
117 18
    return r
118
  }
119
120
  /**
121
   * Divide this by that.
122
   */
123
  div(that: Rat): Rat {
124 43
    const r = new Rat(this.n * that.d, this.d * that.n)
125 43
    r.normalize()
126 43
    return r
127
  }
128
129
  /**
130
   * Mediant of this and that.
131
   */
132
  mediant(that: Rat): Rat {
133 2
    const r = new Rat(this.n + that.n, this.d + that.d)
134 2
    r.normalize()
135 2
    return r
136
  }
137
138
  /**
139
   * Minimum of this and that.
140
   */
141
  min(that: Rat): Rat {
142 2
    return this.isLessThan(that) ? this : that
143
  }
144
145
  /**
146
   * Maximum of this and that.
147
   */
148
  max(that: Rat): Rat {
149 2
    return this.isGreaterThan(that) ? this : that
150
  }
151
152
  /**
153
   * Raise this to the power of that.
154
   */
155
  pow(that: Rat): Rat {
156
    // zero
157 37
    if (that.n === 0n) {
158 1
      return new Rat(1n)
159
    }
160
    // integer
161 36
    if (that.d === 1n) {
162 35
      return new Rat(this.n**that.n, this.d**that.n)
163
    }
164
    // fraction
165
    else {
166 1
      const estimate = Math.pow(+this, +that)
167 1
      return floatToRat(estimate)
168
    }
169
  }
170
171
  /**
172
   * Returns the dot product of this and that.
173
   */
174
  dot(that: Rat): bigint {
175 1
    return this.n * that.n + this.d * that.d
176
  }
177
178
  /**
179
   * Returns true if this equals that.
180
   */
181
  equals(that: Rat): boolean {
182 2
    return this.n === that.n && this.d === that.d
183
  }
184
185
  /**
186
   * Returns true if this approximates the number.
187
   */
188
  approximates(n: number): boolean {
189 2079
    return Math.abs(+this - n) < EPSILON
190
  }
191
192
  /**
193
   * Returns true if this is greater than that.
194
   */
195
  isGreaterThan(that: Rat): boolean {
196 27649785
    return this.n * that.d > that.n * this.d
197
  }
198
199
  /**
200
   * Returns true if this is less than that.
201
   */
202
  isLessThan(that: Rat): boolean {
203 3
    return this.n * that.d < that.n * this.d
204
  }
205
206
  /**
207
   * Absolute value of this.
208
   */
209
  abs(): Rat {
210 2
    const r = this.clone()
211 2
    if (r.n < 0) r.n = -r.n
212 2
    return r
213
  }
214
215
  /**
216
   * Opposite (negative) of this.
217
   */
218
  neg(): Rat {
219 43
    const r = this.clone()
220 43
    r.n = -r.n
221 43
    return r
222
  }
223
224
  /**
225
   * Returns true if this is less than zero.
226
   */
227
  isNegative(): boolean {
228 10
    return this.n < 0
229
  }
230
231
  /**
232
   * Returns true if this is a finite number.
233
   */
234
  isFinite(): boolean {
235 2
    return this.d !== 0n
236
  }
237
238
  /**
239
   * The reciprocal, or multiplicative inverse, of this.
240
   */
241
  inv(): Rat {
242 1
    return new Rat(this.d, this.n)
243
  }
244
245
  /**
246
   * Square root of this.
247
   */
248
  sqrt(): Rat {
249 2
    return this.root(2)
250
  }
251
 
252
  /**
253
   * Returns the nth root, a number which approximates this when multiplied by itself n times.
254
   */
255
  root(n: number): Rat {
256
257
    // Handle 0/1, 1/0, -1/0, 0/0, 1/1
258 5
    if (this.n === 0n || this.d === 0n || this.n === this.d) {
259 2
      return this.clone()
260
    }
261
    
262 3
    if (this.isNegative()) {
263 1
      throw `Roots of negative numbers like ${this.toString()} are too complex for this basic library`
264
    }
265
266 2
    return floatToRat(Math.pow(+this, 1/n))
267
    // return functionToRat(r => r.pow(n), +this)
268
  }
269
270
  /**
271
   * Return the closest integer approximation.
272
   */
273
  round(): bigint {
274 1
    return BigInt(Math.round(+this))
275
  }
276
277
  /**
278
   * Returns the largest integer equal to or smaller than.
279
   */
280
  floor(): bigint {
281 22
    return BigInt(Math.floor(+this))
282
  }
283
284
  /**
285
   * Returns the smallest integer equal to or greater than.
286
   */
287
  ceil(): bigint {
288 2
    return BigInt(Math.ceil(+this))
289
  }
290
291
  /**
292
   * Parametric sine: 2t / (1 + t²)
293
   * @see https://youtu.be/Ui8OvmzDn7o?t=245
294
   */
295
  psin(): Rat {
296 19
    if (this.d === 0n) return new Rat(0n)
297 17
    const one = new Rat(1)
298 17
    const two = new Rat(2)
299 17
    const n = two.mul(this)
300 17
    const d = one.add(this.pow(two))
301 17
    return n.div(d)
302
  }
303
304
  /**
305
   * Parametric cosine: (1 - t²) / (1 + t²)
306
   */
307
  pcos(): Rat {
308 19
    if (this.d === 0n) return new Rat(-1n)
309 17
    const one = new Rat(1)
310 17
    const two = new Rat(2)
311 17
    const t2 = this.pow(two)
312 17
    const n = one.sub(t2)
313 17
    const d = one.add(t2)
314 17
    return n.div(d)
315
  }
316
317
  /**
318
   * Parametric tangent: psin() / pcos()
319
   */
320
  ptan(): Rat {
321 8
    return this.psin().div(this.pcos())
322
  }
323
324
  /**
325
   * Mixed fraction as a string.
326
   */
327
  mixedFractionString(): string {
328 5
    const integerPart = this.isNegative() ? this.ceil() : this.floor()
329 5
    const fractionPart = this.sub(new Rat(integerPart)).toString()
330 5
    return integerPart ? `${integerPart} + ${fractionPart}` : fractionPart
331
  }
332
333
  /**
334
   * Returns the integers representing the continued fraction.
335
   */
336
  *continuedFraction(): Generator<number> {
337 11
    if (this.n === 0n || this.d === 0n) {
338 2
      yield +this
339
    }
340
    else {
341 9
      for (const n of continuedFraction(+this)) {
342 22
        yield n
343
      }
344
    }
345
  }
346
347
  /**
348
   * Continued fraction as a string.
349
   */
350
  continuedFractionString(): string {
351 10
    const a: string[] = []
352 10
    for (const r of this.continuedFraction()) {
353 21
      a.push(r.toString())
354
    }
355 10
    const n = a.shift()
356 10
    if (n !== undefined && this.d !== 0n) {
357 9
      let s = n.toString()
358 9
      if (a.length) {
359 6
        s += '; ' + a.join(', ')
360
      }
361 9
      return `[${s}]`
362
    }
363 1
    return '[]'
364
  }
365
366
  /**
367
   * Returns an array of the prime factors with their exponents.
368
   */
369
  primeFactorization(): Array<[bigint, bigint]> {
370 8
    const f: Array<[bigint, bigint]> = []
371 8
    if (this.n !== 1n) {
372 7
      f.push(...primeFactors(this.n))
373
    }
374 8
    if (this.d !== 1n) {
375 11
      f.push(...primeFactors(this.d).map(f => {f[1]=-f[1]; return f}))
376
    }
377 8
    return f.sort((a, b) => {
378 30
      return Number(a[0] - b[0])
379
    })
380
  }
381
382
  /**
383
   * Prime factorization as a calc string.
384
   */
385
  primeFactorizationString(): string {
386 8
    const a: string[] = []
387 8
    for (const p of this.primeFactorization()) {
388 28
      a.push(p[1]===1n ? p[0].toString() : `${p[0]}^${p[1]}`)
389
    }
390 8
    return a.join(' * ')
391
  }
392
393
  /**
394
   * A list of unit fractions which add up to this number.
395
   */
396
  egyptianFraction(): Array<Rat> {
397 7
    const r: Rat[] = []
398 7
    const f = new Rat(1n)
399 7
    let t = this.clone()
400
401
    // start with the integer part if non-zero
402 7
    const integerPart = this.floor()
403 7
    if (integerPart) {
404 4
      const integerRat = new Rat(integerPart)
405 4
      r.push(integerRat)
406 4
      t = t.sub(integerRat)
407
    }
408
409
    // increment the denominator of f, substracting it from t when bigger, until t has a numerator of 1
410 7
    while (t.n !== 1n) {
411 27649782
      f.d++
412 27649782
      if (t.isGreaterThan(f)) {
413 11
        r.push(f.clone())
414 11
        t = t.sub(f)
415
      }
416
    }
417
418
    // include the final t
419 7
    r.push(t)
420
421 7
    return r
422
  }
423
424
  /**
425
   * Egyptian fraction as a calc string.
426
   */
427
  egyptianFractionString(): string {
428 7
    return this.egyptianFraction().join(' + ')
429
  }
430
431
  /**
432
   * A dictionary with the exponents of 60 and their coefficents, which add up to this number.
433
   */
434
  babylonianFraction(): Array<string> {
435 9
    const a: string[] = []
436 9
    let n = Number(this.floor())
437 9
    let r = Math.abs(+this - n)
438 9
    let d = 0
439
    // consume increasing powers until the integer part is divided
440 9
    for (let p=0; n > 0; p++) {
441 9
      d = n % 60
442 9
      if (d !== 0) {
443 8
        a.unshift(`${d} * 60^${p}`)
444
      }
445 9
      n = (n - d) / 60
446
    }
447
    // consume decreasing powers until the remainder is accumulated
448
    // @todo use a more precise calculation to get rid of this abhorrent epsilon
449 9
    for (let p=-1; r > 1e-10; p--) {
450 79
      r *= 60
451 79
      d = Math.floor(r)
452 79
      r -= d
453 79
      if (d !== 0) {
454 78
        a.push(`${d} * 60^${p}`)
455
      }
456 79
      n = (n - d) / 60
457
    }
458 9
    return a
459
  }
460
461
  /**
462
   * Babylonian fraction as a calc string.
463
   */
464
  babylonianFractionString(): string {
465 9
    const a: string[] = []
466 9
    const f = this.babylonianFraction()
467 9
    for (const i of f) {
468 86
      a.push(`${i}`)
469
    }
470 9
    return a.join(' + ')
471
  }
472
473
}
474
475
/**
476
 * Find a Rat approximation of the floating point number.
477
 */
478 4
export const floatToRat = (n: number): Rat => {
479
480
  // Handle special values: 0/0, 1/0, -1/0
481 20
  if (isNaN(n)) return new Rat(0, 0)
482 19
  if (n===Infinity) return new Rat(1, 0)
483 14
  if (n===-Infinity) return new Rat(-1, 0)
484
485
  // Shortcut for numbers close to an integer or 1/integer
486 13
  if (Math.abs(n%1) < EPSILON) return new Rat(Math.round(n))
487 9
  if (Math.abs(1/n%1) < EPSILON) return new Rat(1, Math.round(1/n))
488
489
  // Traverse the Stern–Brocot tree until a good approximation is found
490
  // If negative, search for the positive value and negate the result
491 7
  const negative = n < 1
492 7
  const r = rationalApproximation(Math.abs(n))
493 7
  return negative ? r.neg() : r
494
}
495
496
/**
497
 * Parse the string for a numeric value and return it as a Rat.
498
 */
499 4
export const stringToRat = (s: string): Rat => {
500
501
  // Handle special values: 0/0, 1/0, -1/0
502 6
  if (s==='NaN') return new Rat(0, 0)
503 5
  if (s==='Infinity') return new Rat(1, 0)
504 4
  if (s==='-Infinity') return new Rat(-1, 0)
505
506 3
  const [n, d] = s.split('/', 2)
507 3
  if (d === undefined) {
508 2
    return floatToRat(Number(n))
509
  }
510 1
  return new Rat(BigInt(n), BigInt(d))
511
512
}
513
514
/**
515
 * Pi, an approximation of the ratio between a circle's circumference and it's diameter.
516
 */
517
// export const π = new Rat(3141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587n, 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n)
518
519
export default Rat
520