Skip to content

Commit

Permalink
Fix complexity issues by coercing to reals
Browse files Browse the repository at this point in the history
refs: #244, #348
  • Loading branch information
frostburn committed Jun 16, 2024
1 parent d2f7fb2 commit dbe8b83
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 36 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
9 changes: 8 additions & 1 deletion 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
45 changes: 45 additions & 0 deletions src/parser/__tests__/expression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2409,6 +2409,51 @@ 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);
});
});

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 dbe8b83

Please sign in to comment.