Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-median-overflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/table-core': patch
---

Fix even-length median aggregation overflowing to `Infinity` when middle values are large and finite
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,15 @@ export const aggregationFn_median = /* @__PURE__ */ constructAggregationFn<
if (!values.length) return undefined
values.sort((a, b) => a - b)
const mid = Math.floor(values.length / 2)
return values.length % 2
? values[mid]
: (values[mid - 1]! + values[mid]!) / 2
if (values.length % 2) {
return values[mid]
}
const a = values[mid - 1]!
const b = values[mid]!
const sum = a + b
return Number.isFinite(sum) || !Number.isFinite(a) || !Number.isFinite(b)
? sum / 2
: a / 2 + b / 2
},
})

Expand Down
37 changes: 37 additions & 0 deletions packages/table-core/tests/unit/fns/aggregationFns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,43 @@ describe('aggregation function definitions', () => {
).toBeUndefined()
})

it('does not overflow median for large finite values and preserves subnormals', () => {
// Large finite values whose intermediate sum overflows to Infinity
expect(
aggregationFn_median.aggregate(
context([Number.MAX_VALUE, Number.MAX_VALUE]),
),
).toBe(Number.MAX_VALUE)
expect(
aggregationFn_median.aggregate(
context([-Number.MAX_VALUE, -Number.MAX_VALUE]),
),
).toBe(-Number.MAX_VALUE)
expect(
aggregationFn_median.aggregate(context([1e308, 1.5e308])),
).toBe(1.25e308)

// Subnormal / tiny values are preserved without underflow to 0
expect(
aggregationFn_median.aggregate(
context([Number.MIN_VALUE, Number.MIN_VALUE]),
),
).toBe(Number.MIN_VALUE)

// Infinity, NaN, and control checks
expect(
aggregationFn_median.aggregate(context([Infinity, Infinity])),
).toBe(Infinity)
expect(
aggregationFn_median.aggregate(context([-Infinity, -Infinity])),
).toBe(-Infinity)
expect(
Number.isNaN(
aggregationFn_median.aggregate(context([-Infinity, Infinity]))!,
),
).toBe(true)
})

it('uses Set semantics for unique values', () => {
expect(
aggregationFn_unique.aggregate(context(['a', null, undefined, 'a'])),
Expand Down