pond-ts 0.24.0 → 0.26.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 +70 -1
- package/dist/batch/aggregate-columns.js +18 -10
- package/dist/batch/operators/column-builders.d.ts +31 -0
- package/dist/batch/operators/column-builders.js +103 -0
- package/dist/batch/time-series.d.ts +8 -0
- package/dist/batch/time-series.js +79 -66
- package/dist/batch/validate.js +7 -1
- package/dist/column.js +91 -21
- package/dist/columnar/chunked-column.d.ts +10 -0
- package/dist/columnar/chunked-column.js +29 -3
- package/dist/columnar/column.d.ts +19 -1
- package/dist/columnar/column.js +40 -5
- package/dist/reducers/avg.js +40 -13
- package/dist/reducers/count.js +30 -4
- package/dist/reducers/index.js +34 -2
- package/dist/reducers/max.js +37 -10
- package/dist/reducers/min.js +37 -23
- package/dist/reducers/percentile.d.ts +12 -28
- package/dist/reducers/percentile.js +49 -43
- package/dist/reducers/stdev.js +115 -29
- package/dist/reducers/sum.js +29 -5
- package/package.json +1 -1
|
@@ -5,36 +5,20 @@ export declare function parsePercentile(op: string): number | undefined;
|
|
|
5
5
|
/**
|
|
6
6
|
* Shared `reduceColumn` body for percentile-shaped reducers
|
|
7
7
|
* (`median`, `p50`, `p95`, etc.). Walks the validity bitmap to
|
|
8
|
-
* gather defined cells into a dense `Float64Array`,
|
|
9
|
-
*
|
|
10
|
-
* the sorted view.
|
|
8
|
+
* gather defined **and finite** cells into a dense `Float64Array`,
|
|
9
|
+
* sorts with the typed-array intrinsic, and reads the percentile
|
|
10
|
+
* from the sorted view.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* Non-finite cells (`NaN` / `±Infinity`) are excluded by the
|
|
13
|
+
* reducer non-finite policy (docs/notes/reducer-nan-policy.md) —
|
|
14
|
+
* uniformly, across every path. With non-finite filtered out
|
|
15
|
+
* before the sort, `Float64Array.prototype.sort()` (the numeric,
|
|
16
|
+
* NaN-free intrinsic, ~2× faster than `Array.sort` with a
|
|
17
|
+
* comparator) produces the same total order as the row path's
|
|
18
|
+
* `Array.sort((a, b) => a - b)` over the same finite values — so
|
|
19
|
+
* there is no longer any NaN-ordering seam to special-case.
|
|
13
20
|
*
|
|
14
|
-
*
|
|
15
|
-
* NaN from the comparator on NaN inputs; V8 treats this as
|
|
16
|
-
* "equal" and leaves NaN cells in undefined order — the
|
|
17
|
-
* resulting percentile is whatever cell happens to land at the
|
|
18
|
-
* computed rank, possibly NaN itself.
|
|
19
|
-
* - `Float64Array.prototype.sort()` (typed-array intrinsic) puts
|
|
20
|
-
* NaN deterministically at the end of the sorted view — the
|
|
21
|
-
* percentile rank then reads a non-NaN cell unless the rank
|
|
22
|
-
* lands in the NaN suffix.
|
|
23
|
-
*
|
|
24
|
-
* The first-pass NaN detection lets us use `Float64Array.sort`'s
|
|
25
|
-
* 2× speedup for the common no-NaN case (full parity with row API
|
|
26
|
-
* because both produce identical sorted orders when no NaN
|
|
27
|
-
* present), and fall back to `Array.sort` with comparator only
|
|
28
|
-
* when NaN is present (preserving bug-for-bug row-API parity on
|
|
29
|
-
* the rare contract-violating input).
|
|
30
|
-
*
|
|
31
|
-
* NaN can only reach a `kind: 'number'` column via trusted
|
|
32
|
-
* construction (`fromEvents`); the public `assertCellKind`
|
|
33
|
-
* rejects it at intake. Closed Codex review finding on PR #153 —
|
|
34
|
-
* earlier L2 fix that filtered NaN was correct in spirit but
|
|
35
|
-
* introduced a *different* divergence from the row API. A
|
|
36
|
-
* principled "filter NaN consistently across both paths" fix is
|
|
37
|
-
* tracked in the followup issue.
|
|
21
|
+
* Empty (no defined+finite values) → `undefined`.
|
|
38
22
|
*/
|
|
39
23
|
export declare function reducePercentileColumn(col: Float64Column, q: number): number | undefined;
|
|
40
24
|
export declare function percentileReducer(q: number): ReducerDef;
|
|
@@ -18,51 +18,64 @@ export function parsePercentile(op) {
|
|
|
18
18
|
/**
|
|
19
19
|
* Shared `reduceColumn` body for percentile-shaped reducers
|
|
20
20
|
* (`median`, `p50`, `p95`, etc.). Walks the validity bitmap to
|
|
21
|
-
* gather defined cells into a dense `Float64Array`,
|
|
22
|
-
*
|
|
23
|
-
* the sorted view.
|
|
21
|
+
* gather defined **and finite** cells into a dense `Float64Array`,
|
|
22
|
+
* sorts with the typed-array intrinsic, and reads the percentile
|
|
23
|
+
* from the sorted view.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
25
|
+
* Non-finite cells (`NaN` / `±Infinity`) are excluded by the
|
|
26
|
+
* reducer non-finite policy (docs/notes/reducer-nan-policy.md) —
|
|
27
|
+
* uniformly, across every path. With non-finite filtered out
|
|
28
|
+
* before the sort, `Float64Array.prototype.sort()` (the numeric,
|
|
29
|
+
* NaN-free intrinsic, ~2× faster than `Array.sort` with a
|
|
30
|
+
* comparator) produces the same total order as the row path's
|
|
31
|
+
* `Array.sort((a, b) => a - b)` over the same finite values — so
|
|
32
|
+
* there is no longer any NaN-ordering seam to special-case.
|
|
26
33
|
*
|
|
27
|
-
*
|
|
28
|
-
* NaN from the comparator on NaN inputs; V8 treats this as
|
|
29
|
-
* "equal" and leaves NaN cells in undefined order — the
|
|
30
|
-
* resulting percentile is whatever cell happens to land at the
|
|
31
|
-
* computed rank, possibly NaN itself.
|
|
32
|
-
* - `Float64Array.prototype.sort()` (typed-array intrinsic) puts
|
|
33
|
-
* NaN deterministically at the end of the sorted view — the
|
|
34
|
-
* percentile rank then reads a non-NaN cell unless the rank
|
|
35
|
-
* lands in the NaN suffix.
|
|
36
|
-
*
|
|
37
|
-
* The first-pass NaN detection lets us use `Float64Array.sort`'s
|
|
38
|
-
* 2× speedup for the common no-NaN case (full parity with row API
|
|
39
|
-
* because both produce identical sorted orders when no NaN
|
|
40
|
-
* present), and fall back to `Array.sort` with comparator only
|
|
41
|
-
* when NaN is present (preserving bug-for-bug row-API parity on
|
|
42
|
-
* the rare contract-violating input).
|
|
43
|
-
*
|
|
44
|
-
* NaN can only reach a `kind: 'number'` column via trusted
|
|
45
|
-
* construction (`fromEvents`); the public `assertCellKind`
|
|
46
|
-
* rejects it at intake. Closed Codex review finding on PR #153 —
|
|
47
|
-
* earlier L2 fix that filtered NaN was correct in spirit but
|
|
48
|
-
* introduced a *different* divergence from the row API. A
|
|
49
|
-
* principled "filter NaN consistently across both paths" fix is
|
|
50
|
-
* tracked in the followup issue.
|
|
34
|
+
* Empty (no defined+finite values) → `undefined`.
|
|
51
35
|
*/
|
|
52
36
|
export function reducePercentileColumn(col, q) {
|
|
53
37
|
const validity = col.validity;
|
|
54
38
|
const values = col._values;
|
|
55
39
|
let dense;
|
|
56
40
|
let denseLength = 0;
|
|
57
|
-
|
|
58
|
-
|
|
41
|
+
// Fast path: every defined cell is finite (`Float64Column.allFinite`),
|
|
42
|
+
// so we gather defined cells with no per-element `Number.isFinite`
|
|
43
|
+
// filter (reducer non-finite policy, docs/notes/reducer-nan-policy.md).
|
|
44
|
+
// The subsequent `Float64Array.sort` is the same NaN-free intrinsic
|
|
45
|
+
// either way → identical order, identical percentile.
|
|
46
|
+
if (col.allFinite) {
|
|
47
|
+
if (validity === undefined) {
|
|
48
|
+
if (col.length === 0)
|
|
49
|
+
return undefined;
|
|
50
|
+
dense = new Float64Array(col.length);
|
|
51
|
+
for (let i = 0; i < col.length; i += 1) {
|
|
52
|
+
dense[denseLength] = values[i];
|
|
53
|
+
denseLength += 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const definedCount = validity.definedCount;
|
|
58
|
+
if (definedCount === 0)
|
|
59
|
+
return undefined;
|
|
60
|
+
dense = new Float64Array(definedCount);
|
|
61
|
+
const bits = validity.bits;
|
|
62
|
+
for (let i = 0; i < col.length; i += 1) {
|
|
63
|
+
if ((bits[i >> 3] & (1 << (i & 7))) === 0)
|
|
64
|
+
continue;
|
|
65
|
+
dense[denseLength] = values[i];
|
|
66
|
+
denseLength += 1;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (validity === undefined) {
|
|
71
|
+
// Guarded path: filter non-finite before the sort.
|
|
59
72
|
if (col.length === 0)
|
|
60
73
|
return undefined;
|
|
61
74
|
dense = new Float64Array(col.length);
|
|
62
75
|
for (let i = 0; i < col.length; i += 1) {
|
|
63
76
|
const v = values[i];
|
|
64
|
-
if (Number.
|
|
65
|
-
|
|
77
|
+
if (!Number.isFinite(v))
|
|
78
|
+
continue;
|
|
66
79
|
dense[denseLength] = v;
|
|
67
80
|
denseLength += 1;
|
|
68
81
|
}
|
|
@@ -77,23 +90,16 @@ export function reducePercentileColumn(col, q) {
|
|
|
77
90
|
if ((bits[i >> 3] & (1 << (i & 7))) === 0)
|
|
78
91
|
continue;
|
|
79
92
|
const v = values[i];
|
|
80
|
-
if (Number.
|
|
81
|
-
|
|
93
|
+
if (!Number.isFinite(v))
|
|
94
|
+
continue;
|
|
82
95
|
dense[denseLength] = v;
|
|
83
96
|
denseLength += 1;
|
|
84
97
|
}
|
|
85
98
|
}
|
|
86
99
|
if (denseLength === 0)
|
|
87
100
|
return undefined;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// diverges from `Float64Array.sort` on NaN ordering.
|
|
91
|
-
const arr = Array.from(dense.subarray(0, denseLength));
|
|
92
|
-
arr.sort((a, b) => a - b);
|
|
93
|
-
return percentileOfSorted(arr, q);
|
|
94
|
-
}
|
|
95
|
-
// No NaN: `Float64Array.sort` is parity-correct (same total
|
|
96
|
-
// order as `Array.sort` with comparator) and ~2× faster.
|
|
101
|
+
// Non-finite excluded upstream by policy → `Float64Array.sort` (numeric,
|
|
102
|
+
// NaN-free) gives the same order as the row path's comparator sort.
|
|
97
103
|
const view = dense.subarray(0, denseLength);
|
|
98
104
|
view.sort();
|
|
99
105
|
const rank = (q / 100) * (denseLength - 1);
|
package/dist/reducers/stdev.js
CHANGED
|
@@ -20,9 +20,12 @@
|
|
|
20
20
|
* agree to floating-point noise — bit-for-bit when they see the values in the
|
|
21
21
|
* same order, which the bucketed paths do.
|
|
22
22
|
*
|
|
23
|
-
* `rollingState`
|
|
24
|
-
*
|
|
25
|
-
* `sq/n − mean²`
|
|
23
|
+
* `rollingState` adds `remove` for its sliding window via Welford's
|
|
24
|
+
* **order-independent delete** (the reverse recurrence). It works in the same
|
|
25
|
+
* deviation space — no `sq/n − mean²` cancellation, no shift drift — and
|
|
26
|
+
* removes *by value*, so it stays correct under the live layer's reorder-mode
|
|
27
|
+
* eviction (which removes the sorted-prefix, not the oldest-arrived event).
|
|
28
|
+
* See its inline note for the mechanics.
|
|
26
29
|
*/
|
|
27
30
|
// Welford accumulator: `add` each value, then read `result()` for the
|
|
28
31
|
// population stdev (`undefined` if nothing was added). Shared by `reduce` and
|
|
@@ -54,20 +57,55 @@ export const stdev = {
|
|
|
54
57
|
},
|
|
55
58
|
reduceColumn(col) {
|
|
56
59
|
// Inlined Welford (mirrors `welfordStdev`) over the packed array — a single
|
|
57
|
-
// pass, skipping gaps via the validity bitmask
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
+
// pass, skipping gaps via the validity bitmask and skipping non-finite
|
|
61
|
+
// cells (reducer non-finite policy, docs/notes/reducer-nan-policy.md). One
|
|
62
|
+
// division per finite element vs the old two-pass's two divisionless scans,
|
|
63
|
+
// but identical to the other paths' recurrence so the fast path and row
|
|
64
|
+
// path cannot diverge. `n` counts only finite contributors, so `n === 0`
|
|
65
|
+
// (no finite cells) → `undefined`.
|
|
60
66
|
const values = col._values;
|
|
61
67
|
const validity = col.validity;
|
|
62
68
|
let n = 0;
|
|
63
69
|
let mean = 0;
|
|
64
70
|
let m2 = 0;
|
|
71
|
+
// Fast path: every defined cell is finite (`Float64Column.allFinite`),
|
|
72
|
+
// so the Welford recurrence runs over every defined cell with no
|
|
73
|
+
// per-element `Number.isFinite` guard (reducer non-finite policy,
|
|
74
|
+
// docs/notes/reducer-nan-policy.md). Same recurrence as the guarded
|
|
75
|
+
// path → identical result.
|
|
76
|
+
if (col.allFinite) {
|
|
77
|
+
const len = col.length;
|
|
78
|
+
if (validity === undefined) {
|
|
79
|
+
for (let i = 0; i < len; i += 1) {
|
|
80
|
+
const v = values[i];
|
|
81
|
+
n += 1;
|
|
82
|
+
const delta = v - mean;
|
|
83
|
+
mean += delta / n;
|
|
84
|
+
m2 += delta * (v - mean);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const bits = validity.bits;
|
|
89
|
+
for (let i = 0; i < len; i += 1) {
|
|
90
|
+
if ((bits[i >> 3] & (1 << (i & 7))) === 0)
|
|
91
|
+
continue;
|
|
92
|
+
const v = values[i];
|
|
93
|
+
n += 1;
|
|
94
|
+
const delta = v - mean;
|
|
95
|
+
mean += delta / n;
|
|
96
|
+
m2 += delta * (v - mean);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return n === 0 ? undefined : Math.sqrt(Math.max(0, m2 / n));
|
|
100
|
+
}
|
|
101
|
+
// Guarded path: skip non-finite cells; `n` counts only finite
|
|
102
|
+
// contributors so `n === 0` (no finite cells) → `undefined`.
|
|
65
103
|
if (validity === undefined) {
|
|
66
104
|
const len = col.length;
|
|
67
|
-
if (len === 0)
|
|
68
|
-
return undefined;
|
|
69
105
|
for (let i = 0; i < len; i += 1) {
|
|
70
106
|
const v = values[i];
|
|
107
|
+
if (!Number.isFinite(v))
|
|
108
|
+
continue;
|
|
71
109
|
n += 1;
|
|
72
110
|
const delta = v - mean;
|
|
73
111
|
mean += delta / n;
|
|
@@ -75,21 +113,21 @@ export const stdev = {
|
|
|
75
113
|
}
|
|
76
114
|
}
|
|
77
115
|
else {
|
|
78
|
-
if (validity.definedCount === 0)
|
|
79
|
-
return undefined;
|
|
80
116
|
const bits = validity.bits;
|
|
81
117
|
const len = col.length;
|
|
82
118
|
for (let i = 0; i < len; i += 1) {
|
|
83
119
|
if ((bits[i >> 3] & (1 << (i & 7))) === 0)
|
|
84
120
|
continue;
|
|
85
121
|
const v = values[i];
|
|
122
|
+
if (!Number.isFinite(v))
|
|
123
|
+
continue;
|
|
86
124
|
n += 1;
|
|
87
125
|
const delta = v - mean;
|
|
88
126
|
mean += delta / n;
|
|
89
127
|
m2 += delta * (v - mean);
|
|
90
128
|
}
|
|
91
129
|
}
|
|
92
|
-
return Math.sqrt(Math.max(0, m2 / n));
|
|
130
|
+
return n === 0 ? undefined : Math.sqrt(Math.max(0, m2 / n));
|
|
93
131
|
},
|
|
94
132
|
bucketState() {
|
|
95
133
|
const w = welfordStdev();
|
|
@@ -104,32 +142,80 @@ export const stdev = {
|
|
|
104
142
|
};
|
|
105
143
|
},
|
|
106
144
|
rollingState() {
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
145
|
+
// Sliding-window population stdev: Welford's online variance with an
|
|
146
|
+
// **order-independent delete**. `add(v)` is the standard recurrence;
|
|
147
|
+
// `remove(v)` reverses it. Working in deviation space, it fixes the old
|
|
148
|
+
// one-pass `sq/n − mean²` failure modes — catastrophic cancellation on
|
|
149
|
+
// near-equal large values (`[1e10, 1e10+1, …]` → 0 or a negative variance →
|
|
150
|
+
// NaN; the audit-§1.1 case, previously still live on this path) and shift
|
|
151
|
+
// drift on trending data (cumulative distance, elevation).
|
|
152
|
+
//
|
|
153
|
+
// Limitation — **outlier eviction** (shared with the old one-pass, so not a
|
|
154
|
+
// regression): like any *subtractive* sliding variance, evicting a value far
|
|
155
|
+
// outside the residual spread cancels — `m2 −= huge` loses the small
|
|
156
|
+
// remainder, and the error persists in the running accumulator (the clamp
|
|
157
|
+
// floors it to 0 at the extreme). Negligible until the evicted point is
|
|
158
|
+
// ~1e7–1e8× the residual stdev (e.g. a ~1e6 spike over a ~0.01-σ baseline) —
|
|
159
|
+
// far beyond realistic monitoring / activity data, and exactly why the
|
|
160
|
+
// add-only paths use plain Welford. A FIFO-only two-stack merge avoids it
|
|
161
|
+
// entirely but can't serve the by-value eviction the live layer needs.
|
|
162
|
+
//
|
|
163
|
+
// Removal is **by value, not by position**. That is load-bearing for the
|
|
164
|
+
// live layer: `LiveReduce` shares this state, and a `reorder`-mode source
|
|
165
|
+
// with retention evicts the sorted-prefix — which may be a later arrival,
|
|
166
|
+
// not the oldest — so a positional (FIFO) remove would corrupt the window.
|
|
167
|
+
// A value-based delete is correct regardless of eviction order (the
|
|
168
|
+
// documented contract; see live-reduce.ts and live-buffer-as-window.test).
|
|
169
|
+
// The batch rolling driver removes strictly oldest-first — a special case.
|
|
170
|
+
//
|
|
171
|
+
// Non-finite / missing cells arrive as `undefined` (the factory wrapper
|
|
172
|
+
// applies the non-finite policy); `add`/`remove` both skip them symmetrically
|
|
173
|
+
// so they never enter `n`.
|
|
112
174
|
let n = 0;
|
|
175
|
+
let mean = 0;
|
|
176
|
+
let m2 = 0;
|
|
113
177
|
return {
|
|
114
178
|
add(_i, v) {
|
|
115
|
-
if (typeof v
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
179
|
+
if (typeof v !== 'number')
|
|
180
|
+
return;
|
|
181
|
+
n += 1;
|
|
182
|
+
const delta = v - mean;
|
|
183
|
+
mean += delta / n;
|
|
184
|
+
m2 += delta * (v - mean);
|
|
120
185
|
},
|
|
121
186
|
remove(_i, v) {
|
|
122
|
-
if (typeof v
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
187
|
+
if (typeof v !== 'number')
|
|
188
|
+
return;
|
|
189
|
+
if (n <= 1) {
|
|
190
|
+
// Removing the final contributor — reset exactly (no 0/0, no drift).
|
|
191
|
+
n = 0;
|
|
192
|
+
mean = 0;
|
|
193
|
+
m2 = 0;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const meanWith = mean;
|
|
197
|
+
n -= 1;
|
|
198
|
+
if (n === 1) {
|
|
199
|
+
// A single remaining element has population variance *exactly* 0.
|
|
200
|
+
// Setting it directly (rather than via the subtraction below) keeps
|
|
201
|
+
// the n→1 result exact at any magnitude — the reverse step alone
|
|
202
|
+
// leaves rounding residue at large offsets (e.g. ~0.016 on 1e10).
|
|
203
|
+
mean = meanWith * 2 - v; // the one survivor: 2·mean₂ − removed
|
|
204
|
+
m2 = 0;
|
|
205
|
+
return;
|
|
126
206
|
}
|
|
207
|
+
// Deviation-space mean update (mean − (v − mean)/n): avoids the large
|
|
208
|
+
// `n·mean − v` product, staying precise at large magnitudes.
|
|
209
|
+
mean = meanWith - (v - meanWith) / n;
|
|
210
|
+
// Reverse Welford: M2 −= (v − meanNew)·(v − meanOld).
|
|
211
|
+
m2 -= (v - mean) * (v - meanWith);
|
|
212
|
+
// Normally absorbs FP round-off; on a gross outlier eviction (see the
|
|
213
|
+
// limitation note above) the subtraction can cancel below 0 — clamp.
|
|
214
|
+
if (m2 < 0)
|
|
215
|
+
m2 = 0;
|
|
127
216
|
},
|
|
128
217
|
snapshot() {
|
|
129
|
-
|
|
130
|
-
return undefined;
|
|
131
|
-
const mean = s / n;
|
|
132
|
-
return Math.sqrt(Math.max(0, sq / n - mean * mean));
|
|
218
|
+
return n === 0 ? undefined : Math.sqrt(Math.max(0, m2 / n));
|
|
133
219
|
},
|
|
134
220
|
};
|
|
135
221
|
},
|
package/dist/reducers/sum.js
CHANGED
|
@@ -7,18 +7,42 @@ export const sum = {
|
|
|
7
7
|
const values = col._values;
|
|
8
8
|
const validity = col.validity;
|
|
9
9
|
let s = 0;
|
|
10
|
+
// Fast path: the column proved every defined cell is finite
|
|
11
|
+
// (`Float64Column.allFinite`), so we can drop the per-element
|
|
12
|
+
// `Number.isFinite` guard the reducer non-finite policy
|
|
13
|
+
// (docs/notes/reducer-nan-policy.md) otherwise requires — plain
|
|
14
|
+
// accumulate, identical result.
|
|
15
|
+
if (col.allFinite) {
|
|
16
|
+
if (validity === undefined) {
|
|
17
|
+
for (let i = 0; i < col.length; i += 1)
|
|
18
|
+
s += values[i];
|
|
19
|
+
return s;
|
|
20
|
+
}
|
|
21
|
+
const bits = validity.bits;
|
|
22
|
+
for (let i = 0; i < col.length; i += 1) {
|
|
23
|
+
if ((bits[i >> 3] & (1 << (i & 7))) !== 0)
|
|
24
|
+
s += values[i];
|
|
25
|
+
}
|
|
26
|
+
return s;
|
|
27
|
+
}
|
|
28
|
+
// Guarded path: finiteness not proven, skip non-finite per policy.
|
|
10
29
|
if (validity === undefined) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
30
|
+
for (let i = 0; i < col.length; i += 1) {
|
|
31
|
+
const v = values[i];
|
|
32
|
+
if (Number.isFinite(v))
|
|
33
|
+
s += v;
|
|
34
|
+
}
|
|
14
35
|
return s;
|
|
15
36
|
}
|
|
16
37
|
// Inline bitmap check rather than method dispatch — same pattern
|
|
17
38
|
// the chart-friction-spike notes flagged for hot draw loops.
|
|
18
39
|
const bits = validity.bits;
|
|
19
40
|
for (let i = 0; i < col.length; i += 1) {
|
|
20
|
-
if ((bits[i >> 3] & (1 << (i & 7))) !== 0)
|
|
21
|
-
|
|
41
|
+
if ((bits[i >> 3] & (1 << (i & 7))) !== 0) {
|
|
42
|
+
const v = values[i];
|
|
43
|
+
if (Number.isFinite(v))
|
|
44
|
+
s += v;
|
|
45
|
+
}
|
|
22
46
|
}
|
|
23
47
|
return s;
|
|
24
48
|
},
|