Passed
Push — main ( 493588...559133 )
by Dylan
04:54
created

Rat.egyptianFraction   B

Complexity

Conditions 5

Size

Total Lines 34
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

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