monogate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/package.json +42 -0
- package/src/complex.js +212 -0
- package/src/complex.test.js +206 -0
- package/src/complex_eml.js +562 -0
- package/src/complex_eml.test.js +381 -0
- package/src/index.js +169 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Complex } from "./complex.js";
|
|
3
|
+
import {
|
|
4
|
+
op_c,
|
|
5
|
+
E_C, ZERO_C, NEG_ONE_C,
|
|
6
|
+
exp_c, ln_c,
|
|
7
|
+
sub_c, neg_c, add_c, mul_c, recip_c, div_c, pow_c,
|
|
8
|
+
NEG_I_PI_C, I_HALF_PI_C, I_CONST, PI_C,
|
|
9
|
+
eul_c, sin_eml, cos_eml,
|
|
10
|
+
IDENTITIES_C, BRANCH_CUT_NOTES,
|
|
11
|
+
} from "./complex_eml.js";
|
|
12
|
+
|
|
13
|
+
const TOL = 1e-10; // EML chain results
|
|
14
|
+
const DTOL = 1e-12; // direct Complex arithmetic reference comparisons
|
|
15
|
+
|
|
16
|
+
const near = (a, b, tol = TOL) => Math.abs(a - b) <= tol;
|
|
17
|
+
const nearC = (z, re, im, tol = TOL) =>
|
|
18
|
+
Math.abs(z.re - re) <= tol && Math.abs(z.im - im) <= tol;
|
|
19
|
+
|
|
20
|
+
// ─── Core constants ────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
describe("complex EML — constants", () => {
|
|
23
|
+
it("E_C ≈ e", () => {
|
|
24
|
+
expect(near(E_C.re, Math.E)).toBe(true);
|
|
25
|
+
expect(near(E_C.im, 0)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("ZERO_C ≈ 0", () => {
|
|
29
|
+
expect(near(ZERO_C.re, 0)).toBe(true);
|
|
30
|
+
expect(near(ZERO_C.im, 0)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("NEG_ONE_C ≈ −1", () => {
|
|
34
|
+
expect(near(NEG_ONE_C.re, -1)).toBe(true);
|
|
35
|
+
expect(near(NEG_ONE_C.im, 0)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ─── exp_c and ln_c ───────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe("exp_c", () => {
|
|
42
|
+
it("exp_c(0) = 1", () => {
|
|
43
|
+
expect(nearC(exp_c(Complex.of(0)), 1, 0)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("exp_c(1) = e", () => {
|
|
47
|
+
expect(near(exp_c(Complex.of(1)).re, Math.E)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("exp_c(iπ) ≈ −1 (Euler's identity)", () => {
|
|
51
|
+
const r = exp_c(Complex.of(0, Math.PI));
|
|
52
|
+
expect(near(r.re, -1)).toBe(true);
|
|
53
|
+
expect(near(r.im, 0)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("ln_c (EML formula)", () => {
|
|
58
|
+
it("ln_c(e) = 1", () => {
|
|
59
|
+
const r = ln_c(Complex.of(Math.E));
|
|
60
|
+
expect(near(r.re, 1)).toBe(true);
|
|
61
|
+
expect(near(r.im, 0)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("ln_c(1) = 0", () => {
|
|
65
|
+
const r = ln_c(Complex.of(1));
|
|
66
|
+
expect(near(r.re, 0, DTOL)).toBe(true);
|
|
67
|
+
expect(near(r.im, 0, DTOL)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("ln_c(−1) ≈ iπ (principal branch, may be −iπ; check |Im| = π)", () => {
|
|
71
|
+
const r = ln_c(Complex.of(-1));
|
|
72
|
+
expect(near(r.re, 0)).toBe(true);
|
|
73
|
+
expect(near(Math.abs(r.im), Math.PI)).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("EML ln_c matches direct .ln() for various inputs (tol 1e-12)", () => {
|
|
77
|
+
const inputs = [
|
|
78
|
+
Complex.of(2),
|
|
79
|
+
Complex.of(0.5),
|
|
80
|
+
Complex.of(1, 1),
|
|
81
|
+
Complex.of(-0.5, 0.5),
|
|
82
|
+
Complex.of(10),
|
|
83
|
+
];
|
|
84
|
+
for (const z of inputs) {
|
|
85
|
+
const eml = ln_c(z);
|
|
86
|
+
const direct = z.ln();
|
|
87
|
+
expect(near(eml.re, direct.re, DTOL)).toBe(true);
|
|
88
|
+
expect(near(eml.im, direct.im, DTOL)).toBe(true);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("exp_c(ln_c(z)) ≈ z for Im(z) ∈ (−π, π)", () => {
|
|
93
|
+
const zs = [Complex.of(2, 1), Complex.of(0.5, -0.7), Complex.of(-1, 0.5)];
|
|
94
|
+
for (const z of zs) {
|
|
95
|
+
const r = exp_c(ln_c(z));
|
|
96
|
+
expect(near(r.re, z.re)).toBe(true);
|
|
97
|
+
expect(near(r.im, z.im)).toBe(true);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ─── Arithmetic ───────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
describe("sub_c", () => {
|
|
105
|
+
it("sub_c(3, 1) ≈ 2", () => {
|
|
106
|
+
expect(nearC(sub_c(Complex.of(3), Complex.of(1)), 2, 0)).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("sub_c(3+4i, 1+2i) ≈ 2+2i", () => {
|
|
110
|
+
const r = sub_c(Complex.of(3, 4), Complex.of(1, 2));
|
|
111
|
+
expect(nearC(r, 2, 2)).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("neg_c", () => {
|
|
116
|
+
it("neg_c(1) ≈ −1", () => {
|
|
117
|
+
expect(nearC(neg_c(Complex.of(1)), -1, 0)).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("neg_c(−1) ≈ 1", () => {
|
|
121
|
+
expect(nearC(neg_c(Complex.of(-1)), 1, 0)).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("neg_c(3+2i) ≈ −3−2i", () => {
|
|
125
|
+
const r = neg_c(Complex.of(3, 2));
|
|
126
|
+
expect(nearC(r, -3, -2)).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("neg_c(neg_c(z)) ≈ z", () => {
|
|
130
|
+
const z = Complex.of(2, 1);
|
|
131
|
+
const r = neg_c(neg_c(z));
|
|
132
|
+
expect(nearC(r, z.re, z.im)).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("neg_c(z).add(z).isZero()", () => {
|
|
136
|
+
const z = Complex.of(1.5, 0.7);
|
|
137
|
+
const r = neg_c(z).add(z);
|
|
138
|
+
expect(r.isZero(TOL)).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("neg_c throws for |Im(y)| > π", () => {
|
|
142
|
+
expect(() => neg_c(Complex.of(0, Math.PI + 0.1))).toThrow(RangeError);
|
|
143
|
+
expect(() => neg_c(Complex.of(0, -(Math.PI + 0.1)))).toThrow(RangeError);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("neg_c does NOT throw at boundary |Im| = π", () => {
|
|
147
|
+
// At Im = ±π, the formula is fragile but works in IEEE 754 practice
|
|
148
|
+
expect(() => neg_c(Complex.of(0, Math.PI))).not.toThrow();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("add_c", () => {
|
|
153
|
+
it("add_c(2, 3) ≈ 5", () => {
|
|
154
|
+
expect(nearC(add_c(Complex.of(2), Complex.of(3)), 5, 0)).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("add_c(1+2i, 3+1i) ≈ 4+3i", () => {
|
|
158
|
+
const r = add_c(Complex.of(1, 2), Complex.of(3, 1));
|
|
159
|
+
expect(nearC(r, 4, 3)).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("add_c throws when first arg is zero", () => {
|
|
163
|
+
expect(() => add_c(Complex.of(0), Complex.of(1))).toThrow(RangeError);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("mul_c", () => {
|
|
168
|
+
it("mul_c(2, 3) ≈ 6", () => {
|
|
169
|
+
expect(nearC(mul_c(Complex.of(2), Complex.of(3)), 6, 0)).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("mul_c(1+i, 1-i) ≈ 2", () => {
|
|
173
|
+
const r = mul_c(Complex.of(1, 1), Complex.of(1, -1));
|
|
174
|
+
expect(nearC(r, 2, 0)).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("mul_c(z, z.recip()) ≈ 1", () => {
|
|
178
|
+
const z = Complex.of(3, 2);
|
|
179
|
+
const r = mul_c(z, z.recip());
|
|
180
|
+
expect(nearC(r, 1, 0)).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("recip_c", () => {
|
|
185
|
+
it("recip_c(2) ≈ 0.5", () => {
|
|
186
|
+
expect(nearC(recip_c(Complex.of(2)), 0.5, 0)).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("recip_c(i) ≈ −i", () => {
|
|
190
|
+
const r = recip_c(Complex.of(0, 1));
|
|
191
|
+
expect(nearC(r, 0, -1)).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("mul_c(z, recip_c(z)) ≈ 1", () => {
|
|
195
|
+
const z = Complex.of(2, 3);
|
|
196
|
+
const r = mul_c(z, recip_c(z));
|
|
197
|
+
expect(nearC(r, 1, 0)).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("div_c", () => {
|
|
202
|
+
it("div_c(6, 2) ≈ 3", () => {
|
|
203
|
+
expect(nearC(div_c(Complex.of(6), Complex.of(2)), 3, 0)).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("div_c(z, z) ≈ 1", () => {
|
|
207
|
+
const z = Complex.of(2, 3);
|
|
208
|
+
expect(nearC(div_c(z, z), 1, 0)).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("pow_c", () => {
|
|
213
|
+
it("pow_c(2, 3) ≈ 8", () => {
|
|
214
|
+
expect(nearC(pow_c(Complex.of(2), Complex.of(3)), 8, 0)).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("pow_c(i, 2) ≈ −1 (i² = −1)", () => {
|
|
218
|
+
const r = pow_c(Complex.of(0, 1), Complex.of(2));
|
|
219
|
+
expect(nearC(r, -1, 0)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// ─── The escape from reals ────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
describe("NEG_I_PI_C — escape from reals", () => {
|
|
226
|
+
it("Re(NEG_I_PI_C) ≈ 0", () => {
|
|
227
|
+
expect(near(NEG_I_PI_C.re, 0)).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("|Im(NEG_I_PI_C)| ≈ π", () => {
|
|
231
|
+
expect(near(Math.abs(NEG_I_PI_C.im), Math.PI)).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("NEG_I_PI_C is not real", () => {
|
|
235
|
+
expect(NEG_I_PI_C.isReal(TOL)).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ─── i, π ────────────────────────────────────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
describe("I_CONST (i)", () => {
|
|
242
|
+
it("Re(i) ≈ 0", () => expect(near(I_CONST.re, 0)).toBe(true));
|
|
243
|
+
it("Im(i) ≈ 1", () => expect(near(I_CONST.im, 1)).toBe(true));
|
|
244
|
+
|
|
245
|
+
it("i² ≈ −1", () => {
|
|
246
|
+
const r = mul_c(I_CONST, I_CONST);
|
|
247
|
+
expect(near(r.re, -1)).toBe(true);
|
|
248
|
+
expect(near(r.im, 0)).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("i⁴ ≈ 1", () => {
|
|
252
|
+
const i2 = mul_c(I_CONST, I_CONST);
|
|
253
|
+
const i4 = mul_c(i2, i2);
|
|
254
|
+
expect(near(i4.re, 1)).toBe(true);
|
|
255
|
+
expect(near(i4.im, 0)).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe("PI_C (π)", () => {
|
|
260
|
+
it("Im(π) ≈ 0", () => expect(near(PI_C.im, 0)).toBe(true));
|
|
261
|
+
it("Re(π) ≈ Math.PI", () => expect(near(PI_C.re, Math.PI)).toBe(true));
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ─── Euler's formula ──────────────────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
describe("eul_c — Euler's formula", () => {
|
|
267
|
+
it("eul_c(0) = 1", () => {
|
|
268
|
+
expect(nearC(eul_c(Complex.of(0)), 1, 0)).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("eul_c(π) ≈ −1 (Euler's identity)", () => {
|
|
272
|
+
const r = eul_c(Complex.of(Math.PI));
|
|
273
|
+
expect(near(r.re, -1)).toBe(true);
|
|
274
|
+
expect(near(r.im, 0)).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("eul_c(π/2) ≈ i", () => {
|
|
278
|
+
const r = eul_c(Complex.of(Math.PI / 2));
|
|
279
|
+
expect(near(r.re, 0)).toBe(true);
|
|
280
|
+
expect(near(r.im, 1)).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("eul_c(−π/2) ≈ −i", () => {
|
|
284
|
+
const r = eul_c(Complex.of(-Math.PI / 2));
|
|
285
|
+
expect(near(r.re, 0)).toBe(true);
|
|
286
|
+
expect(near(r.im, -1)).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("|eul_c(x)| = 1 for real x", () => {
|
|
290
|
+
for (const x of [0, 1, 2, -1, Math.PI, -Math.PI / 3]) {
|
|
291
|
+
expect(near(eul_c(Complex.of(x)).abs(), 1)).toBe(true);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// ─── sin_eml and cos_eml ──────────────────────────────────────────────────────
|
|
297
|
+
|
|
298
|
+
describe("sin_eml / cos_eml", () => {
|
|
299
|
+
const cases = [
|
|
300
|
+
[0, 0, 1],
|
|
301
|
+
[Math.PI / 6, 0.5, Math.sqrt(3) / 2],
|
|
302
|
+
[Math.PI / 4, Math.SQRT1_2, Math.SQRT1_2],
|
|
303
|
+
[Math.PI / 3, Math.sqrt(3) / 2, 0.5],
|
|
304
|
+
[Math.PI / 2, 1, 0],
|
|
305
|
+
[Math.PI, 0, -1],
|
|
306
|
+
[-1, -Math.sin(1), Math.cos(1)],
|
|
307
|
+
[2, Math.sin(2), Math.cos(2)],
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
for (const [x, sinX, cosX] of cases) {
|
|
311
|
+
it(`sin_eml(${x.toFixed(4)}) ≈ ${sinX.toFixed(4)}`, () => {
|
|
312
|
+
expect(near(sin_eml(x), sinX)).toBe(true);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it(`cos_eml(${x.toFixed(4)}) ≈ ${cosX.toFixed(4)}`, () => {
|
|
316
|
+
expect(near(cos_eml(x), cosX)).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
it("sin² + cos² = 1 for x ∈ {0.3, 1, 2, π}", () => {
|
|
321
|
+
for (const x of [0.3, 1, 2, Math.PI]) {
|
|
322
|
+
const s = sin_eml(x);
|
|
323
|
+
const c = cos_eml(x);
|
|
324
|
+
expect(near(s * s + c * c, 1)).toBe(true);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// ─── IDENTITIES_C completeness ────────────────────────────────────────────────
|
|
330
|
+
|
|
331
|
+
describe("IDENTITIES_C", () => {
|
|
332
|
+
it("has at least 15 entries", () => {
|
|
333
|
+
expect(IDENTITIES_C.length).toBeGreaterThanOrEqual(15);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("every entry has name, emlForm, domain, terminal, status", () => {
|
|
337
|
+
for (const id of IDENTITIES_C) {
|
|
338
|
+
expect(id).toHaveProperty("name");
|
|
339
|
+
expect(id).toHaveProperty("emlForm");
|
|
340
|
+
expect(id).toHaveProperty("domain");
|
|
341
|
+
expect(id).toHaveProperty("terminal");
|
|
342
|
+
expect(id).toHaveProperty("status");
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("NEG_I_PI entry uses terminal {1}", () => {
|
|
347
|
+
const entry = IDENTITIES_C.find((e) => e.name === "−iπ");
|
|
348
|
+
expect(entry).toBeDefined();
|
|
349
|
+
expect(entry.terminal).toBe("{1}");
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("i and π entries use terminal {1, 2}", () => {
|
|
353
|
+
const iEntry = IDENTITIES_C.find((e) => e.name === "i");
|
|
354
|
+
const piEntry = IDENTITIES_C.find((e) => e.name === "π");
|
|
355
|
+
expect(iEntry?.terminal).toBe("{1, 2}");
|
|
356
|
+
expect(piEntry?.terminal).toBe("{1, 2}");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("sin and cos are marked as meta-operations", () => {
|
|
360
|
+
const sin = IDENTITIES_C.find((e) => e.name === "sin(x)");
|
|
361
|
+
const cos = IDENTITIES_C.find((e) => e.name === "cos(x)");
|
|
362
|
+
expect(sin?.status).toBe("meta-operation");
|
|
363
|
+
expect(cos?.status).toBe("meta-operation");
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("BRANCH_CUT_NOTES", () => {
|
|
368
|
+
it("is an array with at least 4 entries", () => {
|
|
369
|
+
expect(Array.isArray(BRANCH_CUT_NOTES)).toBe(true);
|
|
370
|
+
expect(BRANCH_CUT_NOTES.length).toBeGreaterThanOrEqual(4);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("every entry has fn, failsWhen, symptom, action", () => {
|
|
374
|
+
for (const note of BRANCH_CUT_NOTES) {
|
|
375
|
+
expect(note).toHaveProperty("fn");
|
|
376
|
+
expect(note).toHaveProperty("failsWhen");
|
|
377
|
+
expect(note).toHaveProperty("symptom");
|
|
378
|
+
expect(note).toHaveProperty("action");
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
});
|
package/src/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* monogate — Exp-Minus-Log arithmetic
|
|
3
|
+
*
|
|
4
|
+
* A single binary operator from which all elementary functions
|
|
5
|
+
* can be constructed, using only the constant 1 as a terminal node.
|
|
6
|
+
*
|
|
7
|
+
* eml(x, y) = exp(x) − ln(y)
|
|
8
|
+
* Grammar: S → 1 | eml(S, S)
|
|
9
|
+
*
|
|
10
|
+
* Reference:
|
|
11
|
+
* "All elementary functions from a single operator"
|
|
12
|
+
* Andrzej Odrzywołek, Jagiellonian University, 2026
|
|
13
|
+
* arXiv:2603.21852v2 [cs.SC] · CC BY 4.0
|
|
14
|
+
*
|
|
15
|
+
* @module monogate
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ─── Core operator ────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The EML operator: eml(x, y) = exp(x) − ln(y)
|
|
22
|
+
*
|
|
23
|
+
* @param {number} x
|
|
24
|
+
* @param {number} y must be > 0 (argument of ln)
|
|
25
|
+
* @returns {number}
|
|
26
|
+
*/
|
|
27
|
+
export const op = (x, y) => Math.exp(x) - Math.log(y);
|
|
28
|
+
|
|
29
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/** e = eml(1,1). Proof: exp(1)−ln(1) = e−0 = e. Nodes:1 Depth:1 */
|
|
32
|
+
export const E = op(1, 1);
|
|
33
|
+
|
|
34
|
+
/** 0 = eml(1, eml(eml(1,1),1)). Proof: eml(1,1)=e → eml(e,1)=eᵉ → eml(1,eᵉ)=e−e=0. Nodes:3 Depth:3 */
|
|
35
|
+
export const ZERO = op(1, op(op(1, 1), 1));
|
|
36
|
+
|
|
37
|
+
/** −1 = eml(ZERO, eml(2,1)). Proof: exp(0)−ln(e²)=1−2=−1. Nodes:5 Depth:4 */
|
|
38
|
+
export const NEG_ONE = op(ZERO, op(2, 1));
|
|
39
|
+
|
|
40
|
+
// ─── Elementary functions ─────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* eˣ = eml(x, 1)
|
|
44
|
+
* Proof: exp(x)−ln(1) = exp(x). ∎ Nodes:1 Depth:1
|
|
45
|
+
*
|
|
46
|
+
* @param {number} x
|
|
47
|
+
* @returns {number}
|
|
48
|
+
*/
|
|
49
|
+
export const exp = (x) => op(x, 1);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* ln(x) = eml(1, eml(eml(1,x), 1))
|
|
53
|
+
* Proof: let s=e−ln(x); eml(s,1)=eˢ=eᵉ/x; eml(1,eᵉ/x)=e−(e−ln(x))=ln(x). ∎
|
|
54
|
+
* Nodes:3 Depth:3 Domain: x > 0
|
|
55
|
+
*
|
|
56
|
+
* @param {number} x must be > 0
|
|
57
|
+
* @returns {number}
|
|
58
|
+
*/
|
|
59
|
+
export const ln = (x) => op(1, op(op(1, x), 1));
|
|
60
|
+
|
|
61
|
+
// ─── Arithmetic ───────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* x − y = eml(ln(x), exp(y))
|
|
65
|
+
* Proof: exp(ln(x))−ln(exp(y)) = x−y. ∎ Nodes:5 Depth:4 Domain: x > 0
|
|
66
|
+
*
|
|
67
|
+
* @param {number} x must be > 0
|
|
68
|
+
* @param {number} y
|
|
69
|
+
* @returns {number}
|
|
70
|
+
*/
|
|
71
|
+
export const sub = (x, y) => op(ln(x), exp(y));
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* −y (negation — two-regime construction for numerical stability)
|
|
75
|
+
*
|
|
76
|
+
* REGIME A — y ≤ 0 (tower formula):
|
|
77
|
+
* Let α=eml(y,1)=eʸ. A=eml(α,α)=exp(eʸ)−y. B=eml(α,1)=exp(eʸ).
|
|
78
|
+
* sub(A,B) = A−B = −y. ∎ Stable: exp(eʸ) finite for all y ≤ 0.
|
|
79
|
+
*
|
|
80
|
+
* REGIME B — y > 0 (shift formula, stable to y < 708):
|
|
81
|
+
* y+1 = sub(y, NEG_ONE).
|
|
82
|
+
* −y = eml(ZERO, eml(y+1, 1)) = 1−ln(exp(y+1)) = 1−(y+1) = −y. ∎
|
|
83
|
+
*
|
|
84
|
+
* Valid for all y ∈ ℝ. Overflows IEEE 754 doubles only for y > 707.
|
|
85
|
+
*
|
|
86
|
+
* @param {number} y
|
|
87
|
+
* @returns {number}
|
|
88
|
+
*/
|
|
89
|
+
export const neg = (y) => {
|
|
90
|
+
if (y <= 0) {
|
|
91
|
+
const a = op(y, 1); // eʸ
|
|
92
|
+
return op(ln(op(a, a)), op(op(a, 1), 1)); // sub(A, B)
|
|
93
|
+
}
|
|
94
|
+
const y1 = op(ln(y), op(NEG_ONE, 1)); // y + 1 [sub(y, −1)]
|
|
95
|
+
return op(ZERO, op(y1, 1)); // 1 − (y+1) = −y
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* x + y
|
|
100
|
+
* Proof: exp(ln(x))−ln(exp(−y)) = x−(−y) = x+y. ∎
|
|
101
|
+
* Generalised for any sign via commutativity and double-negation.
|
|
102
|
+
*
|
|
103
|
+
* @param {number} x
|
|
104
|
+
* @param {number} y
|
|
105
|
+
* @returns {number}
|
|
106
|
+
*/
|
|
107
|
+
export const add = (x, y) => {
|
|
108
|
+
if (x > 0) return op(ln(x), op(neg(y), 1));
|
|
109
|
+
if (y > 0) return op(ln(y), op(neg(x), 1));
|
|
110
|
+
return neg(op(ln(neg(x)), op(neg(neg(y)), 1)));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* x × y = exp(ln(x) + ln(y))
|
|
115
|
+
* Proof: exp(ln(x)+ln(y)) = exp(ln(xy)) = xy. ∎ Domain: x,y > 0
|
|
116
|
+
*
|
|
117
|
+
* @param {number} x must be > 0
|
|
118
|
+
* @param {number} y must be > 0
|
|
119
|
+
* @returns {number}
|
|
120
|
+
*/
|
|
121
|
+
export const mul = (x, y) => op(add(ln(x), ln(y)), 1);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* x / y = exp(ln(x) − ln(y))
|
|
125
|
+
* Proof: exp(ln(x)−ln(y)) = exp(ln(x/y)) = x/y. ∎ Domain: x,y > 0
|
|
126
|
+
*
|
|
127
|
+
* @param {number} x must be > 0
|
|
128
|
+
* @param {number} y must be > 0
|
|
129
|
+
* @returns {number}
|
|
130
|
+
*/
|
|
131
|
+
export const div = (x, y) => op(add(ln(x), neg(ln(y))), 1);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* xⁿ = exp(n · ln(x))
|
|
135
|
+
* Proof: exp(n·ln(x)) = exp(ln(xⁿ)) = xⁿ. ∎ Domain: x > 0, n ∈ ℝ
|
|
136
|
+
*
|
|
137
|
+
* @param {number} x must be > 0
|
|
138
|
+
* @param {number} n
|
|
139
|
+
* @returns {number}
|
|
140
|
+
*/
|
|
141
|
+
export const pow = (x, n) => op(mul(n, ln(x)), 1);
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 1/x = exp(−ln(x))
|
|
145
|
+
* Proof: exp(−ln(x)) = x⁻¹. ∎ Domain: x > 0
|
|
146
|
+
*
|
|
147
|
+
* @param {number} x must be > 0
|
|
148
|
+
* @returns {number}
|
|
149
|
+
*/
|
|
150
|
+
export const recip = (x) => op(neg(ln(x)), 1);
|
|
151
|
+
|
|
152
|
+
// ─── Identity table ───────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/** Complexity table: each identity ranked by EML tree node count and depth. */
|
|
155
|
+
export const IDENTITIES = [
|
|
156
|
+
{ name: "eˣ", emlForm: "eml(x,1)", nodes: 1, depth: 1, status: "verified" },
|
|
157
|
+
{ name: "ln x",emlForm: "eml(1,eml(eml(1,x),1))", nodes: 3, depth: 3, status: "verified" },
|
|
158
|
+
{ name: "e", emlForm: "eml(1,1)", nodes: 1, depth: 1, status: "verified" },
|
|
159
|
+
{ name: "0", emlForm: "eml(1,eml(eml(1,1),1))", nodes: 3, depth: 3, status: "verified" },
|
|
160
|
+
{ name: "x−y", emlForm: "eml(ln(x),exp(y))", nodes: 5, depth: 4, status: "verified" },
|
|
161
|
+
{ name: "−y", emlForm: "two-regime (see source)", nodes: 9, depth: 5, status: "proven" },
|
|
162
|
+
{ name: "x+y", emlForm: "eml(ln(x),eml(neg(y),1))", nodes: 11, depth: 6, status: "proven" },
|
|
163
|
+
{ name: "x×y", emlForm: "eml(add(ln(x),ln(y)),1)", nodes: 13, depth: 7, status: "proven" },
|
|
164
|
+
{ name: "x/y", emlForm: "eml(add(ln(x),neg(ln(y))),1)", nodes: 15, depth: 8, status: "proven" },
|
|
165
|
+
{ name: "xⁿ", emlForm: "eml(mul(n,ln(x)),1)", nodes: 15, depth: 8, status: "proven" },
|
|
166
|
+
{ name: "1/x", emlForm: "eml(neg(ln(x)),1)", nodes: 5, depth: 4, status: "verified" },
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
export default { op, exp, ln, E, ZERO, NEG_ONE, sub, neg, add, mul, div, pow, recip, IDENTITIES };
|