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.
- package/CHANGELOG.md +105 -0
- package/README.md +12 -12
- package/dist/core/engine/channels.js +2 -2
- package/dist/core/engine/channels.js.map +1 -1
- package/dist/core/engine/engine.js +2 -3
- package/dist/core/engine/engine.js.map +1 -1
- package/dist/core/engine/library/maths.js +2 -2
- package/dist/core/engine/library/maths.js.map +1 -1
- package/dist/core/engine/machine.js +5 -5
- package/dist/core/engine/machine.js.map +1 -1
- package/dist/core/engine/memory.js +2 -2
- package/dist/core/engine/memory.js.map +1 -1
- package/dist/core/engine/registers.js +2 -2
- package/dist/core/engine/registers.js.map +1 -1
- package/dist/core/engine/values/index.d.ts +1 -1
- package/dist/core/engine/values/index.d.ts.map +1 -1
- package/dist/core/engine/values/index.js +1 -1
- package/dist/core/engine/values/index.js.map +1 -1
- package/dist/core/engine/values/value.d.ts +2 -0
- package/dist/core/engine/values/value.d.ts.map +1 -1
- package/dist/core/engine/values/value.js +4 -0
- package/dist/core/engine/values/value.js.map +1 -1
- package/dist/core/stdlib/averages/exponential.d.ts.map +1 -1
- package/dist/core/stdlib/averages/exponential.js +3 -6
- package/dist/core/stdlib/averages/exponential.js.map +1 -1
- package/dist/core/stdlib/averages/gaussian.d.ts +15 -0
- package/dist/core/stdlib/averages/gaussian.d.ts.map +1 -0
- package/dist/core/stdlib/averages/gaussian.js +55 -0
- package/dist/core/stdlib/averages/gaussian.js.map +1 -0
- package/dist/core/stdlib/averages/shaped.d.ts +2 -6
- package/dist/core/stdlib/averages/shaped.d.ts.map +1 -1
- package/dist/core/stdlib/averages/shaped.js +8 -22
- package/dist/core/stdlib/averages/shaped.js.map +1 -1
- package/dist/core/stdlib/maths/arithmetic.d.ts.map +1 -1
- package/dist/core/stdlib/maths/arithmetic.js +2 -1
- package/dist/core/stdlib/maths/arithmetic.js.map +1 -1
- package/dist/core/stdlib/maths/elementary.d.ts +3 -20
- package/dist/core/stdlib/maths/elementary.d.ts.map +1 -1
- package/dist/core/stdlib/maths/elementary.js +17 -13
- package/dist/core/stdlib/maths/elementary.js.map +1 -1
- package/dist/core/stdlib/maths/hypot.d.ts +8 -0
- package/dist/core/stdlib/maths/hypot.d.ts.map +1 -0
- package/dist/core/stdlib/maths/hypot.js +68 -0
- package/dist/core/stdlib/maths/hypot.js.map +1 -0
- package/dist/core/stdlib/maths/power.d.ts +3 -0
- package/dist/core/stdlib/maths/power.d.ts.map +1 -0
- package/dist/core/stdlib/maths/power.js +83 -0
- package/dist/core/stdlib/maths/power.js.map +1 -0
- package/dist/core/stdlib/maths/transcendental.d.ts +15 -0
- package/dist/core/stdlib/maths/transcendental.d.ts.map +1 -0
- package/dist/core/stdlib/maths/transcendental.js +185 -0
- package/dist/core/stdlib/maths/transcendental.js.map +1 -0
- package/dist/core/stdlib/maths/trigonometric.d.ts +4 -0
- package/dist/core/stdlib/maths/trigonometric.d.ts.map +1 -0
- package/dist/core/stdlib/maths/trigonometric.js +196 -0
- package/dist/core/stdlib/maths/trigonometric.js.map +1 -0
- package/dist/core/stdlib/series/conditions.d.ts +2 -4
- package/dist/core/stdlib/series/conditions.d.ts.map +1 -1
- package/dist/core/stdlib/series/conditions.js +9 -13
- package/dist/core/stdlib/series/conditions.js.map +1 -1
- package/dist/core/stdlib/series/statistics.d.ts.map +1 -1
- package/dist/core/stdlib/series/statistics.js +2 -0
- package/dist/core/stdlib/series/statistics.js.map +1 -1
- package/dist/core/stdlib/values/history.d.ts +26 -0
- package/dist/core/stdlib/values/history.d.ts.map +1 -0
- package/dist/core/stdlib/values/history.js +52 -0
- package/dist/core/stdlib/values/history.js.map +1 -0
- package/dist/core/stdlib/values/index.d.ts +1 -0
- package/dist/core/stdlib/values/index.d.ts.map +1 -1
- package/dist/core/stdlib/values/index.js +1 -0
- package/dist/core/stdlib/values/index.js.map +1 -1
- package/dist/core/stdlib/values/lookback.d.ts +4 -1
- package/dist/core/stdlib/values/lookback.d.ts.map +1 -1
- package/dist/core/stdlib/values/lookback.js +48 -45
- package/dist/core/stdlib/values/lookback.js.map +1 -1
- package/dist/core/stdlib/values/region.d.ts +6 -5
- package/dist/core/stdlib/values/region.d.ts.map +1 -1
- package/dist/core/stdlib/values/region.js +3 -3
- package/dist/core/stdlib/values/region.js.map +1 -1
- package/dist/core/stdlib/volatility/measures.d.ts.map +1 -1
- package/dist/core/stdlib/volatility/measures.js +6 -4
- package/dist/core/stdlib/volatility/measures.js.map +1 -1
- package/dist/core/version/version.generated.d.ts +1 -1
- package/dist/core/version/version.generated.js +1 -1
- package/package.json +9 -1
- package/src/core/engine/channels.ts +2 -2
- package/src/core/engine/engine.ts +3 -3
- package/src/core/engine/library/maths.ts +2 -2
- package/src/core/engine/machine.ts +5 -5
- package/src/core/engine/memory.ts +2 -2
- package/src/core/engine/registers.ts +2 -2
- package/src/core/engine/values/index.ts +1 -0
- package/src/core/engine/values/value.ts +5 -0
- package/src/core/stdlib/averages/exponential.ts +3 -4
- package/src/core/stdlib/averages/gaussian.ts +52 -0
- package/src/core/stdlib/averages/shaped.ts +8 -21
- package/src/core/stdlib/maths/arithmetic.ts +2 -1
- package/src/core/stdlib/maths/elementary.ts +20 -33
- package/src/core/stdlib/maths/hypot.ts +63 -0
- package/src/core/stdlib/maths/power.ts +57 -0
- package/src/core/stdlib/maths/transcendental.ts +160 -0
- package/src/core/stdlib/maths/trigonometric.ts +160 -0
- package/src/core/stdlib/series/conditions.ts +9 -12
- package/src/core/stdlib/series/statistics.ts +1 -0
- package/src/core/stdlib/values/history.ts +70 -0
- package/src/core/stdlib/values/index.ts +1 -0
- package/src/core/stdlib/values/lookback.ts +51 -47
- package/src/core/stdlib/values/region.ts +7 -5
- package/src/core/stdlib/volatility/measures.ts +6 -4
- 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
|
-
|
|
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
|
-
|
|
54
|
-
return smoothed(state, key, len, value, (previous, next) => (previous * (
|
|
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
|
-
|
|
41
|
-
const
|
|
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
|
-
*
|
|
66
|
-
*
|
|
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
|
|
86
|
-
|
|
87
|
-
const weights
|
|
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
|
-
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
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) ?
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) ?
|
|
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) ?
|
|
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) ?
|
|
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
|
|
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
|
|
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) ?
|
|
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
|
|
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
|
+
}
|