From 360bbc656a937d1093bf40b96c1cea9feed425a4 Mon Sep 17 00:00:00 2001 From: Philipp Legner Date: Fri, 22 Sep 2023 09:43:17 +0200 Subject: [PATCH] Update toFraction and toMixedNumber functions --- src/arithmetic.ts | 33 +++++++++++++++++++++------------ test/arithmetic-test.ts | 19 ++++++++++++++++++- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/arithmetic.ts b/src/arithmetic.ts index 1452391..8dbd4e7 100644 --- a/src/arithmetic.ts +++ b/src/arithmetic.ts @@ -186,7 +186,7 @@ export function toWord(n: number) { // ----------------------------------------------------------------------------- -// Rounding, Decimals and Decimals +// Rounding, Decimals and Fractions /** Returns the digits of a number n. */ export function digits(n: number) { @@ -209,21 +209,30 @@ export function roundTo(n: number, increment = 1) { * Returns an [numerator, denominator] array that approximated a `decimal` to * `precision`. See http://en.wikipedia.org/wiki/Continued_fraction */ -export function toFraction(decimal: number, precision = PRECISION) { - let n = [1, 0]; let d = [0, 1]; - let a = Math.floor(decimal); - let rem = decimal - a; - - while (d[0] <= 1 / precision) { - if (nearlyEquals(n[0] / d[0], precision)) return [n[0], d[0]]; +export function toFraction(x: number, maxDen = 1000, precision = 1e-12): [num: number, den: number] | undefined { + let n = [1, 0]; + let d = [0, 1]; + const absX = Math.abs(x); + let rem = absX; + + while (Math.abs(n[0] / d[0] - absX) > precision) { + const a = Math.floor(rem); n = [a * n[0] + n[1], n[0]]; d = [a * d[0] + d[1], d[0]]; - a = Math.floor(1 / rem); - rem = 1 / rem - a; + if (d[0] > maxDen) return; + rem = 1 / (rem - a); } - // No nice rational representation so return an irrational "fraction" - return [decimal, 1]; + // We get as close as we want with our tolerance, and if that fraction is still good past our computation we return it. + // Otherwise, we return false, meaning we didn't find a good enough rational approximation. + if (d[0] === 1 || !nearlyEquals(n[0] / d[0], absX, precision)) return; + return [sign(x) * n[0], d[0]]; +} + +export function toMixedNumber(x: number, maxDen?: number, precision?: number): [base: number, num: number, den: number] | undefined { + const base = Math.trunc(x); + const fraction = toFraction(Math.abs(x - base), maxDen, precision); + return (fraction) ? [base, ...fraction] : undefined; } diff --git a/test/arithmetic-test.ts b/test/arithmetic-test.ts index ed1f75f..ce87473 100644 --- a/test/arithmetic-test.ts +++ b/test/arithmetic-test.ts @@ -5,7 +5,7 @@ import tape from 'tape'; -import {numberFormat, parseNumber, scientificFormat, toWord} from '../src'; +import {numberFormat, parseNumber, scientificFormat, toFraction, toMixedNumber, toWord} from '../src'; tape('numberFormat', (test) => { @@ -107,3 +107,20 @@ tape('parseNumber', (test) => { test.end(); }); + + +tape('fractions', (test) => { + test.deepEqual(toFraction(0.333333333333), [1, 3]); + test.deepEqual(toFraction(-0.333333333333), [-1, 3]); + test.deepEqual(toFraction(0.999999999999), undefined); + test.deepEqual(toFraction(0.833333333333), [5, 6]); + test.deepEqual(toFraction(0.171717171717), [17, 99]); + + test.deepEqual(toFraction(0.123412341234), undefined); + test.deepEqual(toFraction(0.123412341234, 10000), [1234, 9999]); + + test.deepEqual(toMixedNumber(1.333333333333), [1, 1, 3]); + test.deepEqual(toMixedNumber(-1.333333333333), [-1, 1, 3]); + + test.end(); +});