diff --git a/src/munge.js b/src/munge.js index c23cddd..80176b0 100644 --- a/src/munge.js +++ b/src/munge.js @@ -104,7 +104,7 @@ export const munge = (data, model) => { : Second.END_OF_TIME } - if (datum.end.atomic.leS(datum.start.atomic)) { + if (datum.end.atomic === Second.END_OF_TIME ? datum.start.atomic === Second.END_OF_TIME : datum.end.atomic.leS(datum.start.atomic)) { throw Error('Disordered data') } }) diff --git a/src/rat.js b/src/rat.js index 43bd3ec..b561742 100644 --- a/src/rat.js +++ b/src/rat.js @@ -3,13 +3,13 @@ import { div, gcd } from './div.js' export class Rat { constructor (nu, de = 1n) { if (typeof nu !== 'bigint') { - throw Error('Numerator must be a BigInt') + throw Error('numerator must be a BigInt') } if (typeof de !== 'bigint') { - throw Error('Denominator must be a BigInt') + throw Error('denominator must be a BigInt') } - if (de === 0n && nu <= 0n) { - throw Error('Numerator must be positive if denominator is zero') + if (de === 0n) { + throw Error('denominator cannot be zero') } const g = gcd(nu, de) // non-zero @@ -21,9 +21,6 @@ export class Rat { } plus (other) { - if (this.de === 0n && other.de === 0n) { - return new Rat(this.nu + other.nu, 0n) - } return new Rat(this.nu * other.de + this.de * other.nu, this.de * other.de) } @@ -56,5 +53,3 @@ export class Rat { return div(this.nu, this.de) } } - -Rat.INFINITY = new Rat(1n, 0n) diff --git a/src/second.js b/src/second.js index fffe134..8c17fe3 100644 --- a/src/second.js +++ b/src/second.js @@ -54,7 +54,4 @@ Second.fromMillis = millis => { return new Second(BigInt(millis), 1_000n) } -// Support for this special value is limited. In all cases it either returns -// a correct, meaningful result, or throws an exception - it does NOT return -// bad results. -Second.END_OF_TIME = new Second(1n, 0n) +Second.END_OF_TIME = Symbol('end of time') diff --git a/src/segment.js b/src/segment.js index 3034be2..c26f63f 100644 --- a/src/segment.js +++ b/src/segment.js @@ -9,19 +9,19 @@ import { Second } from './second.js' export class Segment { constructor (start, end = { atomic: Second.END_OF_TIME }, slope = { unixPerAtomic: new Rat(1n) }) { if (!(start.atomic instanceof Second)) { - throw Error('TAI start must be a rational number of seconds') + throw Error('TAI start must be a `Second`') } if (!(start.unix instanceof Second)) { - throw Error('Unix start must be a rational number of seconds') + throw Error('Unix start must be a `Second`') } - if (!(end.atomic instanceof Second)) { - throw Error('TAI end must be a rational number of seconds') + if (!(end.atomic instanceof Second || end.atomic === Second.END_OF_TIME)) { + throw Error('TAI end must be a `Second` or `Second.END_OF_TIME`') } if (!(slope.unixPerAtomic instanceof Rat)) { - throw Error('Slope must be a pure ratio') + throw Error('slope must be a `Rat`') } - if (end.atomic.leS(start.atomic)) { - throw Error('Segment length must be positive') + if (end.atomic === Second.END_OF_TIME ? start.atomic === Second.END_OF_TIME : end.atomic.leS(start.atomic)) { + throw Error('segment length must be positive') } this.slope = { @@ -59,6 +59,10 @@ export class Segment { } atomicToUnix (atomic) { + if (atomic === Second.END_OF_TIME) { + return Second.END_OF_TIME + } + return atomic .minusS(this.start.atomic) .timesR(this.slope.unixPerAtomic) @@ -71,12 +75,12 @@ export class Segment { // Unix by the segment. atomicOnSegment (atomic) { - return this.start.atomic.leS(atomic) && this.end.atomic.gtS(atomic) + return this.start.atomic.leS(atomic) && (this.end.atomic === Second.END_OF_TIME ? atomic !== Second.END_OF_TIME : this.end.atomic.gtS(atomic)) } unixOnSegment (unix) { return this.slope.unixPerAtomic.eq(new Rat(0n)) ? this.start.unix.eqS(unix) - : this.start.unix.leS(unix) && this.end.unix.gtS(unix) + : this.start.unix.leS(unix) && (this.end.unix === Second.END_OF_TIME ? unix !== Second.END_OF_TIME : this.end.unix.gtS(unix)) } } diff --git a/test/converter.spec.js b/test/converter.spec.js index 70b9033..d035878 100644 --- a/test/converter.spec.js +++ b/test/converter.spec.js @@ -678,14 +678,14 @@ describe('Converter', () => { describe('BREAK', () => { it('says no', () => { assert.throws(() => new Converter(data, MODELS.BREAK), - /Segment length must be positive/) + /segment length must be positive/) }) }) describe('STALL', () => { it('says no', () => { assert.throws(() => new Converter(data, MODELS.STALL), - /Segment length must be positive/) + /segment length must be positive/) }) }) }) diff --git a/test/millis-converter.spec.js b/test/millis-converter.spec.js index 35748e0..fb7b3d1 100644 --- a/test/millis-converter.spec.js +++ b/test/millis-converter.spec.js @@ -958,14 +958,14 @@ describe('MillisConverter', () => { describe('BREAK', () => { it('says no', () => { assert.throws(() => new MillisConverter(data, MODELS.BREAK), - /Segment length must be positive/) + /segment length must be positive/) }) }) describe('STALL', () => { it('says no', () => { assert.throws(() => new MillisConverter(data, MODELS.STALL), - /Segment length must be positive/) + /segment length must be positive/) }) }) }) diff --git a/test/rat.spec.js b/test/rat.spec.js index 13cf7f4..52061f5 100644 --- a/test/rat.spec.js +++ b/test/rat.spec.js @@ -4,10 +4,10 @@ import { Rat } from '../src/rat.js' describe('Rat', () => { it('type checking', () => { - assert.throws(() => new Rat(1, 2n), /Numerator must be a BigInt/) - assert.throws(() => new Rat(1n, 2), /Denominator must be a BigInt/) - assert.throws(() => new Rat(1n, 0), /Denominator must be a BigInt/) - assert.throws(() => new Rat(-1n, 0n), /Numerator must be positive if denominator is zero/) + assert.throws(() => new Rat(1, 2n), /numerator must be a BigInt/) + assert.throws(() => new Rat(1n, 2), /denominator must be a BigInt/) + assert.throws(() => new Rat(1n, 0), /denominator must be a BigInt/) + assert.throws(() => new Rat(-1n, 0n), /denominator cannot be zero/) }) it('defaults to an integer', () => { @@ -92,84 +92,4 @@ describe('Rat', () => { assert.strictEqual(new Rat(18n, -10n).trunc(), -2n) }) }) - - describe('positive infinity', () => { - it('reduces to the lowest terms', () => { - assert.deepStrictEqual(Rat.INFINITY, new Rat(133n, 0n)) - }) - - it('adds', () => { - assert.deepStrictEqual(Rat.INFINITY.plus(new Rat(15n, 69n)), Rat.INFINITY) - assert.deepStrictEqual(Rat.INFINITY.plus(new Rat(0n, 3n)), Rat.INFINITY) - assert.deepStrictEqual(Rat.INFINITY.plus(new Rat(-12n, 1n)), Rat.INFINITY) - assert.deepStrictEqual(new Rat(15n, 69n).plus(Rat.INFINITY), Rat.INFINITY) - assert.deepStrictEqual(new Rat(0n, 3n).plus(Rat.INFINITY), Rat.INFINITY) - assert.deepStrictEqual(new Rat(-12n, 1n).plus(Rat.INFINITY), Rat.INFINITY) - assert.deepStrictEqual(Rat.INFINITY.plus(Rat.INFINITY), Rat.INFINITY) - }) - - it('subtracts', () => { - assert.deepStrictEqual(Rat.INFINITY.minus(new Rat(15n, 69n)), Rat.INFINITY) - assert.deepStrictEqual(Rat.INFINITY.minus(new Rat(0n, 3n)), Rat.INFINITY) - assert.deepStrictEqual(Rat.INFINITY.minus(new Rat(-12n, 1n)), Rat.INFINITY) - assert.throws(() => new Rat(15n, 69n).minus(Rat.INFINITY)) - assert.throws(() => new Rat(0n, 3n).minus(Rat.INFINITY)) - assert.throws(() => new Rat(-12n, 1n).minus(Rat.INFINITY)) - assert.throws(() => Rat.INFINITY.minus(Rat.INFINITY)) - }) - - it('multiplies', () => { - assert.deepStrictEqual(Rat.INFINITY.times(new Rat(15n, 69n)), Rat.INFINITY) - assert.throws(() => Rat.INFINITY.times(new Rat(0n, 3n))) - assert.throws(() => Rat.INFINITY.times(new Rat(-12n, 1n))) - assert.deepStrictEqual(new Rat(15n, 69n).times(Rat.INFINITY), Rat.INFINITY) - assert.throws(() => new Rat(0n, 3n).times(Rat.INFINITY)) - assert.throws(() => new Rat(-12n, 1n).times(Rat.INFINITY)) - assert.deepStrictEqual(Rat.INFINITY.times(Rat.INFINITY), Rat.INFINITY) - }) - - it('divides', () => { - assert.deepStrictEqual(Rat.INFINITY.divide(new Rat(15n, 69n)), Rat.INFINITY) - assert.deepStrictEqual(Rat.INFINITY.divide(new Rat(0n, 3n)), Rat.INFINITY) - assert.throws(() => Rat.INFINITY.divide(new Rat(-12n, 1n)), /Numerator must be positive if denominator is zero/) - assert.deepStrictEqual(new Rat(15n, 69n).divide(Rat.INFINITY), new Rat(0n)) - assert.deepStrictEqual(new Rat(0n, 3n).divide(Rat.INFINITY), new Rat(0n)) - assert.deepStrictEqual(new Rat(-12n, 1n).divide(Rat.INFINITY), new Rat(0n)) - assert.throws(() => Rat.INFINITY.divide(Rat.INFINITY)) - }) - - it('equals', () => { - assert.strictEqual(Rat.INFINITY.eq(new Rat(15n, 69n)), false) - assert.strictEqual(Rat.INFINITY.eq(new Rat(0n, 3n)), false) - assert.strictEqual(Rat.INFINITY.eq(new Rat(-12n, 1n)), false) - assert.throws(() => new Rat(15n, 69n).eq(Rat.INFINITY)) - assert.throws(() => new Rat(0n, 3n).eq(Rat.INFINITY)) - assert.throws(() => new Rat(-12n, 1n).eq(Rat.INFINITY)) - assert.throws(() => Rat.INFINITY.eq(Rat.INFINITY)) - }) - - it('less than or equal', () => { - assert.strictEqual(Rat.INFINITY.le(new Rat(15n, 69n)), false) - assert.strictEqual(Rat.INFINITY.le(new Rat(0n, 3n)), false) - assert.strictEqual(Rat.INFINITY.le(new Rat(-12n, 1n)), false) - assert.throws(() => new Rat(15n, 69n).le(Rat.INFINITY)) - assert.throws(() => new Rat(0n, 3n).le(Rat.INFINITY)) - assert.throws(() => new Rat(-12n, 1n).le(Rat.INFINITY)) - assert.throws(() => Rat.INFINITY.le(Rat.INFINITY)) - }) - - it('greater than', () => { - assert.strictEqual(Rat.INFINITY.gt(new Rat(15n, 69n)), true) - assert.strictEqual(Rat.INFINITY.gt(new Rat(0n, 3n)), true) - assert.strictEqual(Rat.INFINITY.gt(new Rat(-12n, 1n)), true) - assert.throws(() => new Rat(15n, 69n).gt(Rat.INFINITY)) - assert.throws(() => new Rat(0n, 3n).gt(Rat.INFINITY)) - assert.throws(() => new Rat(-12n, 1n).gt(Rat.INFINITY)) - assert.throws(() => Rat.INFINITY.gt(Rat.INFINITY)) - }) - - it('truncates', () => { - assert.throws(() => Rat.INFINITY.trunc()) - }) - }) }) diff --git a/test/second.spec.js b/test/second.spec.js index e7d1ad7..2f6ec4c 100644 --- a/test/second.spec.js +++ b/test/second.spec.js @@ -5,10 +5,10 @@ import { Second } from '../src/second.js' describe('Second', () => { it('type checking', () => { - assert.throws(() => new Second(1, 2n), /Numerator must be a BigInt/) - assert.throws(() => new Second(1n, 2), /Denominator must be a BigInt/) - assert.throws(() => new Second(1n, 0), /Denominator must be a BigInt/) - assert.throws(() => new Second(-1n, 0n), /Numerator must be positive if denominator is zero/) + assert.throws(() => new Second(1, 2n), /numerator must be a BigInt/) + assert.throws(() => new Second(1n, 2), /denominator must be a BigInt/) + assert.throws(() => new Second(1n, 0), /denominator must be a BigInt/) + assert.throws(() => new Second(-1n, 0n), /denominator cannot be zero/) }) it('defaults to an integer', () => { @@ -64,83 +64,4 @@ describe('Second', () => { it('toMillis', () => { assert.strictEqual(new Second(123n, 1_000n).toMillis(), 123) }) - - describe('positive infinity', () => { - it('reduces to the lowest terms', () => { - assert.deepStrictEqual(Second.END_OF_TIME, new Second(133n, 0n)) - }) - - it('adds', () => { - assert.deepStrictEqual(Second.END_OF_TIME.plusS(new Second(15n, 69n)), Second.END_OF_TIME) - assert.deepStrictEqual(Second.END_OF_TIME.plusS(new Second(0n, 3n)), Second.END_OF_TIME) - assert.deepStrictEqual(Second.END_OF_TIME.plusS(new Second(-12n, 1n)), Second.END_OF_TIME) - assert.deepStrictEqual(new Second(15n, 69n).plusS(Second.END_OF_TIME), Second.END_OF_TIME) - assert.deepStrictEqual(new Second(0n, 3n).plusS(Second.END_OF_TIME), Second.END_OF_TIME) - assert.deepStrictEqual(new Second(-12n, 1n).plusS(Second.END_OF_TIME), Second.END_OF_TIME) - assert.deepStrictEqual(Second.END_OF_TIME.plusS(Second.END_OF_TIME), Second.END_OF_TIME) - }) - - it('subtracts', () => { - assert.deepStrictEqual(Second.END_OF_TIME.minusS(new Second(15n, 69n)), Second.END_OF_TIME) - assert.deepStrictEqual(Second.END_OF_TIME.minusS(new Second(0n, 3n)), Second.END_OF_TIME) - assert.deepStrictEqual(Second.END_OF_TIME.minusS(new Second(-12n, 1n)), Second.END_OF_TIME) - assert.throws(() => new Second(15n, 69n).minusS(Second.END_OF_TIME)) - assert.throws(() => new Second(0n, 3n).minusS(Second.END_OF_TIME)) - assert.throws(() => new Second(-12n, 1n).minusS(Second.END_OF_TIME)) - assert.throws(() => Second.END_OF_TIME.minusS(Second.END_OF_TIME)) - }) - - it('multiplies', () => { - assert.deepStrictEqual(Second.END_OF_TIME.timesR(new Rat(15n, 69n)), Second.END_OF_TIME) - assert.throws(() => Second.END_OF_TIME.timesR(new Rat(0n, 3n))) - assert.throws(() => Second.END_OF_TIME.timesR(new Second(-12n, 1n))) - assert.deepStrictEqual(new Second(15n, 69n).timesR(Rat.INFINITY), Second.END_OF_TIME) - assert.throws(() => new Second(0n, 3n).timesR(Rat.INFINITY)) - assert.throws(() => new Second(-12n, 1n).timesR(Rat.INFINITY)) - }) - - it('divides', () => { - assert.deepStrictEqual(Second.END_OF_TIME.divideS(new Second(15n, 69n)), Rat.INFINITY) - assert.deepStrictEqual(Second.END_OF_TIME.divideS(new Second(0n, 3n)), Rat.INFINITY) - assert.throws(() => Second.END_OF_TIME.divideS(new Second(-12n, 1n)), /Numerator must be positive if denominator is zero/) - assert.deepStrictEqual(new Second(15n, 69n).divideS(Second.END_OF_TIME), new Rat(0n)) - assert.deepStrictEqual(new Second(0n, 3n).divideS(Second.END_OF_TIME), new Rat(0n)) - assert.deepStrictEqual(new Second(-12n, 1n).divideS(Second.END_OF_TIME), new Rat(0n)) - assert.throws(() => Second.END_OF_TIME.divideS(Second.END_OF_TIME)) - }) - - it('equals', () => { - assert.strictEqual(Second.END_OF_TIME.eqS(new Second(15n, 69n)), false) - assert.strictEqual(Second.END_OF_TIME.eqS(new Second(0n, 3n)), false) - assert.strictEqual(Second.END_OF_TIME.eqS(new Second(-12n, 1n)), false) - assert.throws(() => new Second(15n, 69n).eqS(Second.END_OF_TIME)) - assert.throws(() => new Second(0n, 3n).eqS(Second.END_OF_TIME)) - assert.throws(() => new Second(-12n, 1n).eqS(Second.END_OF_TIME)) - assert.throws(() => Second.END_OF_TIME.eqS(Second.END_OF_TIME)) - }) - - it('less than or equal', () => { - assert.strictEqual(Second.END_OF_TIME.leS(new Second(15n, 69n)), false) - assert.strictEqual(Second.END_OF_TIME.leS(new Second(0n, 3n)), false) - assert.strictEqual(Second.END_OF_TIME.leS(new Second(-12n, 1n)), false) - assert.throws(() => new Second(15n, 69n).leS(Second.END_OF_TIME)) - assert.throws(() => new Second(0n, 3n).leS(Second.END_OF_TIME)) - assert.throws(() => new Second(-12n, 1n).leS(Second.END_OF_TIME)) - assert.throws(() => Second.END_OF_TIME.leS(Second.END_OF_TIME)) - }) - - it('greater than', () => { - assert.strictEqual(Second.END_OF_TIME.gtS(new Second(15n, 69n)), true) - assert.strictEqual(Second.END_OF_TIME.gtS(new Second(0n, 3n)), true) - assert.strictEqual(Second.END_OF_TIME.gtS(new Second(-12n, 1n)), true) - assert.throws(() => new Second(15n, 69n).gtS(Second.END_OF_TIME)) - assert.throws(() => new Second(0n, 3n).gtS(Second.END_OF_TIME)) - assert.throws(() => new Second(-12n, 1n).gtS(Second.END_OF_TIME)) - assert.throws(() => Second.END_OF_TIME.gtS(Second.END_OF_TIME)) - }) - - it('converts to milliseconds', () => { - assert.throws(() => Second.END_OF_TIME.toMillis()) - }) - }) }) diff --git a/test/segment.spec.js b/test/segment.spec.js index 7c837e5..967392e 100644 --- a/test/segment.spec.js +++ b/test/segment.spec.js @@ -11,33 +11,33 @@ describe('Segment', () => { it('disallows bad powers', () => { assert.throws(() => new Segment( { atomic: new Rat(0n) } - ), /TAI start must be a rational number of seconds/) + ), /TAI start must be a `Second`/) assert.throws(() => new Segment( { atomic: Second.fromMillis(0), unix: new Rat(0n) } - ), /Unix start must be a rational number of seconds/) + ), /Unix start must be a `Second`/) assert.throws(() => new Segment( { atomic: Second.fromMillis(0), unix: Second.fromMillis(0) }, { atomic: new Rat(1n) } - ), /TAI end must be a rational number of seconds/) + ), /TAI end must be a `Second` or `Second.END_OF_TIME`/) assert.throws(() => new Segment( { atomic: Second.fromMillis(0), unix: Second.fromMillis(0) }, { atomic: Second.fromMillis(1_000) }, { unixPerAtomic: new Second(1n, 1n) } - ), /Slope must be a pure ratio/) + ), /slope must be a `Rat`/) }) it('disallows rays which run backwards', () => { assert.throws(() => new Segment( { atomic: Second.fromMillis(0), unix: Second.fromMillis(0) }, { atomic: new Second(-1n, 1_000_000_000_000n) } - ), /Segment length must be positive/) + ), /segment length must be positive/) }) it('disallows zero-length rays which run backwards', () => { assert.throws(() => new Segment( { atomic: Second.fromMillis(0), unix: Second.fromMillis(0) }, { atomic: Second.fromMillis(0) } - ), /Segment length must be positive/) + ), /segment length must be positive/) }) describe('basic infinite ray', () => {