Skip to content

Commit

Permalink
Fix complexity issues by coercing to reals
Browse files Browse the repository at this point in the history
Fix potential this alias issues in TimeMonzo.log().

refs: #244, #348
  • Loading branch information
frostburn committed Jun 16, 2024
1 parent d2f7fb2 commit 8aa360e
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 44 deletions.
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@
"vitest": "^1.6.0"
},
"dependencies": {
"moment-of-symmetry": "^0.8.1",
"xen-dev-utils": "^0.9.0"
"moment-of-symmetry": "^0.8.2",
"xen-dev-utils": "^0.9.2"
},
"engines": {
"node": ">=12.0.0"
Expand Down
32 changes: 23 additions & 9 deletions src/monzo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1827,7 +1827,14 @@ export class TimeMonzo {
}
const vector = [];
for (let i = 0; i < other.primeExponents.length; ++i) {
vector.push(this.primeExponents[i].add(other.primeExponents[i]));
try {
vector.push(this.primeExponents[i].add(other.primeExponents[i]));
} catch {
return new TimeReal(
this.timeExponent.valueOf() + other.timeExponent.valueOf(),
this.valueOf() * other.valueOf()
);
}
}
try {
const residual = this.residual.mul(other.residual);
Expand Down Expand Up @@ -1929,7 +1936,14 @@ export class TimeMonzo {
}
const vector = [];
for (let i = 0; i < other.primeExponents.length; ++i) {
vector.push(self.primeExponents[i].sub(other.primeExponents[i]));
try {
vector.push(self.primeExponents[i].sub(other.primeExponents[i]));
} catch {
return new TimeReal(
self.timeExponent.valueOf() - other.timeExponent.valueOf(),
self.valueOf() / other.valueOf()
);
}
}
try {
const residual = self.residual.div(other.residual);
Expand Down Expand Up @@ -2063,29 +2077,29 @@ export class TimeMonzo {
if (solution === undefined) {
if (other.primeExponents[i].n) {
solution = self.primeExponents[i].div(other.primeExponents[i]);
} else if (this.primeExponents[i].n) {
return this.totalCents() / other.totalCents();
} else if (self.primeExponents[i].n) {
return self.totalCents() / other.totalCents();
}
} else if (solution !== undefined) {
if (
!self.primeExponents[i].equals(
other.primeExponents[i].mul(solution)
)
) {
return this.totalCents() / other.totalCents();
return self.totalCents() / other.totalCents();
}
}
}
if (solution === undefined) {
const residualLog = this.residual.log(other.residual);
const residualLog = self.residual.log(other.residual);
if (residualLog === null) {
return this.totalCents() / other.totalCents();
return self.totalCents() / other.totalCents();
}
return residualLog;
}
const residualPow = other.residual.pow(solution);
if (residualPow === null || !residualPow.equals(this.residual)) {
return this.totalCents() / other.totalCents();
if (residualPow === null || !residualPow.equals(self.residual)) {
return self.totalCents() / other.totalCents();
}
return solution;
}
Expand Down
51 changes: 51 additions & 0 deletions src/parser/__tests__/expression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2409,6 +2409,57 @@ describe('SonicWeave expression evaluator', () => {
const {interval} = parseSingle(String.raw`\\\P1 tmpr 12@`);
expect(interval.totalCents()).toBe(-1500);
});

it('coerces too large integers to real', () => {
const interval = evaluate('9007199254740997') as Interval;
expect(interval.value).toBeInstanceOf(TimeReal);
expect(interval.valueOf()).toBeCloseTo(9007199254740996);
});

it('coerces too accurate fractions to real', () => {
const interval = evaluate('9007199254740997/9000000000000000') as Interval;
expect(interval.value).toBeInstanceOf(TimeReal);
expect(interval.valueOf()).toBeCloseTo(1.0008, 6);
});

it('coerces too accurate decimals to real', () => {
const interval = evaluate('1.23456789012345678901e') as Interval;
expect(interval.value).toBeInstanceOf(TimeReal);
expect(interval.valueOf()).toBeCloseTo(1.23456);
});

it('coerces too accurate hertz to real', () => {
const interval = evaluate('1.23456789012345678901Hz') as Interval;
expect(interval.value).toBeInstanceOf(TimeReal);
expect(interval.valueOf()).toBeCloseTo(1.23456);
expect(interval.isAbsolute()).toBe(true);
});

it('coarces too accurate nedji to real', () => {
const interval = evaluate(
'1000000000000000\\9007199254740997<3>'
) as Interval;
expect(interval.value).toBeInstanceOf(TimeReal);
expect(interval.valueOf()).toBeCloseTo(1.12972);
});

it('coerces too accurate cents to real', () => {
const interval = evaluate('1234.56789012345678901') as Interval;
expect(interval.value).toBeInstanceOf(TimeReal);
expect(interval.valueOf()).toBeCloseTo(2.04);
});

it('coerces too accurate monzos to real', () => {
const ronzo = evaluate('[1.234567890123456780901>') as Interval;
expect(ronzo.value).toBeInstanceOf(TimeReal);
expect(ronzo.valueOf()).toBeCloseTo(2.3531);
});

it('coerces too complex nedo subtraction', () => {
const interval = evaluate('103\\94906266 - 1\\94906267') as Interval;
expect(interval.value).toBeInstanceOf(TimeReal);
expect(interval.valueOf()).toBeCloseTo(1.000000745, 10);
});
});

describe('Poor grammar / Fun with "<"', () => {
Expand Down
30 changes: 30 additions & 0 deletions src/parser/__tests__/source.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1991,4 +1991,34 @@ describe('SonicWeave parser', () => {
'Index out of range.'
);
});

it('can handle too complex multiplication resulting from too accurate prime mappings', () => {
const scale = expand(`
14/13
8/7
44/35
4/3
99/70
99/65
396/245
2178/1225
66/35
9801/4900
(* Commas = 1716/1715, 2080/2079 *)
PrimeMapping(1200., 1902.0236738027506, 2786.2942222449124, 3369.11433503606, 4151.361209675464, 4440.252343874884)
cents(£, 3)
`);
expect(scale).toEqual([
'128.862',
'230.886',
'395.953',
'497.976',
'600.',
'728.862',
'830.886',
'995.953',
'1097.976',
'1200.',
]);
});
});
116 changes: 92 additions & 24 deletions src/parser/expression.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Fraction, gcd} from 'xen-dev-utils';
import {Fraction, PRIMES, gcd} from 'xen-dev-utils';
import {
NedjiLiteral,
IntegerLiteral,
Expand Down Expand Up @@ -662,7 +662,18 @@ export class ExpressionVisitor {

protected visitComponent(component: VectorComponent) {
// XXX: This is so backwards...
return new Fraction(formatComponent(component));
const str = formatComponent(component);
try {
return new Fraction(str);
} catch {
if (component.separator === '/') {
return (
(component.left / parseInt(component.right)) *
10 ** (component.exponent ?? 0)
);
}
return parseFloat(str);
}
}

protected upLift(
Expand Down Expand Up @@ -703,7 +714,21 @@ export class ExpressionVisitor {
}
}
} else {
value = new TimeMonzo(ZERO, exponents);
let valid = true;
for (const exponent of exponents) {
if (typeof exponent === 'number') {
valid = false;
}
}
if (valid) {
value = new TimeMonzo(ZERO, exponents as Fraction[]);
} else {
let num = 1;
for (let i = 0; i < exponents.length; ++i) {
num *= PRIMES[i] ** exponents[i].valueOf();
}
value = new TimeReal(0, num);
}
}
const result = this.upLift(value, node);
if (steps.d !== 1) {
Expand All @@ -719,6 +744,11 @@ export class ExpressionVisitor {

protected visitValLiteral(node: ValLiteral) {
const val = node.components.map(this.visitComponent);
for (const component of val) {
if (typeof component === 'number') {
throw new Error('Invalid val literal.');
}
}
let value: TimeMonzo;
let equave = TWO_MONZO;
if (node.basis.length) {
Expand All @@ -729,7 +759,7 @@ export class ExpressionVisitor {
value = valToTimeMonzo(val, subgroup);
equave = subgroup[0];
} else {
value = new TimeMonzo(ZERO, val);
value = new TimeMonzo(ZERO, val as Fraction[]);
}
return new Val(value, equave, node);
}
Expand Down Expand Up @@ -1874,8 +1904,13 @@ export class ExpressionVisitor {
}

protected visitIntegerLiteral(node: IntegerLiteral): Interval {
const value = TimeMonzo.fromBigInt(node.value);
return new Interval(value, 'linear', 0, node);
try {
const value = TimeMonzo.fromBigInt(node.value);
return new Interval(value, 'linear', 0, node);
} catch {
const value = TimeReal.fromValue(Number(node.value));
return new Interval(value, 'linear');
}
}

protected visitDecimalLiteral(node: DecimalLiteral): Interval {
Expand All @@ -1899,11 +1934,26 @@ export class ExpressionVisitor {
numerator = 10n * numerator + BigInt(c);
denominator *= 10n;
}
const value = TimeMonzo.fromBigNumeratorDenominator(numerator, denominator);
if (node.flavor === 'z') {
value.timeExponent = NEGATIVE_ONE;
try {
const value = TimeMonzo.fromBigNumeratorDenominator(
numerator,
denominator
);
if (node.flavor === 'z') {
value.timeExponent = NEGATIVE_ONE;
}
return new Interval(value, 'linear', 0, node);
} catch {
const value = TimeReal.fromValue(
parseFloat(
`${node.sign}${node.whole}.${node.fractional}e${node.exponent ?? '0'}`
)
);
if (node.flavor === 'z') {
value.timeExponent = -1;
}
return new Interval(value, 'linear');
}
return new Interval(value, 'linear', 0, node);
}

protected visitCentsLiteral(node: CentsLiteral): Interval {
Expand Down Expand Up @@ -1937,25 +1987,43 @@ export class ExpressionVisitor {
}

protected visitFractionLiteral(node: FractionLiteral): Interval {
const value = TimeMonzo.fromBigNumeratorDenominator(
node.numerator,
node.denominator
);
return new Interval(value, 'linear', 0, node);
try {
const value = TimeMonzo.fromBigNumeratorDenominator(
node.numerator,
node.denominator
);
return new Interval(value, 'linear', 0, node);
} catch {
const value = TimeReal.fromValue(
Number(node.numerator) / Number(node.denominator)
);
return new Interval(value, 'linear');
}
}

protected visitNedjiLiteral(node: NedjiLiteral): Interval {
let value: TimeMonzo;
const fractionOfEquave = new Fraction(node.numerator, node.denominator);
if (node.equaveNumerator !== null) {
value = TimeMonzo.fromEqualTemperament(
fractionOfEquave,
new Fraction(node.equaveNumerator, node.equaveDenominator ?? undefined)
try {
let value: TimeMonzo;
const fractionOfEquave = new Fraction(node.numerator, node.denominator);
if (node.equaveNumerator !== null) {
value = TimeMonzo.fromEqualTemperament(
fractionOfEquave,
new Fraction(
node.equaveNumerator,
node.equaveDenominator ?? undefined
)
);
} else {
value = TimeMonzo.fromEqualTemperament(fractionOfEquave);
}
return new Interval(value, 'logarithmic', 0, node);
} catch {
const base = (node.equaveNumerator ?? 2) / (node.equaveDenominator ?? 1);
const value = TimeReal.fromValue(
base ** (node.numerator / node.denominator)
);
} else {
value = TimeMonzo.fromEqualTemperament(fractionOfEquave);
return new Interval(value, 'logarithmic');
}
return new Interval(value, 'logarithmic', 0, node);
}

protected visitHertzLiteral(node: HertzLiteral): Interval {
Expand Down

0 comments on commit 8aa360e

Please sign in to comment.