Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix complexity issues by coercing to reals #349

Merged
merged 1 commit into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {

This comment was marked as resolved.

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
Loading