|
| 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