From 6b1b0ab74b9fc29292d733c1b8624d7934677dde Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 10 Apr 2026 09:47:35 +0800 Subject: [PATCH 1/3] fix: aggregation precisionAdd precisionSub --- .../dataset/aggregation-float.test.ts | 17 ++++ .../src/ts-types/dataset/aggregation.ts | 85 ++++++++++--------- 2 files changed, 61 insertions(+), 41 deletions(-) create mode 100644 packages/vtable/__tests__/dataset/aggregation-float.test.ts diff --git a/packages/vtable/__tests__/dataset/aggregation-float.test.ts b/packages/vtable/__tests__/dataset/aggregation-float.test.ts new file mode 100644 index 0000000000..9ebf2ae371 --- /dev/null +++ b/packages/vtable/__tests__/dataset/aggregation-float.test.ts @@ -0,0 +1,17 @@ +import { SumAggregator } from '../../src/ts-types/dataset/aggregation'; + +describe('SumAggregator floating point precision', () => { + it('should sum 0.1 and 0.2 exactly to 0.3', () => { + const aggregator = new SumAggregator({ key: 'test', field: 'value', isRecord: false }); + aggregator.push({ value: 0.1 }); + aggregator.push({ value: 0.2 }); + expect(aggregator.value()).toBe(0.3); + }); + + it('should subtract correctly', () => { + const aggregator = new SumAggregator({ key: 'test', field: 'value', isRecord: false }); + aggregator.push({ value: 0.3 }); + aggregator.deleteRecord({ value: 0.1 }); + expect(aggregator.value()).toBe(0.2); + }); +}); diff --git a/packages/vtable/src/ts-types/dataset/aggregation.ts b/packages/vtable/src/ts-types/dataset/aggregation.ts index 6a88f29a18..50317bb1af 100644 --- a/packages/vtable/src/ts-types/dataset/aggregation.ts +++ b/packages/vtable/src/ts-types/dataset/aggregation.ts @@ -1,4 +1,4 @@ -import { isValid } from '@visactor/vutils'; +import { isValid, precisionAdd, precisionSub } from '@visactor/vutils'; import type { SortOrder } from '..'; import { AggregationType, SortType } from '..'; import type { BaseTableAPI } from '../base-table'; @@ -409,22 +409,22 @@ export class SumAggregator extends Aggregator { if (record.isAggregator && this.children) { this.children.push(record); const value = record.value(); - this.sum += value ?? 0; + this.sum = precisionAdd(this.sum, value ?? 0); if (this.needSplitPositiveAndNegativeForSum) { if (value > 0) { - this.positiveSum += value; + this.positiveSum = precisionAdd(this.positiveSum, value); } else if (value < 0) { - this.nagetiveSum += value; + this.nagetiveSum = precisionAdd(this.nagetiveSum, value); } } } else if (this.field && !isNaN(parseFloat(record[this.field]))) { const value = parseFloat(record[this.field]); - this.sum += value; + this.sum = precisionAdd(this.sum, value); if (this.needSplitPositiveAndNegativeForSum) { if (value > 0) { - this.positiveSum += value; + this.positiveSum = precisionAdd(this.positiveSum, value); } else if (value < 0) { - this.nagetiveSum += value; + this.nagetiveSum = precisionAdd(this.nagetiveSum, value); } } } @@ -439,22 +439,22 @@ export class SumAggregator extends Aggregator { if (record.isAggregator && this.children) { this.children = this.children.filter(item => item !== record); const value = record.value(); - this.sum -= value ?? 0; + this.sum = precisionSub(this.sum, value ?? 0); if (this.needSplitPositiveAndNegativeForSum) { if (value > 0) { - this.positiveSum -= value; + this.positiveSum = precisionSub(this.positiveSum, value); } else if (value < 0) { - this.nagetiveSum -= value; + this.nagetiveSum = precisionSub(this.nagetiveSum, value); } } } else if (this.field && !isNaN(parseFloat(record[this.field]))) { const value = parseFloat(record[this.field]); - this.sum -= value; + this.sum = precisionSub(this.sum, value); if (this.needSplitPositiveAndNegativeForSum) { if (value > 0) { - this.positiveSum -= value; + this.positiveSum = precisionSub(this.positiveSum, value); } else if (value < 0) { - this.nagetiveSum -= value; + this.nagetiveSum = precisionSub(this.nagetiveSum, value); } } } @@ -476,33 +476,33 @@ export class SumAggregator extends Aggregator { this.children = this.children.filter(item => item !== oldRecord); const newValue = newRecord.value(); this.children.push(newRecord); - this.sum += newValue - oldValue; + this.sum = precisionAdd(this.sum, precisionSub(newValue, oldValue)); if (this.needSplitPositiveAndNegativeForSum) { if (oldValue > 0) { - this.positiveSum -= oldValue; + this.positiveSum = precisionSub(this.positiveSum, oldValue); } else if (oldValue < 0) { - this.nagetiveSum -= oldValue; + this.nagetiveSum = precisionSub(this.nagetiveSum, oldValue); } if (newValue > 0) { - this.positiveSum += newValue; + this.positiveSum = precisionAdd(this.positiveSum, newValue); } else if (newValue < 0) { - this.nagetiveSum += newValue; + this.nagetiveSum = precisionAdd(this.nagetiveSum, newValue); } } } else if (this.field && !isNaN(parseFloat(oldRecord[this.field]))) { const oldValue = parseFloat(oldRecord[this.field]); const newValue = parseFloat(newRecord[this.field]); - this.sum += newValue - oldValue; + this.sum = precisionAdd(this.sum, precisionSub(newValue, oldValue)); if (this.needSplitPositiveAndNegativeForSum) { if (oldValue > 0) { - this.positiveSum -= oldValue; + this.positiveSum = precisionSub(this.positiveSum, oldValue); } else if (oldValue < 0) { - this.nagetiveSum -= oldValue; + this.nagetiveSum = precisionSub(this.nagetiveSum, oldValue); } if (newValue > 0) { - this.positiveSum += newValue; + this.positiveSum = precisionAdd(this.positiveSum, newValue); } else if (newValue < 0) { - this.nagetiveSum += newValue; + this.nagetiveSum = precisionAdd(this.nagetiveSum, newValue); } } } @@ -531,12 +531,12 @@ export class SumAggregator extends Aggregator { const child = this.children[i]; if (child.isAggregator) { const value = child.value(); - this.sum += value ?? 0; + this.sum = precisionAdd(this.sum, value ?? 0); if (this.needSplitPositiveAndNegativeForSum) { if (value > 0) { - this.positiveSum += value; + this.positiveSum = precisionAdd(this.positiveSum, value); } else if (value < 0) { - this.nagetiveSum += value; + this.nagetiveSum = precisionAdd(this.nagetiveSum, value); } } } @@ -546,22 +546,22 @@ export class SumAggregator extends Aggregator { const record = this.records[i]; if (record.isAggregator) { const value = record.value(); - this.sum += value ?? 0; + this.sum = precisionAdd(this.sum, value ?? 0); if (this.needSplitPositiveAndNegativeForSum) { if (value > 0) { - this.positiveSum += value; + this.positiveSum = precisionAdd(this.positiveSum, value); } else if (value < 0) { - this.nagetiveSum += value; + this.nagetiveSum = precisionAdd(this.nagetiveSum, value); } } } else if (this.field && !isNaN(parseFloat(record[this.field]))) { const value = parseFloat(record[this.field]); - this.sum += value; + this.sum = precisionAdd(this.sum, value); if (this.needSplitPositiveAndNegativeForSum) { if (value > 0) { - this.positiveSum += value; + this.positiveSum = precisionAdd(this.positiveSum, value); } else if (value < 0) { - this.nagetiveSum += value; + this.nagetiveSum = precisionAdd(this.nagetiveSum, value); } } } @@ -687,10 +687,10 @@ export class AvgAggregator extends Aggregator { if (this.children) { this.children.push(record); } - this.sum += record.sum; + this.sum = precisionAdd(this.sum, record.sum); this.count += record.count; } else if (this.field && !isNaN(parseFloat(record[this.field]))) { - this.sum += parseFloat(record[this.field]); + this.sum = precisionAdd(this.sum, parseFloat(record[this.field])); this.count++; } } @@ -705,10 +705,10 @@ export class AvgAggregator extends Aggregator { if (this.children) { this.children = this.children.filter(item => item !== record); } - this.sum -= record.sum; + this.sum = precisionSub(this.sum, record.sum); this.count -= record.count; } else if (this.field && !isNaN(parseFloat(record[this.field]))) { - this.sum -= parseFloat(record[this.field]); + this.sum = precisionSub(this.sum, parseFloat(record[this.field])); this.count--; } } @@ -733,10 +733,13 @@ export class AvgAggregator extends Aggregator { return item; }); } - this.sum += newRecord.sum - oldRecord.sum; + this.sum = precisionAdd(this.sum, precisionSub(newRecord.sum, oldRecord.sum)); this.count += newRecord.count - oldRecord.count; } else if (this.field && !isNaN(parseFloat(oldRecord[this.field]))) { - this.sum += parseFloat(newRecord[this.field]) - parseFloat(oldRecord[this.field]); + this.sum = precisionAdd( + this.sum, + precisionSub(parseFloat(newRecord[this.field]), parseFloat(oldRecord[this.field])) + ); // this.count++; } this.clearCacheValue(); @@ -761,7 +764,7 @@ export class AvgAggregator extends Aggregator { const child = this.children[i]; if (child.isAggregator && child.type === AggregationType.AVG) { const childValue = child.value(); - this.sum += childValue * (child as AvgAggregator).count; + this.sum = precisionAdd(this.sum, childValue * (child as AvgAggregator).count); this.count += (child as AvgAggregator).count; } } @@ -769,10 +772,10 @@ export class AvgAggregator extends Aggregator { for (let i = 0; i < this.records.length; i++) { const record = this.records[i]; if (record.isAggregator && record.type === AggregationType.AVG) { - this.sum += record.sum; + this.sum = precisionAdd(this.sum, record.sum); this.count += record.count; } else if (this.field && !isNaN(parseFloat(record[this.field]))) { - this.sum += parseFloat(record[this.field]); + this.sum = precisionAdd(this.sum, parseFloat(record[this.field])); this.count++; } } From e3816ab427208a6c6cfb972cdd24fa5528258b34 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 10 Apr 2026 09:47:57 +0800 Subject: [PATCH 2/3] docs: update changlog of rush --- .../5102-bug-aggregation-float_2026-04-10-01-47.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/5102-bug-aggregation-float_2026-04-10-01-47.json diff --git a/common/changes/@visactor/vtable/5102-bug-aggregation-float_2026-04-10-01-47.json b/common/changes/@visactor/vtable/5102-bug-aggregation-float_2026-04-10-01-47.json new file mode 100644 index 0000000000..6ab1afabc4 --- /dev/null +++ b/common/changes/@visactor/vtable/5102-bug-aggregation-float_2026-04-10-01-47.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "fix: aggregation precisionAdd precisionSub\n\n", + "type": "none", + "packageName": "@visactor/vtable" + } + ], + "packageName": "@visactor/vtable", + "email": "892739385@qq.com" +} \ No newline at end of file From b23408c7064037cac0a8e62f4314f3aa2f095f44 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 10 Apr 2026 11:36:38 +0800 Subject: [PATCH 3/3] fix: aggregation error --- packages/vtable/__tests__/pivotChart.test.ts | 4 +- .../src/ts-types/dataset/aggregation.ts | 70 ++++++++++++------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/packages/vtable/__tests__/pivotChart.test.ts b/packages/vtable/__tests__/pivotChart.test.ts index f2abe620a9..729075a0b8 100644 --- a/packages/vtable/__tests__/pivotChart.test.ts +++ b/packages/vtable/__tests__/pivotChart.test.ts @@ -9487,7 +9487,7 @@ describe('pivotChart init test', () => { }, 二级: { max: 81585.58783792704, - min: -15135.001037597656, + min: -15135.001037597654, positiveMax: 81585.58783792704, negativeMin: -19659.724044799805 }, @@ -9598,7 +9598,7 @@ describe('pivotChart init test', () => { }, '230713150305011': { 一级: { - max: 40780.32046222687, + max: 40780.32046222686, min: -22801.40795326233, positiveMax: 44028.34812831879, negativeMin: -22801.40795326233 diff --git a/packages/vtable/src/ts-types/dataset/aggregation.ts b/packages/vtable/src/ts-types/dataset/aggregation.ts index 50317bb1af..e6e4df96d9 100644 --- a/packages/vtable/src/ts-types/dataset/aggregation.ts +++ b/packages/vtable/src/ts-types/dataset/aggregation.ts @@ -434,10 +434,16 @@ export class SumAggregator extends Aggregator { deleteRecord(record: any) { if (record) { if (this.isRecord && this.records) { - this.records = this.records.filter(item => item !== record); + const index = this.records.indexOf(record); + if (index !== -1) { + this.records.splice(index, 1); + } } if (record.isAggregator && this.children) { - this.children = this.children.filter(item => item !== record); + const index = this.children.indexOf(record); + if (index !== -1) { + this.children.splice(index, 1); + } const value = record.value(); this.sum = precisionSub(this.sum, value ?? 0); if (this.needSplitPositiveAndNegativeForSum) { @@ -464,18 +470,18 @@ export class SumAggregator extends Aggregator { updateRecord(oldRecord: any, newRecord: any): void { if (oldRecord && newRecord) { if (this.isRecord && this.records) { - this.records = this.records.map(item => { - if (item === oldRecord) { - return newRecord; - } - return item; - }); + const index = this.records.indexOf(oldRecord); + if (index !== -1) { + this.records[index] = newRecord; + } } if (oldRecord.isAggregator && this.children) { const oldValue = oldRecord.value(); - this.children = this.children.filter(item => item !== oldRecord); + const index = this.children.indexOf(oldRecord); + if (index !== -1) { + this.children[index] = newRecord; + } const newValue = newRecord.value(); - this.children.push(newRecord); this.sum = precisionAdd(this.sum, precisionSub(newValue, oldValue)); if (this.needSplitPositiveAndNegativeForSum) { if (oldValue > 0) { @@ -510,7 +516,10 @@ export class SumAggregator extends Aggregator { } } value() { - return this.changedValue ?? (this.records?.length >= 1 ? this.sum : undefined); + return ( + this.changedValue ?? + (this.records && this.records.length >= 1 ? this.sum : this.isRecord === false ? this.sum : undefined) + ); } positiveValue() { return this.positiveSum; @@ -699,11 +708,17 @@ export class AvgAggregator extends Aggregator { deleteRecord(record: any) { if (record) { if (this.isRecord && this.records) { - this.records = this.records.filter(item => item !== record); + const index = this.records.indexOf(record); + if (index !== -1) { + this.records.splice(index, 1); + } } if (record.isAggregator && record.type === AggregationType.AVG) { if (this.children) { - this.children = this.children.filter(item => item !== record); + const index = this.children.indexOf(record); + if (index !== -1) { + this.children.splice(index, 1); + } } this.sum = precisionSub(this.sum, record.sum); this.count -= record.count; @@ -717,21 +732,17 @@ export class AvgAggregator extends Aggregator { updateRecord(oldRecord: any, newRecord: any): void { if (oldRecord && newRecord) { if (this.isRecord && this.records) { - this.records = this.records.map(item => { - if (item === oldRecord) { - return newRecord; - } - return item; - }); + const index = this.records.indexOf(oldRecord); + if (index !== -1) { + this.records[index] = newRecord; + } } if (oldRecord.isAggregator && oldRecord.type === AggregationType.AVG) { if (this.children && newRecord.isAggregator) { - this.children = this.children.map(item => { - if (item === oldRecord) { - return newRecord; - } - return item; - }); + const index = this.children.indexOf(oldRecord); + if (index !== -1) { + this.children[index] = newRecord; + } } this.sum = precisionAdd(this.sum, precisionSub(newRecord.sum, oldRecord.sum)); this.count += newRecord.count - oldRecord.count; @@ -746,7 +757,14 @@ export class AvgAggregator extends Aggregator { } } value() { - return this.changedValue ?? (this.records?.length >= 1 ? this.sum / this.count : undefined); + return ( + this.changedValue ?? + (this.records && this.records.length >= 1 + ? this.sum / this.count + : this.isRecord === false && this.count > 0 + ? this.sum / this.count + : undefined) + ); } reset() { this.changedValue = undefined;