Skip to content

Commit 7b2740b

Browse files
committed
chore: changeset for the compensated sum / avg on every face
Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY Co-authored-by: Claude <noreply@anthropic.com>
1 parent e07690e commit 7b2740b

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
'@objectstack/core': minor
3+
'@objectstack/objectql': patch
4+
'@objectstack/driver-memory': patch
5+
'@objectstack/service-analytics': patch
6+
---
7+
8+
fix: `sum` / `avg` answer the same double on every face the platform owns, added with one compensated fold that `@objectstack/core` now exports as `compensatedSum` (#20544)
9+
10+
Clause-②: yes
11+
12+
**New export.** `@objectstack/core` exports `compensatedSum(nums)`: the sum of
13+
`nums`, added in order with Kahan-Babuska-Neumaier compensation, which is the
14+
summation SQLite (3.43 and later) uses for its own `sum` and `avg`. It moved
15+
here from `@objectstack/objectql`'s rows path (`in-memory-aggregation.ts`),
16+
which now imports it instead of keeping a private copy.
17+
18+
**What changed.** Three folds still added a group's values naively, and now call
19+
the same function:
20+
21+
- `@objectstack/driver-memory`'s `aggregate()` and `find()` with aggregations,
22+
the path `engine.aggregate` takes on an in-memory datasource;
23+
- `@objectstack/driver-memory`'s analytics face (`MemoryAnalyticsService`),
24+
whose `sum` / `avg` measures are now a `$group` `$accumulator` in place of
25+
mingo's `$sum` / `$avg`;
26+
- `@objectstack/service-analytics`' draft preview.
27+
28+
Over a `number` column holding `0.1`, `0.2` and `0.3`, each of them answered
29+
`0.6000000000000001` / `0.20000000000000004`. They now answer `0.6` /
30+
`0.19999999999999998`, as SQLite and the engine's rows path do. Over
31+
`1e16, 1, -1e16` they answered `0` and now answer `1`. On driver-memory,
32+
`engine.aggregate` gave two answers depending on its path: `having { s: { $eq:
33+
0.6 } }` kept the group on the rows path and dropped it on the native path. It
34+
now keeps it on both.
35+
36+
**What did not move.** Two addends, integers whose running total stays within
37+
2^53, and a non-finite total give the same answer as before. Which values count
38+
as addends did not change either: booleans as 1 / 0, and nulls and non-numeric
39+
strings left out, as each face already had it. `count`, `min` and `max` are
40+
untouched. The analytics face's pipeline dump (`result.sql`) now renders the
41+
accumulator's functions by name, so a `sum` measure and an `avg` measure still
42+
dump differently.
43+
44+
**Residual.** PostgreSQL and MySQL add their doubles natively without
45+
compensation, and the platform does not wrap that arithmetic. So over three or
46+
more fractions their native path can still differ from these faces in the last
47+
place. An exact `$eq` on a fractional sum compares doubles; compare with a range.

0 commit comments

Comments
 (0)