Skip to content

Commit

Permalink
Rat and Second are now vaguely intelligible
Browse files Browse the repository at this point in the history
  • Loading branch information
qntm committed Jul 20, 2024
1 parent 99913b0 commit bddd49d
Show file tree
Hide file tree
Showing 9 changed files with 37 additions and 200 deletions.
2 changes: 1 addition & 1 deletion src/munge.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
})
Expand Down
13 changes: 4 additions & 9 deletions src/rat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}

Expand Down Expand Up @@ -56,5 +53,3 @@ export class Rat {
return div(this.nu, this.de)
}
}

Rat.INFINITY = new Rat(1n, 0n)
5 changes: 1 addition & 4 deletions src/second.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
22 changes: 13 additions & 9 deletions src/segment.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)
Expand All @@ -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))
}
}
4 changes: 2 additions & 2 deletions test/converter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
})
})
})
Expand Down
4 changes: 2 additions & 2 deletions test/millis-converter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/)
})
})
})
Expand Down
88 changes: 4 additions & 84 deletions test/rat.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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())
})
})
})
87 changes: 4 additions & 83 deletions test/second.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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())
})
})
})
12 changes: 6 additions & 6 deletions test/segment.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down

0 comments on commit bddd49d

Please sign in to comment.