diff --git a/package.json b/package.json index c680586..4b87061 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "jsdom": "^20.0.0", "prettier": "^2.5.1", "typescript": "^4.7.4", - "vitest": "^0.17.1", + "vitest": "^0.26.3", "vue-tsc": "^0.38.1" }, "engines": { diff --git a/src/game/formulas.ts b/src/game/formulas.ts new file mode 100644 index 0000000..e9e96f5 --- /dev/null +++ b/src/game/formulas.ts @@ -0,0 +1,1136 @@ +import Decimal, { DecimalSource } from "util/bignum"; +import { convertComputable, ProcessedComputable } from "util/computed"; +import { unref } from "vue"; + +export type FormulaSource = ProcessedComputable | Formula; +export type InvertibleFormulaSource = ProcessedComputable | InvertibleFormula; + +export type InvertibleFormula = Formula & { invertible: true }; +export type VariableFormula = InvertibleFormula & { hasVariable: true }; + +function F(value: InvertibleFormulaSource): InvertibleFormula; +function F(value: FormulaSource): Formula; +function F(value: FormulaSource) { + return value instanceof Formula + ? value + : new Formula( + () => new Decimal(unref(value)), + value => new Decimal(value) + ); +} + +function processFormulaSource(value: FormulaSource) { + return value instanceof Formula ? value : convertComputable(value); +} + +function unrefFormulaSource(value: Formula | ProcessedComputable) { + return value instanceof Formula ? value.evaluate() : unref(value); +} + +export default class Formula { + public readonly invertible: boolean; + public readonly hasVariable: boolean; + public readonly evaluate: () => Decimal; + public readonly invert: (value: DecimalSource) => Decimal; + + constructor( + evaluate: () => Decimal, + invert?: (value: DecimalSource) => Decimal, + hasVariable = false + ) { + this.invertible = invert != null; + this.hasVariable = hasVariable; + this.evaluate = evaluate; + this.invert = invert ?? (() => Decimal.dNaN); + } + + public static constant(value: InvertibleFormulaSource): InvertibleFormula { + return F(value); + } + + public static variable(value: ProcessedComputable): VariableFormula { + return new Formula( + () => new Decimal(unref(value)), + value => new Decimal(value), + true + ) as VariableFormula; + } + + public static abs(value: FormulaSource) { + return F(value).abs(); + } + + public static neg(value: InvertibleFormulaSource): InvertibleFormula; + public static neg(value: FormulaSource): Formula; + public static neg(value: FormulaSource) { + return F(value).neg(); + } + + public static negate(value: InvertibleFormulaSource): InvertibleFormula; + public static negate(value: FormulaSource): Formula; + public static negate(value: FormulaSource) { + return F(value).neg(); + } + + public static negated(value: InvertibleFormulaSource): InvertibleFormula; + public static negated(value: FormulaSource): Formula; + public static negated(value: FormulaSource) { + return F(value).neg(); + } + + public static sign(value: FormulaSource) { + return F(value).sign(); + } + + public static sgn(value: FormulaSource) { + return F(value).sign(); + } + + public static round(value: FormulaSource) { + return F(value).round(); + } + + public static floor(value: FormulaSource) { + return F(value).floor(); + } + + public static ceil(value: FormulaSource) { + return F(value).ceil(); + } + + public static trunc(value: FormulaSource) { + return F(value).trunc(); + } + + public static add( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static add(value: FormulaSource, other: FormulaSource): Formula; + public static add(value: FormulaSource, other: FormulaSource) { + return F(value).add(other); + } + + public static plus( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static plus(value: FormulaSource, other: FormulaSource): Formula; + public static plus(value: FormulaSource, other: FormulaSource) { + return F(value).add(other); + } + + public static sub( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static sub(value: FormulaSource, other: FormulaSource): Formula; + public static sub(value: FormulaSource, other: FormulaSource) { + return F(value).sub(other); + } + + public static subtract( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static subtract(value: FormulaSource, other: FormulaSource): Formula; + public static subtract(value: FormulaSource, other: FormulaSource) { + return F(value).sub(other); + } + + public static minus( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static minus(value: FormulaSource, other: FormulaSource): Formula; + public static minus(value: FormulaSource, other: FormulaSource) { + return F(value).sub(other); + } + + public static mul( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static mul(value: FormulaSource, other: FormulaSource): Formula; + public static mul(value: FormulaSource, other: FormulaSource) { + return F(value).mul(other); + } + + public static multiply( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static multiply(value: FormulaSource, other: FormulaSource): Formula; + public static multiply(value: FormulaSource, other: FormulaSource) { + return F(value).mul(other); + } + + public static times( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static times(value: FormulaSource, other: FormulaSource): Formula; + public static times(value: FormulaSource, other: FormulaSource) { + return F(value).mul(other); + } + + public static div( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static div(value: FormulaSource, other: FormulaSource): Formula; + public static div(value: FormulaSource, other: FormulaSource) { + return F(value).div(other); + } + + public static divide( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static divide(value: FormulaSource, other: FormulaSource): Formula; + public static divide(value: FormulaSource, other: FormulaSource) { + return F(value).div(other); + } + + public static recip(value: InvertibleFormulaSource): InvertibleFormula; + public static recip(value: FormulaSource): Formula; + public static recip(value: FormulaSource) { + return F(value).recip(); + } + + public static reciprocal(value: InvertibleFormulaSource): InvertibleFormula; + public static reciprocal(value: FormulaSource): Formula; + public static reciprocal(value: FormulaSource) { + return F(value).recip(); + } + + public static reciprocate(value: InvertibleFormulaSource): InvertibleFormula; + public static reciprocate(value: FormulaSource): Formula; + public static reciprocate(value: FormulaSource) { + return F(value).reciprocate(); + } + + public static max(value: FormulaSource, other: FormulaSource) { + return F(value).max(other); + } + + public static min(value: FormulaSource, other: FormulaSource) { + return F(value).min(other); + } + + public static minabs(value: FormulaSource, other: FormulaSource) { + return F(value).minabs(other); + } + + public static maxabs(value: FormulaSource, other: FormulaSource) { + return F(value).maxabs(other); + } + + public static clamp(value: FormulaSource, min: FormulaSource, max: FormulaSource) { + return F(value).clamp(min, max); + } + + public static clampMin(value: FormulaSource, min: FormulaSource) { + return F(value).clampMin(min); + } + + public static clampMax(value: FormulaSource, max: FormulaSource) { + return F(value).clampMax(max); + } + + public static pLog10(value: InvertibleFormulaSource): InvertibleFormula; + public static pLog10(value: FormulaSource): Formula; + public static pLog10(value: FormulaSource) { + return F(value).pLog10(); + } + + public static absLog10(value: InvertibleFormulaSource): InvertibleFormula; + public static absLog10(value: FormulaSource): Formula; + public static absLog10(value: FormulaSource) { + return F(value).absLog10(); + } + + public static log10(value: InvertibleFormulaSource): InvertibleFormula; + public static log10(value: FormulaSource): Formula; + public static log10(value: FormulaSource) { + return F(value).log10(); + } + + public static log( + value: InvertibleFormulaSource, + base: InvertibleFormulaSource + ): InvertibleFormula; + public static log(value: FormulaSource, base: FormulaSource): Formula; + public static log(value: FormulaSource, base: FormulaSource) { + return F(value).log(base); + } + + public static logarithm( + value: InvertibleFormulaSource, + base: InvertibleFormulaSource + ): InvertibleFormula; + public static logarithm(value: FormulaSource, base: FormulaSource): Formula; + public static logarithm(value: FormulaSource, base: FormulaSource) { + return F(value).log(base); + } + + public static log2(value: InvertibleFormulaSource): InvertibleFormula; + public static log2(value: FormulaSource): Formula; + public static log2(value: FormulaSource) { + return F(value).log2(); + } + + public static ln(value: InvertibleFormulaSource): InvertibleFormula; + public static ln(value: FormulaSource): Formula; + public static ln(value: FormulaSource) { + return F(value).ln(); + } + + public static pow( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static pow(value: FormulaSource, other: FormulaSource): Formula; + public static pow(value: FormulaSource, other: FormulaSource) { + return F(value).pow(other); + } + + public static pow10(value: InvertibleFormulaSource): InvertibleFormula; + public static pow10(value: FormulaSource): Formula; + public static pow10(value: FormulaSource) { + return F(value).pow10(); + } + + public static pow_base( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static pow_base(value: FormulaSource, other: FormulaSource): Formula; + public static pow_base(value: FormulaSource, other: FormulaSource) { + return F(value).pow_base(other); + } + + public static root( + value: InvertibleFormulaSource, + other: InvertibleFormulaSource + ): InvertibleFormula; + public static root(value: FormulaSource, other: FormulaSource): Formula; + public static root(value: FormulaSource, other: FormulaSource) { + return F(value).root(other); + } + + public static factorial(value: FormulaSource) { + return F(value).factorial(); + } + + public static gamma(value: FormulaSource) { + return F(value).gamma(); + } + + public static lngamma(value: FormulaSource) { + return F(value).lngamma(); + } + + public static exp(value: InvertibleFormulaSource): InvertibleFormula; + public static exp(value: FormulaSource): Formula; + public static exp(value: FormulaSource) { + return F(value).exp(); + } + + public static sqr(value: InvertibleFormulaSource): InvertibleFormula; + public static sqr(value: FormulaSource): Formula; + public static sqr(value: FormulaSource) { + return F(value).sqr(); + } + + public static sqrt(value: InvertibleFormulaSource): InvertibleFormula; + public static sqrt(value: FormulaSource): Formula; + public static sqrt(value: FormulaSource) { + return F(value).sqrt(); + } + + public static cube(value: InvertibleFormulaSource): InvertibleFormula; + public static cube(value: FormulaSource): Formula; + public static cube(value: FormulaSource) { + return F(value).cube(); + } + + public static cbrt(value: InvertibleFormulaSource): InvertibleFormula; + public static cbrt(value: FormulaSource): Formula; + public static cbrt(value: FormulaSource) { + return F(value).cbrt(); + } + + public static tetrate( + value: InvertibleFormulaSource, + height?: InvertibleFormulaSource, + payload?: InvertibleFormulaSource + ): InvertibleFormula; + public static tetrate( + value: FormulaSource, + height?: FormulaSource, + payload?: FormulaSource + ): Formula; + public static tetrate( + value: FormulaSource, + height: FormulaSource = 2, + payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1) + ) { + return F(value).tetrate(height, payload); + } + + public static iteratedexp( + value: InvertibleFormulaSource, + height?: InvertibleFormulaSource, + payload?: InvertibleFormulaSource + ): InvertibleFormula; + public static iteratedexp( + value: FormulaSource, + height?: FormulaSource, + payload?: FormulaSource + ): Formula; + public static iteratedexp( + value: FormulaSource, + height: FormulaSource = 2, + payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1) + ) { + return F(value).iteratedexp(height, payload); + } + + public static iteratedlog( + value: FormulaSource, + base: FormulaSource = 10, + times: FormulaSource = 1 + ) { + return F(value).iteratedlog(base, times); + } + + public static slog( + value: InvertibleFormulaSource, + base?: InvertibleFormulaSource + ): InvertibleFormula; + public static slog(value: FormulaSource, base?: FormulaSource): Formula; + public static slog(value: FormulaSource, base: FormulaSource = 10) { + return F(value).slog(base); + } + + public static layeradd10(value: FormulaSource, diff: FormulaSource) { + return F(value).layeradd10(diff); + } + + public static layeradd( + value: InvertibleFormulaSource, + diff: InvertibleFormulaSource, + base?: InvertibleFormulaSource + ): InvertibleFormula; + public static layeradd( + value: FormulaSource, + diff: FormulaSource, + base?: FormulaSource + ): Formula; + public static layeradd(value: FormulaSource, diff: FormulaSource, base: FormulaSource = 10) { + return F(value).layeradd(diff, base); + } + + public static lambertw(value: InvertibleFormulaSource): InvertibleFormula; + public static lambertw(value: FormulaSource): Formula; + public static lambertw(value: FormulaSource) { + return F(value).lambertw(); + } + + public static ssqrt(value: InvertibleFormulaSource): InvertibleFormula; + public static ssqrt(value: FormulaSource): Formula; + public static ssqrt(value: FormulaSource) { + return F(value).ssqrt(); + } + + public static pentate( + value: FormulaSource, + height: FormulaSource = 2, + payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1) + ) { + return F(value).pentate(height, payload); + } + + public static sin(value: InvertibleFormulaSource): InvertibleFormula; + public static sin(value: FormulaSource): Formula; + public static sin(value: FormulaSource) { + return F(value).sin(); + } + + public static cos(value: InvertibleFormulaSource): InvertibleFormula; + public static cos(value: FormulaSource): Formula; + public static cos(value: FormulaSource) { + return F(value).cos(); + } + + public static tan(value: InvertibleFormulaSource): InvertibleFormula; + public static tan(value: FormulaSource): Formula; + public static tan(value: FormulaSource) { + return F(value).tan(); + } + + public static asin(value: InvertibleFormulaSource): InvertibleFormula; + public static asin(value: FormulaSource): Formula; + public static asin(value: FormulaSource) { + return F(value).asin(); + } + + public static acos(value: InvertibleFormulaSource): InvertibleFormula; + public static acos(value: FormulaSource): Formula; + public static acos(value: FormulaSource) { + return F(value).acos(); + } + + public static atan(value: InvertibleFormulaSource): InvertibleFormula; + public static atan(value: FormulaSource): Formula; + public static atan(value: FormulaSource) { + return F(value).atan(); + } + + public static sinh(value: InvertibleFormulaSource): InvertibleFormula; + public static sinh(value: FormulaSource): Formula; + public static sinh(value: FormulaSource) { + return F(value).sinh(); + } + + public static cosh(value: InvertibleFormulaSource): InvertibleFormula; + public static cosh(value: FormulaSource): Formula; + public static cosh(value: FormulaSource) { + return F(value).cosh(); + } + + public static tanh(value: InvertibleFormulaSource): InvertibleFormula; + public static tanh(value: FormulaSource): Formula; + public static tanh(value: FormulaSource) { + return F(value).tanh(); + } + + public static asinh(value: InvertibleFormulaSource): InvertibleFormula; + public static asinh(value: FormulaSource): Formula; + public static asinh(value: FormulaSource) { + return F(value).asinh(); + } + + public static acosh(value: InvertibleFormulaSource): InvertibleFormula; + public static acosh(value: FormulaSource): Formula; + public static acosh(value: FormulaSource) { + return F(value).acosh(); + } + + public static atanh(value: InvertibleFormulaSource): InvertibleFormula; + public static atanh(value: FormulaSource): Formula; + public static atanh(value: FormulaSource) { + return F(value).atanh(); + } + + public abs() { + return new Formula(() => this.evaluate().abs()); + } + + public neg(this: InvertibleFormula): InvertibleFormula; + public neg(this: Formula): Formula; + public neg() { + return new Formula( + () => this.evaluate().neg(), + value => Decimal.neg(value) + ); + } + + public negate(this: InvertibleFormula): InvertibleFormula; + public negate(this: Formula): Formula; + public negate() { + return this.neg(); + } + + public negated(this: InvertibleFormula): InvertibleFormula; + public negated(this: Formula): Formula; + public negated() { + return this.neg(); + } + + public sign() { + return new Formula(() => new Decimal(this.evaluate().sign)); + } + + public sgn() { + return this.sign(); + } + + public round() { + return new Formula(() => this.evaluate().round()); + } + + public floor() { + return new Formula(() => this.evaluate().floor()); + } + + public ceil() { + return new Formula(() => this.evaluate().ceil()); + } + + public trunc() { + return new Formula(() => this.evaluate().trunc()); + } + + public add(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public add(this: Formula, value: FormulaSource): Formula; + public add(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula( + () => this.evaluate().add(unrefFormulaSource(v)), + (v instanceof Formula && !v.invertible) || !this.invertible + ? undefined + : value => Decimal.sub(value, unrefFormulaSource(v)) + ); + } + + public plus(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public plus(this: Formula, value: FormulaSource): Formula; + public plus(value: FormulaSource) { + return this.add(value); + } + + public sub(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public sub(this: Formula, value: FormulaSource): Formula; + public sub(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula( + () => this.evaluate().sub(unrefFormulaSource(v)), + (v instanceof Formula && !v.invertible) || !this.invertible + ? undefined + : value => Decimal.add(value, unrefFormulaSource(v)) + ); + } + + public subtract(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public subtract(this: Formula, value: FormulaSource): Formula; + public subtract(value: FormulaSource) { + return this.sub(value); + } + + public minus(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public minus(this: Formula, value: FormulaSource): Formula; + public minus(value: FormulaSource) { + return this.sub(value); + } + + public mul(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public mul(this: Formula, value: FormulaSource): Formula; + public mul(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula( + () => this.evaluate().mul(unrefFormulaSource(v)), + (v instanceof Formula && !v.invertible) || !this.invertible + ? undefined + : value => Decimal.div(value, unrefFormulaSource(v)) + ); + } + + public multiply(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public multiply(this: Formula, value: FormulaSource): Formula; + public multiply(value: FormulaSource) { + return this.mul(value); + } + + public times(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public times(this: Formula, value: FormulaSource): Formula; + public times(value: FormulaSource) { + return this.mul(value); + } + + public div(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public div(this: Formula, value: FormulaSource): Formula; + public div(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula( + () => this.evaluate().div(unrefFormulaSource(v)), + (v instanceof Formula && !v.invertible) || !this.invertible + ? undefined + : value => Decimal.mul(value, unrefFormulaSource(v)) + ); + } + + public divide(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public divide(this: Formula, value: FormulaSource): Formula; + public divide(value: FormulaSource) { + return this.div(value); + } + + public divideBy(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public divideBy(this: Formula, value: FormulaSource): Formula; + public divideBy(value: FormulaSource) { + return this.div(value); + } + + public dividedBy(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public dividedBy(this: Formula, value: FormulaSource): Formula; + public dividedBy(value: FormulaSource) { + return this.div(value); + } + + public recip(this: InvertibleFormula): InvertibleFormula; + public recip(this: Formula): Formula; + public recip() { + return new Formula( + () => this.evaluate().recip(), + !this.invertible ? undefined : value => Decimal.recip(value) + ); + } + + public reciprocal(this: InvertibleFormula): InvertibleFormula; + public reciprocal(this: Formula): Formula; + public reciprocal() { + return this.recip(); + } + + public reciprocate(this: InvertibleFormula): InvertibleFormula; + public reciprocate(this: Formula): Formula; + public reciprocate() { + return this.recip(); + } + + public max(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula(() => this.evaluate().max(unrefFormulaSource(v))); + } + + public min(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula(() => this.evaluate().min(unrefFormulaSource(v))); + } + + public maxabs(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula(() => this.evaluate().maxabs(unrefFormulaSource(v))); + } + + public minabs(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula(() => this.evaluate().minabs(unrefFormulaSource(v))); + } + + public clamp(min: FormulaSource, max: FormulaSource) { + const minValue = processFormulaSource(min); + const maxValue = processFormulaSource(max); + return new Formula(() => + this.evaluate().clamp(unrefFormulaSource(minValue), unrefFormulaSource(maxValue)) + ); + } + + public clampMin(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula(() => this.evaluate().clampMin(unrefFormulaSource(v))); + } + + public clampMax(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula(() => this.evaluate().clampMax(unrefFormulaSource(v))); + } + + public pLog10(this: InvertibleFormula): InvertibleFormula; + public pLog10(this: Formula): Formula; + public pLog10() { + return new Formula(() => this.evaluate().pLog10()); + } + + public absLog10(this: InvertibleFormula): InvertibleFormula; + public absLog10(this: Formula): Formula; + public absLog10() { + return new Formula(() => this.evaluate().absLog10()); + } + + public log10(this: InvertibleFormula): InvertibleFormula; + public log10(this: Formula): Formula; + public log10() { + return new Formula( + () => this.evaluate().log10(), + !this.invertible ? undefined : value => Decimal.pow10(value) + ); + } + + public log(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public log(this: Formula, value: FormulaSource): Formula; + public log(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula( + () => this.evaluate().log(unrefFormulaSource(v)), + (v instanceof Formula && !v.invertible) || !this.invertible + ? undefined + : value => Decimal.pow(unrefFormulaSource(v), value) + ); + } + + public logarithm(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public logarithm(this: Formula, value: FormulaSource): Formula; + public logarithm(value: FormulaSource) { + return this.log(value); + } + + public log2(this: InvertibleFormula): InvertibleFormula; + public log2(this: Formula): Formula; + public log2() { + return new Formula( + () => this.evaluate().log2(), + !this.invertible ? undefined : value => Decimal.pow(2, value) + ); + } + + public ln(this: InvertibleFormula): InvertibleFormula; + public ln(this: Formula): Formula; + public ln() { + return new Formula( + () => this.evaluate().ln(), + !this.invertible ? undefined : value => Decimal.exp(value) + ); + } + + public pow(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public pow(this: Formula, value: FormulaSource): Formula; + public pow(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula( + () => this.evaluate().pow(unrefFormulaSource(v)), + (v instanceof Formula && !v.invertible) || !this.invertible + ? undefined + : value => Decimal.root(value, unrefFormulaSource(v)) + ); + } + + public pow10(this: InvertibleFormula): InvertibleFormula; + public pow10(this: Formula): Formula; + public pow10() { + return new Formula( + () => this.evaluate().pow10(), + !this.invertible ? undefined : value => Decimal.root(value, 10) + ); + } + + public pow_base(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public pow_base(this: Formula, value: FormulaSource): Formula; + public pow_base(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula( + () => this.evaluate().pow_base(unrefFormulaSource(v)), + (v instanceof Formula && !v.invertible) || !this.invertible + ? undefined + : value => Decimal.root(unrefFormulaSource(v), value) + ); + } + + public root(this: InvertibleFormula, value: FormulaSource): InvertibleFormula; + public root(this: Formula, value: FormulaSource): Formula; + public root(value: FormulaSource) { + const v = processFormulaSource(value); + return new Formula( + () => this.evaluate().root(unrefFormulaSource(v)), + (v instanceof Formula && !v.invertible) || !this.invertible + ? undefined + : value => Decimal.pow(value, unrefFormulaSource(v)) + ); + } + + public factorial() { + return new Formula(() => this.evaluate().factorial()); + } + + public gamma() { + return new Formula(() => this.evaluate().gamma()); + } + public lngamma() { + return new Formula(() => this.evaluate().lngamma()); + } + + public exp(this: InvertibleFormula): InvertibleFormula; + public exp(this: Formula): Formula; + public exp() { + return new Formula( + () => this.evaluate().exp(), + !this.invertible ? undefined : value => Decimal.ln(value) + ); + } + + public sqr(this: InvertibleFormula): InvertibleFormula; + public sqr(this: Formula): Formula; + public sqr() { + return this.pow(2); + } + + public sqrt(this: InvertibleFormula): InvertibleFormula; + public sqrt(this: Formula): Formula; + public sqrt() { + return this.root(2); + } + + public cube(this: InvertibleFormula): InvertibleFormula; + public cube(this: Formula): Formula; + public cube() { + return this.pow(3); + } + + public cbrt(this: InvertibleFormula): InvertibleFormula; + public cbrt(this: Formula): Formula; + public cbrt() { + return this.pow(1 / 3); + } + + public tetrate( + this: InvertibleFormula, + height?: FormulaSource, + payload?: FormulaSource + ): InvertibleFormula; + public tetrate(this: Formula, height?: FormulaSource, payload?: FormulaSource): Formula; + public tetrate( + height: FormulaSource = 2, + payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1) + ) { + const heightValue = processFormulaSource(height); + const payloadValue = processFormulaSource(payload); + return new Formula( + () => + this.evaluate().tetrate( + Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(), + unrefFormulaSource(payloadValue) + ), + (heightValue instanceof Formula && !heightValue.invertible) || + (payloadValue instanceof Formula && !payloadValue.invertible) || + !this.invertible + ? undefined + : value => + Decimal.slog( + value, + Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber() + ) + ); + } + + public iteratedexp( + this: InvertibleFormula, + height?: FormulaSource, + payload?: FormulaSource + ): InvertibleFormula; + public iteratedexp(this: Formula, height?: FormulaSource, payload?: FormulaSource): Formula; + public iteratedexp( + height: FormulaSource = 2, + payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1) + ) { + const heightValue = processFormulaSource(height); + const payloadValue = processFormulaSource(payload); + return new Formula( + () => + this.evaluate().iteratedexp( + Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(), + new Decimal(unrefFormulaSource(payloadValue)) + ), + (heightValue instanceof Formula && !heightValue.invertible) || + (payloadValue instanceof Formula && !payloadValue.invertible) || + !this.invertible + ? undefined + : value => + Decimal.iteratedlog( + value, + Math.E, + Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber() + ) + ); + } + + public iteratedlog(base: FormulaSource = 10, times: FormulaSource = 1) { + const baseValue = processFormulaSource(base); + const timesValue = processFormulaSource(times); + return new Formula(() => + this.evaluate().iteratedlog( + unrefFormulaSource(baseValue), + Decimal.min(1e308, unrefFormulaSource(timesValue)).toNumber() + ) + ); + } + + public slog(base: FormulaSource = 10) { + const baseValue = processFormulaSource(base); + return new Formula( + () => + this.evaluate().slog(Decimal.min(1e308, unrefFormulaSource(baseValue)).toNumber()), + (baseValue instanceof Formula && !baseValue.invertible) || !this.invertible + ? undefined + : value => + Decimal.tetrate( + value, + Decimal.min(1e308, unrefFormulaSource(baseValue)).toNumber() + ) + ); + } + + public layeradd10(diff: FormulaSource) { + const diffValue = processFormulaSource(diff); + return new Formula(() => this.evaluate().layeradd10(unrefFormulaSource(diffValue))); + } + + public layeradd( + this: InvertibleFormula, + diff: FormulaSource, + base: FormulaSource + ): InvertibleFormula; + public layeradd(this: Formula, diff: FormulaSource, base: FormulaSource): Formula; + public layeradd(diff: FormulaSource, base: FormulaSource) { + const diffValue = processFormulaSource(diff); + const baseValue = processFormulaSource(base); + return new Formula( + () => + this.evaluate().layeradd( + Decimal.min(1e308, unrefFormulaSource(diffValue)).toNumber(), + unrefFormulaSource(baseValue) + ), + (diffValue instanceof Formula && !diffValue.invertible) || + (diffValue instanceof Formula && !diffValue.invertible) || + !this.invertible + ? undefined + : value => + Decimal.layeradd( + value, + Decimal.min(1e308, unrefFormulaSource(diffValue)).negate().toNumber(), + unrefFormulaSource(baseValue) + ) + ); + } + + public lambertw(this: InvertibleFormula): InvertibleFormula; + public lambertw(this: Formula): Formula; + public lambertw() { + return new Formula( + () => this.evaluate().lambertw(), + !this.invertible ? undefined : value => Decimal.pow(Math.E, value).times(value) + ); + } + + public ssqrt(this: InvertibleFormula): InvertibleFormula; + public ssqrt(this: Formula): Formula; + public ssqrt() { + return new Formula( + () => this.evaluate().ssqrt(), + !this.invertible ? undefined : value => Decimal.tetrate(value, 2) + ); + } + + public pentate( + height: FormulaSource = 2, + payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1) + ) { + const heightValue = processFormulaSource(height); + const payloadValue = processFormulaSource(payload); + return new Formula(() => + this.evaluate().pentate( + Decimal.min(1e308, unrefFormulaSource(heightValue)).toNumber(), + unrefFormulaSource(payloadValue) + ) + ); + } + + public sin(this: InvertibleFormula): InvertibleFormula; + public sin(this: Formula): Formula; + public sin() { + return new Formula( + () => this.evaluate().sin(), + !this.invertible ? undefined : value => Decimal.asin(value) + ); + } + + public cos(this: InvertibleFormula): InvertibleFormula; + public cos(this: Formula): Formula; + public cos() { + return new Formula( + () => this.evaluate().cos(), + !this.invertible ? undefined : value => Decimal.acos(value) + ); + } + + public tan(this: InvertibleFormula): InvertibleFormula; + public tan(this: Formula): Formula; + public tan() { + return new Formula( + () => this.evaluate().tan(), + !this.invertible ? undefined : value => Decimal.atan(value) + ); + } + + public asin(this: InvertibleFormula): InvertibleFormula; + public asin(this: Formula): Formula; + public asin() { + return new Formula( + () => this.evaluate().asin(), + !this.invertible ? undefined : value => Decimal.sin(value) + ); + } + + public acos(this: InvertibleFormula): InvertibleFormula; + public acos(this: Formula): Formula; + public acos() { + return new Formula( + () => this.evaluate().acos(), + !this.invertible ? undefined : value => Decimal.cos(value) + ); + } + + public atan(this: InvertibleFormula): InvertibleFormula; + public atan(this: Formula): Formula; + public atan() { + return new Formula( + () => this.evaluate().atan(), + !this.invertible ? undefined : value => Decimal.tan(value) + ); + } + + public sinh(this: InvertibleFormula): InvertibleFormula; + public sinh(this: Formula): Formula; + public sinh() { + return new Formula( + () => this.evaluate().sinh(), + !this.invertible ? undefined : value => Decimal.asinh(value) + ); + } + + public cosh(this: InvertibleFormula): InvertibleFormula; + public cosh(this: Formula): Formula; + public cosh() { + return new Formula( + () => this.evaluate().cosh(), + !this.invertible ? undefined : value => Decimal.acosh(value) + ); + } + + public tanh(this: InvertibleFormula): InvertibleFormula; + public tanh(this: Formula): Formula; + public tanh() { + return new Formula( + () => this.evaluate().tanh(), + !this.invertible ? undefined : value => Decimal.atanh(value) + ); + } + + public asinh(this: InvertibleFormula): InvertibleFormula; + public asinh(this: Formula): Formula; + public asinh() { + return new Formula( + () => this.evaluate().asinh(), + !this.invertible ? undefined : value => Decimal.sinh(value) + ); + } + + public acosh(this: InvertibleFormula): InvertibleFormula; + public acosh(this: Formula): Formula; + public acosh() { + return new Formula( + () => this.evaluate().acosh(), + !this.invertible ? undefined : value => Decimal.cosh(value) + ); + } + + public atanh(this: InvertibleFormula): InvertibleFormula; + public atanh(this: Formula): Formula; + public atanh() { + return new Formula( + () => this.evaluate().atanh(), + !this.invertible ? undefined : value => Decimal.tanh(value) + ); + } +} diff --git a/src/lib/break_eternity.ts b/src/lib/break_eternity.ts index b2d71be..a1d957c 100644 --- a/src/lib/break_eternity.ts +++ b/src/lib/break_eternity.ts @@ -344,16 +344,16 @@ export type DecimalSource = Decimal | number | string; * The Decimal's value is simply mantissa * 10^exponent. */ export default class Decimal { - public static readonly dZero = FC_NN(0, 0, 0); - public static readonly dOne = FC_NN(1, 0, 1); - public static readonly dNegOne = FC_NN(-1, 0, 1); - public static readonly dTwo = FC_NN(1, 0, 2); - public static readonly dTen = FC_NN(1, 0, 10); - public static readonly dNaN = FC_NN(Number.NaN, Number.NaN, Number.NaN); - public static readonly dInf = FC_NN(1, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); - public static readonly dNegInf = FC_NN(-1, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY); - public static readonly dNumberMax = FC(1, 0, Number.MAX_VALUE); - public static readonly dNumberMin = FC(1, 0, Number.MIN_VALUE); + public static dZero: Decimal; + public static dOne: Decimal; + public static dNegOne: Decimal; + public static dTwo: Decimal; + public static dTen: Decimal; + public static dNaN: Decimal; + public static dInf: Decimal; + public static dNegInf: Decimal; + public static dNumberMax: Decimal; + public static dNumberMin: Decimal; private static fromStringCache = new LRUCache(DEFAULT_FROM_STRING_CACHE_SIZE); @@ -705,7 +705,7 @@ export default class Decimal { public static eq_tolerance( value: DecimalSource, other: DecimalSource, - tolerance: number + tolerance?: number ): boolean { return D(value).eq_tolerance(other, tolerance); } @@ -713,7 +713,7 @@ export default class Decimal { public static equals_tolerance( value: DecimalSource, other: DecimalSource, - tolerance: number + tolerance?: number ): boolean { return D(value).eq_tolerance(other, tolerance); } @@ -858,7 +858,7 @@ export default class Decimal { return D(value).layeradd10(diff); } - public static layeradd(value: DecimalSource, diff: number, base = 10): Decimal { + public static layeradd(value: DecimalSource, diff: number, base: DecimalSource = 10): Decimal { return D(value).layeradd(diff, base); } @@ -2037,7 +2037,7 @@ export default class Decimal { * For example, if you put in 1e-9, then any number closer to the * larger number than (larger number)*1e-9 will be considered equal. */ - public eq_tolerance(value: DecimalSource, tolerance: number): boolean { + public eq_tolerance(value: DecimalSource, tolerance?: number): boolean { const decimal = D(value); // https://stackoverflow.com/a/33024979 if (tolerance == null) { tolerance = 1e-7; @@ -2961,6 +2961,19 @@ export default class Decimal { // return Decimal; } +// Assign these after the Decimal is assigned because vitest had issues otherwise +// If we can figure out why, we can make these readonly properties instead +Decimal.dZero = FC_NN(0, 0, 0); +Decimal.dOne = FC_NN(1, 0, 1); +Decimal.dNegOne = FC_NN(-1, 0, 1); +Decimal.dTwo = FC_NN(1, 0, 2); +Decimal.dTen = FC_NN(1, 0, 10); +Decimal.dNaN = FC_NN(Number.NaN, Number.NaN, Number.NaN); +Decimal.dInf = FC_NN(1, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); +Decimal.dNegInf = FC_NN(-1, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY); +Decimal.dNumberMax = FC(1, 0, Number.MAX_VALUE); +Decimal.dNumberMin = FC(1, 0, Number.MIN_VALUE); + // return Decimal; // Optimise Decimal aliases. diff --git a/tests/game/formulas.test.ts b/tests/game/formulas.test.ts new file mode 100644 index 0000000..565ec76 --- /dev/null +++ b/tests/game/formulas.test.ts @@ -0,0 +1,365 @@ +import Formula, { InvertibleFormula } from "game/formulas"; +import Decimal, { DecimalSource } from "util/bignum"; +import { beforeAll, describe, expect, test } from "vitest"; +import { ref } from "vue"; + +type FormulaFunctions = keyof Formula & keyof typeof Formula & keyof typeof Decimal; + +interface FixedLengthArray extends ArrayLike { + length: L; +} + +function compare_tolerance(value: DecimalSource) { + return (other: DecimalSource) => Decimal.eq_tolerance(value, other); +} + +function testConstant( + desc: string, + formulaFunc: () => InvertibleFormula, + expectedValue: DecimalSource = 10 +) { + describe(desc, () => { + let formula: Formula; + beforeAll(() => { + formula = formulaFunc(); + }); + test("evaluates correctly", () => expect(formula.evaluate()).toEqual(expectedValue)); + test("inverts correctly", () => expect(formula.invert(10)).toEqual(expectedValue)); + test("is invertible", () => expect(formula.invertible).toBe(true)); + test("is not marked as having a variable", () => expect(formula.hasVariable).toBe(false)); + }); +} + +// Utility function that will test all the different +// It's a lot of tests, but I'd rather be exhaustive +function testFormula( + functionNames: readonly T[], + args: Readonly["length"]>>, + invertible = true +) { + let value: Decimal; + + beforeAll(() => { + value = testValueFormulas[args[0]].evaluate(); + }); + + functionNames.forEach(name => { + let testName = name + "("; + for (let i = 0; i < args.length; i++) { + if (i !== 0) { + testName += ", "; + } + testName += testValues[args[i]]; + } + testName += ")"; + describe(testName, () => { + let expectedEvaluation: Decimal | undefined; + let formulaArgs: Formula[]; + let staticFormula: Formula; + let instanceFormula: Formula; + beforeAll(() => { + for (let i = 0; i < args.length; i++) { + formulaArgs.push(testValueFormulas[args[i]]); + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + staticFormula = Formula[name](...formulaArgs); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + instanceFormula = formulaArgs[0][name](...formulaArgs.slice(1)); + + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expectedEvaluation = Decimal[name](...args); + } catch { + // If this is an invalid Decimal operation, then ignore this test case + return; + } + }); + + test("Static formula is not marked as having a variable", () => + expect(staticFormula.hasVariable).toBe(false)); + test("Static function evaluates correctly", () => + expectedEvaluation != null && + expect(staticFormula.evaluate()).toSatisfy(compare_tolerance(expectedEvaluation))); + test("Static function invertible", () => + expect(staticFormula.invertible).toBe(invertible)); + if (invertible) { + test("Static function inverts correctly", () => + expectedEvaluation != null && + !Decimal.isNaN(expectedEvaluation) && + expect(staticFormula.invert(expectedEvaluation)).toSatisfy( + compare_tolerance(value) + )); + } + + // Do those tests again but for non-static methods + test("Instance formula is not marked as having a variable", () => + expect(instanceFormula.hasVariable).toBe(false)); + test("Instance function evaluates correctly", () => + expectedEvaluation != null && + expect(instanceFormula.evaluate()).toSatisfy( + compare_tolerance(expectedEvaluation) + )); + test("Instance function invertible", () => + expect(instanceFormula.invertible).toBe(invertible)); + if (invertible) { + test("Instance function inverts correctly", () => + expectedEvaluation != null && + !Decimal.isNaN(expectedEvaluation) && + expect(instanceFormula.invert(expectedEvaluation)).toSatisfy( + compare_tolerance(value) + )); + } + }); + }); +} + +const testValues = [-2.5, -1, -0.1, 0, 0.1, 1, 2.5] as const; +let testValueFormulas: InvertibleFormula[] = []; + +const invertibleZeroParamFunctionNames = [ + ["neg", "negate", "negated"], + ["recip", "reciprocal", "reciprocate"], + ["log10"], + ["log2"], + ["ln"], + ["pow10"], + ["exp"], + ["sqr"], + ["sqrt"], + ["cube"], + ["cbrt"], + ["lambertw"], + ["ssqrt"], + ["sin"], + ["cos"], + ["tan"], + ["asin"], + ["acos"], + ["atan"], + ["sinh"], + ["cosh"], + ["tanh"], + ["asinh"], + ["acosh"], + ["atanh"] +] as const; + +const nonInvertibleZeroParamFunctionNames = [ + ["abs"], + ["sign", "sgn"], + ["round"], + ["floor"], + ["ceil"], + ["trunc"], + ["pLog10"], + ["absLog10"], + ["factorial"], + ["gamma"], + ["lngamma"] +] as const; + +const invertibleOneParamFunctionNames = [ + ["add", "plus"], + ["sub", "subtract", "minus"], + ["mul", "multiply", "times"], + ["div", "divide"], + ["log", "logarithm"], + ["pow"], + ["root"], + ["slog"] +] as const; + +const nonInvertibleOneParamFunctionNames = [ + ["max"], + ["min"], + ["maxabs"], + ["minabs"], + ["clampMin"], + ["clampMax"], + ["layeradd10"] +] as const; + +const invertibleTwoParamFunctionNames = [["tetrate"]] as const; + +const nonInvertibleTwoParamFunctionNames = [ + ["clamp"], + ["iteratedexp"], + ["iteratedlog"], + ["layeradd"], + ["pentate"] +] as const; + +describe("Creating Formulas", () => { + beforeAll(() => { + testValueFormulas = testValues.map(v => Formula.constant(v)); + }); + + describe("Constants", () => { + testConstant("number", () => Formula.constant(10)); + testConstant("string", () => Formula.constant("10")); + testConstant("formula", () => Formula.constant(Formula.constant(10))); + testConstant("decimal", () => Formula.constant(new Decimal("1e400")), "1e400"); + testConstant("ref", () => Formula.constant(ref(10))); + }); + + describe("Invertible 0-param", () => { + invertibleZeroParamFunctionNames.forEach(names => { + for (let i = 0; i < testValues.length; i++) { + testFormula(names, [i] as const); + } + }); + }); + describe("Non-Invertible 0-param", () => { + nonInvertibleZeroParamFunctionNames.forEach(names => { + for (let i = 0; i < testValues.length; i++) { + testFormula(names, [i] as const, false); + } + }); + }); + describe("Invertible 1-param", () => { + invertibleOneParamFunctionNames.forEach(names => { + for (let i = 0; i < testValues.length; i++) { + for (let j = 0; j < testValues.length; j++) { + testFormula(names, [i, j] as const); + } + } + }); + }); + describe("Non-Invertible 1-param", () => { + nonInvertibleOneParamFunctionNames.forEach(names => { + for (let i = 0; i < testValues.length; i++) { + for (let j = 0; j < testValues.length; j++) { + testFormula(names, [i, j] as const, false); + } + } + }); + }); + describe("Invertible 2-param", () => { + invertibleTwoParamFunctionNames.forEach(names => { + for (let i = 0; i < testValues.length; i++) { + for (let j = 0; j < testValues.length; j++) { + for (let k = 0; k < testValues.length; k++) { + testFormula(names, [i, j, k] as const); + } + } + } + }); + }); + describe("Non-Invertible 2-param", () => { + nonInvertibleTwoParamFunctionNames.forEach(names => { + for (let i = 0; i < testValues.length; i++) { + for (let j = 0; j < testValues.length; j++) { + for (let k = 0; k < testValues.length; k++) { + testFormula(names, [i, j, k] as const, false); + } + } + } + }); + }); + + describe("Variables", () => { + let variable: Formula; + let constant: Formula; + beforeAll(() => { + variable = Formula.variable(10); + constant = Formula.constant(10); + }); + + test("Created variable is marked as a variable", () => + expect(variable.hasVariable).toBe(true)); + test("Evaluate() returns variable's value", () => + expect(variable.evaluate()).toSatisfy(compare_tolerance(10))); + test("Invert() is pass-through", () => + expect(variable.invert(100)).toSatisfy(compare_tolerance(100))); + + test("Nested variable is marked as having a variable", () => + expect(variable.add(10).div(3).pow(2).hasVariable).toBe(false)); + test("Nested non-variable is marked as not having a variable", () => + expect(constant.add(10).div(3).pow(2).hasVariable).toBe(false)); + + describe("Invertible Formulas correctly calculate when they contain a variable", () => { + function checkFormula(formula: Formula, expectedBool = true) { + expect(formula.invertible).toBe(expectedBool); + expect(formula.hasVariable).toBe(expectedBool); + } + invertibleZeroParamFunctionNames.flat().forEach(name => { + describe(name, () => { + test(`${name}(var) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](variable))); + }); + }); + invertibleOneParamFunctionNames.flat().forEach(name => { + describe(name, () => { + test(`${name}(var, const) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](variable, constant))); + test(`${name}(const, var) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](constant, variable))); + test(`${name}(var, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, variable), false)); + }); + }); + invertibleTwoParamFunctionNames.flat().forEach(name => { + describe(name, () => { + test(`${name}(var, const, const) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](variable, constant, constant))); + test(`${name}(const, var, const) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](constant, variable, constant))); + test(`${name}(const, const, var) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](constant, constant, variable))); + test(`${name}(var, var, const) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, variable, constant), false)); + test(`${name}(var, const, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, constant, variable), false)); + test(`${name}(const, var, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](constant, variable, variable), false)); + test(`${name}(var, var, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, variable, variable), false)); + }); + }); + }); + + describe("Non-Invertible Formulas never marked as having a variable", () => { + function checkFormula(formula: Formula) { + expect(formula.invertible).toBe(false); + expect(formula.hasVariable).toBe(false); + } + nonInvertibleZeroParamFunctionNames.flat().forEach(name => { + describe(name, () => { + test(`${name}(var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable))); + }); + }); + nonInvertibleOneParamFunctionNames.flat().forEach(name => { + describe(name, () => { + test(`${name}(var, const) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, constant))); + test(`${name}(const, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](constant, variable))); + test(`${name}(var, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, variable))); + }); + }); + nonInvertibleTwoParamFunctionNames.flat().forEach(name => { + describe(name, () => { + test(`${name}(var, const, const) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](variable, constant, constant))); + test(`${name}(const, var, const) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](constant, variable, constant))); + test(`${name}(const, const, var) is marked as invertible and having a variable`, () => + checkFormula(Formula[name](constant, constant, variable))); + test(`${name}(var, var, const) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, variable, constant))); + test(`${name}(var, const, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, constant, variable))); + test(`${name}(const, var, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](constant, variable, variable))); + test(`${name}(var, var, var) is marked as not invertible and not having a variable`, () => + checkFormula(Formula[name](variable, variable, variable))); + }); + }); + }); + }); +});