diff --git a/src/__tests__/fraction.spec.ts b/src/__tests__/fraction.spec.ts index 2049891..ff61b11 100644 --- a/src/__tests__/fraction.spec.ts +++ b/src/__tests__/fraction.spec.ts @@ -674,4 +674,54 @@ describe('Fraction', () => { expect(fraction.n).toBe(0); expect(fraction.d).toBe(1); }); + + it('normalizes zero (infinite denominator)', () => { + const fraction = new Fraction({n: 123, d: Infinity}); + expect(fraction.s).toBe(0); + expect(fraction.n).toBe(0); + expect(fraction.d).toBe(1); + }); + + it('normalizes zero (infinite second argument)', () => { + const fraction = new Fraction(-123, Infinity); + expect(fraction.s).toBe(0); + expect(fraction.n).toBe(0); + expect(fraction.d).toBe(1); + }); + + it('throws an informative error for (Infinity, 1)', () => { + expect(() => new Fraction(Infinity, 1)).throws( + 'Cannot represent Infinity as a fraction' + ); + }); + + it('throws an informative error for (-Infinity, 1)', () => { + expect(() => new Fraction(-Infinity, 1)).throws( + 'Cannot represent Infinity as a fraction' + ); + }); + + it('throws an informative error for Infinity', () => { + expect(() => new Fraction(Infinity)).throws( + 'Cannot represent Infinity as a fraction' + ); + }); + + it('throws an informative error for {n: Infinity, d:1}', () => { + expect(() => new Fraction({n: Infinity, d: 1})).throws( + 'Cannot represent Infinity as a fraction' + ); + }); + + it('throws for NaN (literal)', () => { + expect(() => new Fraction(NaN)).throws( + 'Cannot represent NaN as a fraction' + ); + }); + + it('throws for NaN (implicit)', () => { + expect(() => new Fraction(Infinity, Infinity)).throws( + 'Cannot represent NaN as a fraction' + ); + }); }); diff --git a/src/fraction.ts b/src/fraction.ts index 4f7a449..9fa9635 100644 --- a/src/fraction.ts +++ b/src/fraction.ts @@ -114,12 +114,18 @@ export class Fraction { this.s = Math.sign(numerator * denominator); this.n = Math.abs(numerator); this.d = Math.abs(denominator); - if (this.n > Number.MAX_SAFE_INTEGER) { - throw new Error('Numerator above safe limit'); - } + + this.screenInfinity(); + if (this.d > Number.MAX_SAFE_INTEGER) { throw new Error('Denominator above safe limit'); } + if (this.n > Number.MAX_SAFE_INTEGER) { + if (!isFinite(this.n)) { + throw new Error('Cannot represent Infinity as a fraction'); + } + throw new Error('Numerator above safe limit'); + } if (this.d === 0) { throw new Error('Division by Zero'); } @@ -131,6 +137,9 @@ export class Fraction { this.s = Math.sign(numerator); this.n = Math.abs(numerator); this.d = 1; + if (!isFinite(this.n)) { + throw new Error('Cannot represent Infinity as a fraction'); + } this.defloat(); } else if (typeof numerator === 'string') { numerator = numerator.toLowerCase(); @@ -222,6 +231,7 @@ export class Fraction { } this.n = Math.abs(numerator.n); this.d = Math.abs(numerator.d); + this.screenInfinity(); this.reduce(); } this.validate(); @@ -235,6 +245,9 @@ export class Fraction { throw new Error('Cannot represent NaN as a fraction'); } if (this.n > Number.MAX_SAFE_INTEGER) { + if (!isFinite(this.n)) { + throw new Error('Cannot represent Infinity as a fraction'); + } throw new Error('Numerator above safe limit'); } if (this.d > Number.MAX_SAFE_INTEGER) { @@ -296,6 +309,20 @@ export class Fraction { this.d /= commonFactor; } + /** + * Normalize infinite denominator into 0/1. + */ + screenInfinity() { + if (!isFinite(this.d)) { + if (!isFinite(this.n)) { + throw new Error('Cannot represent NaN as a fraction'); + } + this.s = 0; + this.n = 0; + this.d = 1; + } + } + /** * Creates a string representation of a fraction with all digits. *