Skip to content

Commit

Permalink
v1.0.10
Browse files Browse the repository at this point in the history
- Optimize `Statistics` to allow computation of big numbers without overflow issues.
  • Loading branch information
gmpassos committed Dec 29, 2021
1 parent fdcb877 commit 2c38e8a
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.0.10

- Optimize `Statistics` to allow computation of big numbers without overflow issues.

## 1.0.9

- Added extensions:
Expand Down
74 changes: 64 additions & 10 deletions lib/src/statistics_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,12 @@ String? formatDecimal(Object? value,

/// A statistics summary of a numeric collection.
class Statistics<N extends num> extends DataEntry {
/// Represents the maximum safe integer in JavaScript: `(2^53 - 1)`
static final int maxSafeInt = 9007199254740991;

/// Square root of [maxSafeInt].
static final int maxSafeIntSqrt = math.sqrt(maxSafeInt).toInt();

/// The length/size of the numeric collection.
num length;

Expand Down Expand Up @@ -233,6 +239,12 @@ class Statistics<N extends num> extends DataEntry {
/// The total sum of squares of the numeric collection.
num squaresSum;

/// Same as [sum] but as a [BigInt].
BigInt sumBigInt;

/// Same as [squaresSum] but as a [BigInt].
BigInt squaresSumBigInt;

/// Returns the mean/average of the numeric collection.
double mean;

Expand All @@ -255,12 +267,18 @@ class Statistics<N extends num> extends DataEntry {
double? standardDeviation,
num? sum,
num? squaresSum,
BigInt? sumBigInt,
BigInt? squaresSumBigInt,
this.lowerStatistics,
this.upperStatistics,
}) : medianLow = medianLow ?? medianHigh,
sum = sum ?? (mean! * length),
squaresSum =
squaresSum ?? ((standardDeviation! * standardDeviation) * length),
sumBigInt = sumBigInt ?? sum?.toBigInt() ?? (mean! * length).toBigInt(),
squaresSumBigInt = squaresSumBigInt ??
squaresSum?.toBigInt() ??
((standardDeviation! * standardDeviation) * length).toBigInt(),
mean = mean ?? (sum! / length),
standardDeviation =
standardDeviation ?? math.sqrt(squaresSum! / length);
Expand All @@ -286,10 +304,12 @@ class Statistics<N extends num> extends DataEntry {
/// This allows some usage optimization, do not pass an inconsistent value.
/// - [computeLowerAndUpper] if `true` will compute [lowerStatistics] and [upperStatistics].
/// - [keepData] if `true` will keep a copy of [data] at [data].
/// - [useBigIntToCompute] if `true` will force use of [BigInt] for internal computation to avoid overflow.
factory Statistics.compute(Iterable<N> data,
{bool alreadySortedData = false,
bool computeLowerAndUpper = true,
bool keepData = false}) {
bool keepData = false,
bool useBigIntToCompute = false}) {
var length = data.length;
if (length == 0) {
var statistics = Statistics._empty(data);
Expand Down Expand Up @@ -328,18 +348,50 @@ class Statistics<N extends num> extends DataEntry {
}
}

num sum = first;
var squaresSum = first * first;
num sum;
num squaresSum;
BigInt sumBigInt;
BigInt squaresSumBigInt;

for (var i = 1; i < length; ++i) {
var n = listSorted[i];
sum += n;
squaresSum += n * n;
}
double mean;
double standardDeviation;

if (useBigIntToCompute || max > (maxSafeIntSqrt ~/ length)) {
var firstBigInt = first.toBigInt();
sumBigInt = firstBigInt;
squaresSumBigInt = firstBigInt * firstBigInt;

for (var i = 1; i < length; ++i) {
var n = listSorted[i];
var nBigInt = n.toBigInt();
sumBigInt += nBigInt;
squaresSumBigInt += nBigInt * nBigInt;
}

var mean = sum / length;
sum = sumBigInt.toInt();
squaresSum = squaresSumBigInt.toInt();

var standardDeviation = math.sqrt(squaresSum / length);
var lengthBigInt = length.toBigInt();

mean = sumBigInt / lengthBigInt;
standardDeviation = math.sqrt(squaresSumBigInt / lengthBigInt);
} else {
sum = first;
squaresSum = first * first;

for (var i = 1; i < length; ++i) {
var n = listSorted[i];
sum += n;
squaresSum += n * n;
}

sumBigInt = sum.toBigInt();
squaresSumBigInt = squaresSum.toBigInt();

mean = sum / length;

standardDeviation = math.sqrt(squaresSum / length);
}

Statistics<N>? lowerStatistics;
Statistics<N>? upperStatistics;
Expand Down Expand Up @@ -370,6 +422,8 @@ class Statistics<N extends num> extends DataEntry {
medianHigh: medianHigh,
sum: sum,
squaresSum: squaresSum,
sumBigInt: sumBigInt,
squaresSumBigInt: squaresSumBigInt,
mean: mean,
standardDeviation: standardDeviation,
lowerStatistics: lowerStatistics,
Expand Down
6 changes: 6 additions & 0 deletions lib/src/statistics_extension_num.dart
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,9 @@ extension NumExtension on num {
}
}

/// Converts this `num` to [BigInt].
BigInt toBigInt() => BigInt.from(this);

/// Returns the square of `this` number.
num get square => this * this;

Expand All @@ -911,6 +914,9 @@ extension NumExtension on num {

/// extension for `double`.
extension DoubleExtension on double {
/// Converts this `double` to [BigInt].
BigInt toBigInt() => BigInt.from(this);

/// Returns the square of `this` number.
double get square => this * this;

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: statistics
description: Statistics package for easy and efficient data manipulation with many built-in mathematical functions and tools.
version: 1.0.9
version: 1.0.10
homepage: https://github.com/gmpassos/statistics

environment:
Expand Down
21 changes: 20 additions & 1 deletion test/statistics_base_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,29 @@ void main() {
expect(statistics.medianLow, equals(0));
expect(statistics.medianHigh, equals(0));
expect(statistics.median, equals(0));

expect(data.statisticsWithData.data, equals(data));
});

test('BigInt', () {
var statistics = Statistics.compute([
Statistics.maxSafeInt ~/ 2,
Statistics.maxSafeInt,
Statistics.maxSafeInt ~/ 2
], useBigIntToCompute: true);

expect(
statistics.sumBigInt,
equals(BigInt.from(Statistics.maxSafeInt) +
BigInt.from(Statistics.maxSafeInt) -
BigInt.one));

expect(
statistics.mean,
equals(
(BigInt.from(Statistics.maxSafeInt) ~/ 3.toBigInt() * BigInt.two)
.toDouble()));
});

test('error(alreadySortedData)', () {
expect(() => Statistics.compute([10, 20, 30], alreadySortedData: false),
isNot(throwsArgumentError));
Expand Down

0 comments on commit 2c38e8a

Please sign in to comment.