openalgo-script 0.6.0 → 0.7.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.
Files changed (110) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +12 -12
  3. package/dist/core/engine/channels.js +2 -2
  4. package/dist/core/engine/channels.js.map +1 -1
  5. package/dist/core/engine/engine.js +2 -3
  6. package/dist/core/engine/engine.js.map +1 -1
  7. package/dist/core/engine/library/maths.js +2 -2
  8. package/dist/core/engine/library/maths.js.map +1 -1
  9. package/dist/core/engine/machine.js +5 -5
  10. package/dist/core/engine/machine.js.map +1 -1
  11. package/dist/core/engine/memory.js +2 -2
  12. package/dist/core/engine/memory.js.map +1 -1
  13. package/dist/core/engine/registers.js +2 -2
  14. package/dist/core/engine/registers.js.map +1 -1
  15. package/dist/core/engine/values/index.d.ts +1 -1
  16. package/dist/core/engine/values/index.d.ts.map +1 -1
  17. package/dist/core/engine/values/index.js +1 -1
  18. package/dist/core/engine/values/index.js.map +1 -1
  19. package/dist/core/engine/values/value.d.ts +2 -0
  20. package/dist/core/engine/values/value.d.ts.map +1 -1
  21. package/dist/core/engine/values/value.js +4 -0
  22. package/dist/core/engine/values/value.js.map +1 -1
  23. package/dist/core/stdlib/averages/exponential.d.ts.map +1 -1
  24. package/dist/core/stdlib/averages/exponential.js +3 -6
  25. package/dist/core/stdlib/averages/exponential.js.map +1 -1
  26. package/dist/core/stdlib/averages/gaussian.d.ts +15 -0
  27. package/dist/core/stdlib/averages/gaussian.d.ts.map +1 -0
  28. package/dist/core/stdlib/averages/gaussian.js +55 -0
  29. package/dist/core/stdlib/averages/gaussian.js.map +1 -0
  30. package/dist/core/stdlib/averages/shaped.d.ts +2 -6
  31. package/dist/core/stdlib/averages/shaped.d.ts.map +1 -1
  32. package/dist/core/stdlib/averages/shaped.js +8 -22
  33. package/dist/core/stdlib/averages/shaped.js.map +1 -1
  34. package/dist/core/stdlib/maths/arithmetic.d.ts.map +1 -1
  35. package/dist/core/stdlib/maths/arithmetic.js +2 -1
  36. package/dist/core/stdlib/maths/arithmetic.js.map +1 -1
  37. package/dist/core/stdlib/maths/elementary.d.ts +3 -20
  38. package/dist/core/stdlib/maths/elementary.d.ts.map +1 -1
  39. package/dist/core/stdlib/maths/elementary.js +17 -13
  40. package/dist/core/stdlib/maths/elementary.js.map +1 -1
  41. package/dist/core/stdlib/maths/hypot.d.ts +8 -0
  42. package/dist/core/stdlib/maths/hypot.d.ts.map +1 -0
  43. package/dist/core/stdlib/maths/hypot.js +68 -0
  44. package/dist/core/stdlib/maths/hypot.js.map +1 -0
  45. package/dist/core/stdlib/maths/power.d.ts +3 -0
  46. package/dist/core/stdlib/maths/power.d.ts.map +1 -0
  47. package/dist/core/stdlib/maths/power.js +83 -0
  48. package/dist/core/stdlib/maths/power.js.map +1 -0
  49. package/dist/core/stdlib/maths/transcendental.d.ts +15 -0
  50. package/dist/core/stdlib/maths/transcendental.d.ts.map +1 -0
  51. package/dist/core/stdlib/maths/transcendental.js +185 -0
  52. package/dist/core/stdlib/maths/transcendental.js.map +1 -0
  53. package/dist/core/stdlib/maths/trigonometric.d.ts +4 -0
  54. package/dist/core/stdlib/maths/trigonometric.d.ts.map +1 -0
  55. package/dist/core/stdlib/maths/trigonometric.js +196 -0
  56. package/dist/core/stdlib/maths/trigonometric.js.map +1 -0
  57. package/dist/core/stdlib/series/conditions.d.ts +2 -4
  58. package/dist/core/stdlib/series/conditions.d.ts.map +1 -1
  59. package/dist/core/stdlib/series/conditions.js +9 -13
  60. package/dist/core/stdlib/series/conditions.js.map +1 -1
  61. package/dist/core/stdlib/series/statistics.d.ts.map +1 -1
  62. package/dist/core/stdlib/series/statistics.js +2 -0
  63. package/dist/core/stdlib/series/statistics.js.map +1 -1
  64. package/dist/core/stdlib/values/history.d.ts +26 -0
  65. package/dist/core/stdlib/values/history.d.ts.map +1 -0
  66. package/dist/core/stdlib/values/history.js +52 -0
  67. package/dist/core/stdlib/values/history.js.map +1 -0
  68. package/dist/core/stdlib/values/index.d.ts +1 -0
  69. package/dist/core/stdlib/values/index.d.ts.map +1 -1
  70. package/dist/core/stdlib/values/index.js +1 -0
  71. package/dist/core/stdlib/values/index.js.map +1 -1
  72. package/dist/core/stdlib/values/lookback.d.ts +4 -1
  73. package/dist/core/stdlib/values/lookback.d.ts.map +1 -1
  74. package/dist/core/stdlib/values/lookback.js +48 -45
  75. package/dist/core/stdlib/values/lookback.js.map +1 -1
  76. package/dist/core/stdlib/values/region.d.ts +6 -5
  77. package/dist/core/stdlib/values/region.d.ts.map +1 -1
  78. package/dist/core/stdlib/values/region.js +3 -3
  79. package/dist/core/stdlib/values/region.js.map +1 -1
  80. package/dist/core/stdlib/volatility/measures.d.ts.map +1 -1
  81. package/dist/core/stdlib/volatility/measures.js +6 -4
  82. package/dist/core/stdlib/volatility/measures.js.map +1 -1
  83. package/dist/core/version/version.generated.d.ts +1 -1
  84. package/dist/core/version/version.generated.js +1 -1
  85. package/package.json +9 -1
  86. package/src/core/engine/channels.ts +2 -2
  87. package/src/core/engine/engine.ts +3 -3
  88. package/src/core/engine/library/maths.ts +2 -2
  89. package/src/core/engine/machine.ts +5 -5
  90. package/src/core/engine/memory.ts +2 -2
  91. package/src/core/engine/registers.ts +2 -2
  92. package/src/core/engine/values/index.ts +1 -0
  93. package/src/core/engine/values/value.ts +5 -0
  94. package/src/core/stdlib/averages/exponential.ts +3 -4
  95. package/src/core/stdlib/averages/gaussian.ts +52 -0
  96. package/src/core/stdlib/averages/shaped.ts +8 -21
  97. package/src/core/stdlib/maths/arithmetic.ts +2 -1
  98. package/src/core/stdlib/maths/elementary.ts +20 -33
  99. package/src/core/stdlib/maths/hypot.ts +63 -0
  100. package/src/core/stdlib/maths/power.ts +57 -0
  101. package/src/core/stdlib/maths/transcendental.ts +160 -0
  102. package/src/core/stdlib/maths/trigonometric.ts +160 -0
  103. package/src/core/stdlib/series/conditions.ts +9 -12
  104. package/src/core/stdlib/series/statistics.ts +1 -0
  105. package/src/core/stdlib/values/history.ts +70 -0
  106. package/src/core/stdlib/values/index.ts +1 -0
  107. package/src/core/stdlib/values/lookback.ts +51 -47
  108. package/src/core/stdlib/values/region.ts +7 -5
  109. package/src/core/stdlib/volatility/measures.ts +6 -4
  110. package/src/core/version/version.generated.ts +1 -1
@@ -27,8 +27,7 @@ export function emaStep(
27
27
  value: Value,
28
28
  len: number | null,
29
29
  ): Value {
30
- if (len === null) return NONE;
31
- const weight = 2 / (len + 1);
30
+ const weight = len === null ? 0 : 2 / (len + 1);
32
31
  const rest = 1 - weight;
33
32
  return smoothed(state, key, len, value, (previous, next) => next * weight + previous * rest);
34
33
  }
@@ -50,8 +49,8 @@ export function rmaStep(
50
49
  value: Value,
51
50
  len: number | null,
52
51
  ): Value {
53
- if (len === null) return NONE;
54
- return smoothed(state, key, len, value, (previous, next) => (previous * (len - 1) + next) / len);
52
+ const span = len ?? 1;
53
+ return smoothed(state, key, len, value, (previous, next) => (previous * (span - 1) + next) / span);
55
54
  }
56
55
 
57
56
  /** `rma(src, len)` as a tail. */
@@ -0,0 +1,52 @@
1
+ /** Pure immutable Gaussian coefficients, outside all checkpoint state. */
2
+ import { exp } from '../maths/index.js';
3
+
4
+ interface Kernel { readonly weights: readonly number[]; readonly norm: number }
5
+ const view = new DataView(new ArrayBuffer(8));
6
+ function keyOf(value: number): string {
7
+ view.setFloat64(0, value);
8
+ return view.getBigUint64(0).toString(16);
9
+ }
10
+
11
+ /** Internal cache with independently bounded entry and coefficient counts. */
12
+ export class GaussianCache {
13
+ private readonly entries = new Map<string, Kernel>();
14
+ private count = 0;
15
+ get size(): number { return this.entries.size; }
16
+ get coefficients(): number { return this.count; }
17
+
18
+ get(length: number, offset: number, sigma: number): Kernel | null {
19
+ if (!Number.isFinite(offset) || !Number.isFinite(sigma) || !(sigma > 0)) return null;
20
+ const key = `${keyOf(length)}/${keyOf(offset)}/${keyOf(sigma)}`;
21
+ const held = this.entries.get(key);
22
+ if (held !== undefined) return held;
23
+ const peak = offset * (length - 1), spread = length / sigma;
24
+ const denominator = (2 * spread) * spread;
25
+ if (denominator === 0) return null;
26
+ const weights: number[] = [];
27
+ let norm = 0;
28
+ for (let position = 0; position < length; position++) {
29
+ const gap = position - peak, exponent = -(gap * gap) / denominator;
30
+ // Internal limiting weight; the public exp still rejects infinities.
31
+ const weight = exponent === -Infinity ? 0 : exp(exponent);
32
+ if (typeof weight !== 'number') return null;
33
+ weights.push(weight); norm += weight;
34
+ }
35
+ if (norm === 0) return null;
36
+ const computed = Object.freeze({ weights: Object.freeze(weights), norm });
37
+ if (length <= 4096) {
38
+ while (this.entries.size >= 8 || this.count + length > 4096) {
39
+ const oldest = this.entries.keys().next().value as string;
40
+ this.count -= (this.entries.get(oldest) as Kernel).weights.length;
41
+ this.entries.delete(oldest);
42
+ }
43
+ this.entries.set(key, computed); this.count += length;
44
+ }
45
+ return computed;
46
+ }
47
+ }
48
+
49
+ const cache = new GaussianCache();
50
+ export function gaussianWeights(length: number, offset: number, sigma: number): Kernel | null {
51
+ return cache.get(length, offset, sigma);
52
+ }
@@ -17,6 +17,7 @@ import {
17
17
  import { roundHalfAway } from '../maths/index.js';
18
18
 
19
19
  import { wmaStep } from './simple.js';
20
+ import { gaussianWeights } from './gaussian.js';
20
21
 
21
22
  /**
22
23
  * `hma(src, len)`: from bar `len + round(sqrt(len)) - 2`.
@@ -37,9 +38,8 @@ export function hmaStep(
37
38
  value: Value,
38
39
  len: number | null,
39
40
  ): Value {
40
- if (len === null) return NONE;
41
- const half = Math.max(1, Math.floor(len / 2));
42
- const outer = Math.max(1, roundHalfAway(Math.sqrt(len)));
41
+ const half = len === null ? null : Math.max(1, Math.floor(len / 2));
42
+ const outer = len === null ? null : Math.max(1, roundHalfAway(Math.sqrt(len)));
43
43
  const near = wmaStep(state, `${key}f`, value, half);
44
44
  const far = wmaStep(state, `${key}s`, value, len);
45
45
  const raw = isPresent(near) && isPresent(far) ? result(2 * near - far) : NONE;
@@ -62,12 +62,8 @@ export function hma(src: Series, len: number): Value[] {
62
62
  * `offset` slides the kernel's peak between lag and smoothness and `sigma` sets
63
63
  * how sharply it falls away.
64
64
  *
65
- * The kernel depends only on the position in the lookback, so it could be built
66
- * once; it is rebuilt each bar instead, because the region 2.11 requires holds
67
- * numbers and queues and not a cached array of weights, and a step that reads
68
- * its own arguments every bar is the only form an engine can drive. The
69
- * accumulation is over the lookback in index order either way, so the number is
70
- * the same one.
65
+ * A bounded pure cache holds immutable coefficients keyed by the current
66
+ * parameters. Source observations remain exclusively in the copyable region.
71
67
  */
72
68
  export function almaStep(
73
69
  state: StateRecord,
@@ -82,18 +78,9 @@ export function almaStep(
82
78
  if (len === null || offset === null || sigma === null) return NONE;
83
79
  if (!lookback.complete()) return NONE;
84
80
  if (!(sigma > 0) || !isLength(len)) return NONE;
85
- const peak = offset * (len - 1);
86
- const spread = len / sigma;
87
- const weights: number[] = [];
88
- let norm = 0;
89
- // Index order: position 0 is the oldest bar in the lookback.
90
- for (let position = 0; position < len; position += 1) {
91
- const gap = position - peak;
92
- const weight = Math.exp(-(gap * gap) / (2 * spread * spread));
93
- weights.push(weight);
94
- norm += weight;
95
- }
96
- if (norm === 0) return NONE;
81
+ const kernel = gaussianWeights(len, offset, sigma);
82
+ if (kernel === null) return NONE;
83
+ const { weights, norm } = kernel;
97
84
  let total = 0;
98
85
  for (let position = 0; position < len; position += 1) {
99
86
  total += (lookback.at(len - 1 - position) as number) * (weights[position] as number);
@@ -65,7 +65,8 @@ export function isNone(x: Value): boolean {
65
65
 
66
66
  /** `x` when present, `fallback` when absent. */
67
67
  export function orElse(x: Value, fallback: Value): Value {
68
- return isPresent(x) ? x : fallback;
68
+ const value = isPresent(x) ? x : fallback;
69
+ return value === 0 ? 0 : value;
69
70
  }
70
71
 
71
72
  /** `toBool(x)`: absence to false, a bool to itself. Numbers are a type error. */
@@ -1,27 +1,14 @@
1
1
  /**
2
- * Square root, the logarithms, the powers and the trigonometry.
3
- *
4
- * **A gap, stated here rather than discovered later.**
5
- * `compiled-program.md` section 8.3 requires that the transcendental functions
6
- * not use the platform's own maths library, because a platform implementation
7
- * is correct to within an ulp or so and differs between platforms in the last
8
- * bit, which this project has declared a release blocker. It names the portable
9
- * reference algorithm as living in the library manifest. **That manifest does
10
- * not exist yet**, so there is nothing to implement against, and the functions
11
- * below call the host until it does. Everything in this file except `sqrt` is
12
- * therefore portable in value but not yet guaranteed in the last bit.
13
- *
14
- * `sqrt` is the exception and is not affected: IEEE-754 requires it to be
15
- * correctly rounded, so every conforming platform returns the same bits.
16
- *
17
- * The consequence is worth stating plainly, because it decides what is safe to
18
- * gate on today. Of the five gate studies, EMA, RSI, MACD, Bollinger Bands and
19
- * Supertrend, four use only addition, subtraction, multiplication and division,
20
- * and Bollinger Bands adds `sqrt`. None of them reaches this file's uncertain
21
- * half. The functions that do are `alma` (exp), `hv` (log) and `chop` (log10).
2
+ * Elementary functions. Hypotenuse and exp/log use sections 20.10.1 and
3
+ * 20.10.2; power and trigonometry use sections 20.10.3 and 20.10.4.
4
+ * Square root uses its correctly rounded host operation.
22
5
  */
23
6
  import type { Value } from '../values/index.js';
24
7
  import { NONE, isPresent, result } from '../values/index.js';
8
+ import { hypotenuse } from './hypot.js';
9
+ import { transcendental } from './transcendental.js';
10
+ import { power } from './power.js';
11
+ import { trigonometric } from './trigonometric.js';
25
12
 
26
13
  /**
27
14
  * `sqrt(x)`: square root, absent below zero.
@@ -37,30 +24,30 @@ export function sqrt(x: Value): Value {
37
24
  /** `pow(x, y)`: absent where the result is not a finite real. */
38
25
  export function pow(x: Value, y: Value): Value {
39
26
  if (!isPresent(x) || !isPresent(y)) return NONE;
40
- return result(Math.pow(x, y));
27
+ return power(x, y);
41
28
  }
42
29
 
43
30
  /** `exp(x)`: e to the power x. */
44
31
  export function exp(x: Value): Value {
45
- return isPresent(x) ? result(Math.exp(x)) : NONE;
32
+ return isPresent(x) ? transcendental('exp', x) : NONE;
46
33
  }
47
34
 
48
35
  /** `log(x)`: natural logarithm, absent at or below zero. */
49
36
  export function log(x: Value): Value {
50
37
  if (!isPresent(x) || x <= 0) return NONE;
51
- return result(Math.log(x));
38
+ return transcendental('log', x);
52
39
  }
53
40
 
54
41
  /** `log10(x)`: base ten logarithm, absent at or below zero. */
55
42
  export function log10(x: Value): Value {
56
43
  if (!isPresent(x) || x <= 0) return NONE;
57
- return result(Math.log10(x));
44
+ return transcendental('log10', x);
58
45
  }
59
46
 
60
47
  /** `math.log2(x)`: base two logarithm, absent at or below zero. */
61
48
  export function log2(x: Value): Value {
62
49
  if (!isPresent(x) || x <= 0) return NONE;
63
- return result(Math.log2(x));
50
+ return transcendental('log2', x);
64
51
  }
65
52
 
66
53
  /** The circle constant. */
@@ -72,7 +59,7 @@ export const E: number = Math.E;
72
59
  /** `math.hypot(x, y)`: `sqrt(x * x + y * y)` without intermediate overflow. */
73
60
  export function hypot(x: Value, y: Value): Value {
74
61
  if (!isPresent(x) || !isPresent(y)) return NONE;
75
- return result(Math.hypot(x, y));
62
+ return hypotenuse(x, y);
76
63
  }
77
64
 
78
65
  /** `math.toDegrees(x)`: radians to degrees. */
@@ -87,38 +74,38 @@ export function toRadians(x: Value): Value {
87
74
 
88
75
  /** `math.sin(x)`: sine of an angle in radians. */
89
76
  export function sin(x: Value): Value {
90
- return isPresent(x) ? result(Math.sin(x)) : NONE;
77
+ return isPresent(x) ? trigonometric('sin', x) : NONE;
91
78
  }
92
79
 
93
80
  /** `math.cos(x)`: cosine. */
94
81
  export function cos(x: Value): Value {
95
- return isPresent(x) ? result(Math.cos(x)) : NONE;
82
+ return isPresent(x) ? trigonometric('cos', x) : NONE;
96
83
  }
97
84
 
98
85
  /** `math.tan(x)`: tangent. */
99
86
  export function tan(x: Value): Value {
100
- return isPresent(x) ? result(Math.tan(x)) : NONE;
87
+ return isPresent(x) ? trigonometric('tan', x) : NONE;
101
88
  }
102
89
 
103
90
  /** `math.asin(x)`: inverse sine, absent outside -1 to 1. */
104
91
  export function asin(x: Value): Value {
105
92
  if (!isPresent(x) || x < -1 || x > 1) return NONE;
106
- return result(Math.asin(x));
93
+ return trigonometric('asin', x);
107
94
  }
108
95
 
109
96
  /** `math.acos(x)`: inverse cosine, absent outside -1 to 1. */
110
97
  export function acos(x: Value): Value {
111
98
  if (!isPresent(x) || x < -1 || x > 1) return NONE;
112
- return result(Math.acos(x));
99
+ return trigonometric('acos', x);
113
100
  }
114
101
 
115
102
  /** `math.atan(x)`: inverse tangent. */
116
103
  export function atan(x: Value): Value {
117
- return isPresent(x) ? result(Math.atan(x)) : NONE;
104
+ return isPresent(x) ? trigonometric('atan', x) : NONE;
118
105
  }
119
106
 
120
107
  /** `math.atan2(y, x)`: angle of a vector, correct in all four quadrants. */
121
108
  export function atan2(y: Value, x: Value): Value {
122
109
  if (!isPresent(y) || !isPresent(x)) return NONE;
123
- return result(Math.atan2(y, x));
110
+ return trigonometric('atan2', y, x);
124
111
  }
@@ -0,0 +1,63 @@
1
+ /** Exact two-argument hypotenuse, `stdlib.md` section 20.10.1. */
2
+ const view = new DataView(new ArrayBuffer(8));
3
+ const LEADING = 1n << 52n;
4
+ const FRACTION = LEADING - 1n;
5
+
6
+ function parts(value: number): { significand: bigint; exponent: number } {
7
+ view.setFloat64(0, Math.abs(value), false);
8
+ const encoded = view.getBigUint64(0, false);
9
+ const exponent = Number(encoded >> 52n);
10
+ return {
11
+ significand: (encoded & FRACTION) | (exponent === 0 ? 0n : LEADING),
12
+ exponent: exponent === 0 ? -1074 : exponent - 1075,
13
+ };
14
+ }
15
+
16
+ function decode(encoded: bigint): number {
17
+ view.setBigUint64(0, encoded, false);
18
+ return view.getFloat64(0, false);
19
+ }
20
+
21
+ /** A power-of-two upper bound makes integer Newton steps decrease to the floor. */
22
+ export function integerSqrt(value: bigint): bigint {
23
+ if (value < 2n) return value;
24
+ let before = 1n << BigInt(Math.ceil(value.toString(2).length / 2));
25
+ for (;;) {
26
+ const next = (before + value / before) >> 1n;
27
+ if (next >= before) return before;
28
+ before = next;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Keep the squared sum exact, then compare against a squared rounding midpoint.
34
+ * Binary64 input widths bound every temporary integer to fewer than 4,200 bits.
35
+ */
36
+ export function hypotenuse(x: number, y: number): number | null {
37
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
38
+ if (x === 0) return Math.abs(y);
39
+ if (y === 0) return Math.abs(x);
40
+ const a = parts(x);
41
+ const b = parts(y);
42
+ const exponent = Math.min(a.exponent, b.exponent);
43
+ const square = (a.significand * a.significand << BigInt(2 * (a.exponent - exponent))) +
44
+ (b.significand * b.significand << BigInt(2 * (b.exponent - exponent)));
45
+ const top = square.toString(2).length - 1;
46
+ let spacing = Math.max(-1074, exponent + Math.floor(top / 2) - 52);
47
+ const shift = exponent - spacing;
48
+ const numerator = shift < 0 ? square : square << BigInt(2 * shift);
49
+ const denominator = shift < 0 ? 1n << BigInt(-2 * shift) : 1n;
50
+ let significand = integerSqrt(numerator / denominator);
51
+ const midpoint = 2n * significand + 1n;
52
+ const comparison = 4n * numerator - denominator * midpoint * midpoint;
53
+ if (comparison > 0n || comparison === 0n && (significand & 1n) !== 0n) significand += 1n;
54
+ if (significand >= 2n * LEADING) {
55
+ significand >>= 1n;
56
+ spacing += 1;
57
+ }
58
+ // Encoding after rounding avoids a second rounding through a floating scale.
59
+ if (significand < LEADING) return decode(significand);
60
+ const encodedExponent = spacing + 1075;
61
+ if (encodedExponent >= 2047) return null;
62
+ return decode(BigInt(encodedExponent) << 52n | (significand - LEADING));
63
+ }
@@ -0,0 +1,57 @@
1
+ /** Exact real power rounded once, following section 20.10.3. */
2
+ import { integerSqrt } from './hypot.js';
3
+ import { ceil, exponentialEndpoint, floor, logarithm, parts, rounded } from './transcendental.js';
4
+
5
+ function oddParts(value: number): readonly [bigint, number] {
6
+ let [integer, exponent] = parts(value);
7
+ while ((integer & 1n) === 0n) { integer >>= 1n; exponent++; }
8
+ return [integer, exponent];
9
+ }
10
+
11
+ /** Undefined requests intervals; null is an exactly determined overflow. */
12
+ function rationalResult(base: number, exponent: number): number | null | undefined {
13
+ let [odd, scale] = oddParts(base);
14
+ let [numerator, shift] = oddParts(exponent);
15
+ if (shift >= 0) numerator <<= BigInt(shift);
16
+ else for (; shift < 0; shift++) {
17
+ if (scale % 2 !== 0) return undefined;
18
+ const root = integerSqrt(odd);
19
+ if (root * root !== odd) return undefined;
20
+ odd = root; scale /= 2;
21
+ }
22
+ if (odd === 1n) {
23
+ const power = BigInt(scale) * numerator * (exponent < 0 ? -1n : 1n);
24
+ if (power > 1023n) return null;
25
+ if (power < -1075n) return 0;
26
+ return rounded(1n, Number(power));
27
+ }
28
+ // These exact odd powers include every possible dyadic rounding midpoint.
29
+ if (exponent > 0 && numerator <= 53n) return rounded(odd ** numerator, scale * Number(numerator));
30
+ return undefined;
31
+ }
32
+
33
+ /** Internal kernel; a lower starting precision exposes refinement to tests. */
34
+ export function power(base: number, exponent: number, initialPrecision = 160): number | null {
35
+ if (!Number.isFinite(base) || !Number.isFinite(exponent)) return null;
36
+ if (exponent === 0) return 1;
37
+ if (base === 0) return exponent > 0 ? 0 : null;
38
+ if (base < 0 && !Number.isInteger(exponent)) return null;
39
+ const sign = base < 0 && exponent % 2 !== 0 ? -1 : 1;
40
+ const magnitude = Math.abs(base);
41
+ if (magnitude === 1) return sign;
42
+ const signed = (value: number | null): number | null => value === null ? null : value === 0 ? 0 : sign * value;
43
+ const exact = rationalResult(magnitude, exponent);
44
+ if (exact !== undefined) return signed(exact);
45
+ const [multiplier, shift] = parts(exponent);
46
+ for (let precision = Math.max(64, initialPrecision); ; precision += 80) {
47
+ let [low, high] = logarithm(magnitude, precision);
48
+ low *= multiplier; high *= multiplier;
49
+ if (shift >= 0) { low <<= BigInt(shift); high <<= BigInt(shift); }
50
+ else { const divisor = 1n << BigInt(-shift); low = floor(low, divisor); high = ceil(high, divisor); }
51
+ if (exponent < 0) [low, high] = [-high, -low];
52
+ const below = exponentialEndpoint(low, precision), above = exponentialEndpoint(high, precision);
53
+ if (below === undefined || above === undefined) continue;
54
+ const left = below[0], right = above[1];
55
+ if (Object.is(left, right)) return signed(left);
56
+ }
57
+ }
@@ -0,0 +1,160 @@
1
+ /** Exact integer enclosures for section 20.10.2. No host elementary calls. */
2
+ export type ElementaryKind = 'exp' | 'log' | 'log10' | 'log2';
3
+ export type Interval = readonly [bigint, bigint];
4
+ const view = new DataView(new ArrayBuffer(8));
5
+ const mask = (1n << 52n) - 1n;
6
+ let defaultTwo: Interval | undefined;
7
+ let defaultTen: Interval | undefined;
8
+
9
+ export const bitLength = (x: bigint): number => x === 0n ? 0 : x.toString(2).length;
10
+ export const floor = (n: bigint, d: bigint): bigint => n >= 0n ? n / d : -((-n + d - 1n) / d);
11
+ export const ceil = (n: bigint, d: bigint): bigint => -floor(-n, d);
12
+
13
+ export function parts(x: number): readonly [bigint, number] {
14
+ view.setFloat64(0, Math.abs(x));
15
+ const bits = view.getBigUint64(0), exponent = Number(bits >> 52n);
16
+ return [exponent === 0 ? bits : (bits & mask) | (1n << 52n), exponent === 0 ? -1074 : exponent - 1075];
17
+ }
18
+
19
+ function fixed(x: number, precision: number): Interval {
20
+ const [m, e] = parts(x), shift = e + precision;
21
+ return shift >= 0 ? [m << BigInt(shift), m << BigInt(shift)]
22
+ : [m >> BigInt(-shift), ceil(m, 1n << BigInt(-shift))];
23
+ }
24
+
25
+ /** Round an exact dyadic endpoint directly, with the subnormal spacing floor. */
26
+ export function rounded(numerator: bigint, exponent: number): number | null {
27
+ if (numerator === 0n) return 0;
28
+ const negative = numerator < 0n;
29
+ const magnitude = negative ? -numerator : numerator;
30
+ let spacing = Math.max(-1074, bitLength(magnitude) - 1 + exponent - 52);
31
+ const shift = spacing - exponent;
32
+ let significand: bigint;
33
+ if (shift <= 0) significand = magnitude << BigInt(-shift);
34
+ else {
35
+ const divisor = 1n << BigInt(shift), remainder = magnitude % divisor;
36
+ significand = magnitude / divisor;
37
+ if (remainder * 2n > divisor || (remainder * 2n === divisor && (significand & 1n))) significand++;
38
+ }
39
+ if (significand === 0n) return 0;
40
+ if (significand >= 1n << 53n) { significand >>= 1n; spacing++; }
41
+ if (spacing > 971) return null;
42
+ const bits = significand < 1n << 52n ? significand
43
+ : (BigInt(spacing + 1075) << 52n) | (significand & mask);
44
+ view.setBigUint64(0, bits | (negative ? 1n << 63n : 0n));
45
+ return view.getFloat64(0);
46
+ }
47
+
48
+ /** Twice the positive odd-power sum, for 0 <= a/b <= 1/3. */
49
+ function oddSeries(a: bigint, b: bigint, precision: number): Interval {
50
+ if (a === 0n) return [0n, 0n];
51
+ const unit = 1n << BigInt(precision), low = a * unit / b, high = ceil(a * unit, b);
52
+ const squaredLow = low * low / unit, squaredHigh = ceil(high * high, unit);
53
+ let powerLow = low, powerHigh = high, sumLow = 0n, sumHigh = 0n, odd = 1n;
54
+ for (;;) {
55
+ sumLow += powerLow / odd; sumHigh += ceil(powerHigh, odd);
56
+ powerLow = powerLow * squaredLow / unit;
57
+ powerHigh = ceil(powerHigh * squaredHigh, unit); odd += 2n;
58
+ if (powerHigh <= 2n) return [2n * sumLow, 2n * (sumHigh + ceil(9n * powerHigh, 8n * odd))];
59
+ }
60
+ }
61
+
62
+ function lnTwo(precision: number): Interval {
63
+ if (precision !== 160) return oddSeries(1n, 3n, precision);
64
+ return defaultTwo ??= oddSeries(1n, 3n, precision);
65
+ }
66
+
67
+ export function logarithm(x: number, precision: number): Interval {
68
+ const [m, e] = parts(x), width = bitLength(m), one = 1n << BigInt(width - 1);
69
+ const k = BigInt(e + width - 1), [low, high] = oddSeries(m - one, m + one, precision);
70
+ const [constantLow, constantHigh] = lnTwo(precision);
71
+ return k >= 0n ? [low + k * constantLow, high + k * constantHigh]
72
+ : [low + k * constantHigh, high + k * constantLow];
73
+ }
74
+
75
+ function lnTen(precision: number): Interval {
76
+ if (precision !== 160) return logarithm(10, precision);
77
+ return defaultTen ??= logarithm(10, precision);
78
+ }
79
+
80
+ function quotient(numerator: Interval, denominator: Interval, precision: number): Interval {
81
+ const unit = 1n << BigInt(precision);
82
+ let low: bigint | undefined, high: bigint | undefined;
83
+ for (const n of numerator) for (const d of denominator) {
84
+ const below = floor(n * unit, d), above = ceil(n * unit, d);
85
+ if (low === undefined || below < low) low = below;
86
+ if (high === undefined || above > high) high = above;
87
+ }
88
+ return [low as bigint, high as bigint];
89
+ }
90
+
91
+ function exponentialRange(low: bigint, high: bigint, positive: boolean, precision: number): readonly [bigint, bigint, number] | undefined {
92
+ const unit = 1n << BigInt(precision);
93
+ const [constantLow, constantHigh] = lnTwo(precision), k = low / constantHigh;
94
+ const reducedLow = low - k * constantHigh, reducedHigh = high - k * constantLow;
95
+ if (reducedLow < 0n || reducedHigh >= unit) return undefined;
96
+ let termLow = unit, termHigh = unit, sumLow = unit, sumHigh = unit, n = 0n;
97
+ for (;;) {
98
+ n++;
99
+ termLow = termLow * reducedLow / (unit * n);
100
+ termHigh = ceil(termHigh * reducedHigh, unit * n);
101
+ if (termHigh <= 1n) { sumHigh += 2n * termHigh; break; }
102
+ sumLow += termLow; sumHigh += termHigh;
103
+ }
104
+ return positive ? [sumLow, sumHigh, Number(k) - precision]
105
+ : [unit * unit / sumHigh, ceil(unit * unit, sumLow), -Number(k) - precision];
106
+ }
107
+
108
+ /** Enclose exp(integer / 2**precision) without a binary64 intermediate. */
109
+ export function exponentialEndpoint(integer: bigint, precision: number): readonly [number | null, number | null] | undefined {
110
+ const cutoff = 1024n << BigInt(precision);
111
+ if (integer >= cutoff) return [null, null];
112
+ if (integer <= -cutoff) return [0, 0];
113
+ if (integer === 0n) return [1, 1];
114
+ const magnitude = integer < 0n ? -integer : integer;
115
+ const range = exponentialRange(magnitude, magnitude, integer > 0n, precision);
116
+ if (range === undefined) return undefined;
117
+ const [low, high, exponent] = range;
118
+ return [rounded(low, exponent), rounded(high, exponent)];
119
+ }
120
+
121
+ /** Internal kernel. The optional starting precision permits refinement tests. */
122
+ export function transcendental(kind: ElementaryKind, x: number, initialPrecision = 160): number | null {
123
+ if (!Number.isFinite(x) || (kind !== 'exp' && x <= 0)) return null;
124
+ if (kind === 'exp') {
125
+ if (x >= 1024) return null;
126
+ if (x <= -1024) return 0;
127
+ if (Math.abs(x) < 2 ** -60) return 1;
128
+ } else {
129
+ if (x === 1) return 0;
130
+ if (kind === 'log2') {
131
+ const [m, e] = parts(x);
132
+ if ((m & (m - 1n)) === 0n) return e + bitLength(m) - 1;
133
+ }
134
+ if (kind === 'log10') {
135
+ const [m, e] = parts(x);
136
+ let integer = e >= 0 ? m << BigInt(e)
137
+ : m % (1n << BigInt(-e)) === 0n ? m >> BigInt(-e) : 0n;
138
+ let count = 0;
139
+ while (integer > 1n && integer % 10n === 0n) { integer /= 10n; count++; }
140
+ if (integer === 1n) return count;
141
+ }
142
+ }
143
+ for (let precision = Math.max(64, initialPrecision); ; precision += 80) {
144
+ let low: bigint, high: bigint, exponent: number;
145
+ if (kind === 'exp') {
146
+ const [start, end] = fixed(x, precision);
147
+ const range = exponentialRange(start, end, x >= 0, precision);
148
+ if (range === undefined) continue;
149
+ [low, high, exponent] = range;
150
+ }
151
+ else {
152
+ const natural = logarithm(x, precision);
153
+ [low, high] = kind === 'log' ? natural
154
+ : quotient(natural, kind === 'log2' ? lnTwo(precision) : lnTen(precision), precision);
155
+ exponent = -precision;
156
+ }
157
+ const left = rounded(low, exponent), right = rounded(high, exponent);
158
+ if (Object.is(left, right)) return left;
159
+ }
160
+ }