From 820257487f487feb3ad3597001d309c5700dd452 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 8 Apr 2025 18:57:22 -0300 Subject: [PATCH 01/23] v1.2.1 - New `StatisticsBigInt`. - New `IterableBigIntExtension`. - Numeric extensions: - Added `sum2` and `mean2`: returns a record with extra parameter `allEqualsTo`. - `mean` and `standardDeviation` are computed in a way that minimizes floating-point precision errors. - `statistics_base_test.dart`: improved numeric tests. --- CHANGELOG.md | 11 + lib/src/statistics_base.dart | 454 +++++++++++++++++++- lib/src/statistics_extension_num.dart | 592 +++++++++++++++++++++++++- pubspec.yaml | 2 +- test/statistics_base_test.dart | 199 ++++++++- 5 files changed, 1239 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9326cbc..09a0aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.2.1 + +- New `StatisticsBigInt`. +- New `IterableBigIntExtension`. + +- Numeric extensions: + - Added `sum2` and `mean2`: returns a record with extra parameter `allEqualsTo`. + - `mean` and `standardDeviation` are computed in a way that minimizes floating-point precision errors. + +- `statistics_base_test.dart`: improved numeric tests. + ## 1.2.0 - sdk: '>=3.6.0 <4.0.0' diff --git a/lib/src/statistics_base.dart b/lib/src/statistics_base.dart index e61f03e..63a9875 100644 --- a/lib/src/statistics_base.dart +++ b/lib/src/statistics_base.dart @@ -306,7 +306,8 @@ class Statistics extends DataEntry { squaresSumBigInt ??= squaresSum!.toBigInt(); return StandardDeviationComputerBigInt._compute( - sumBigInt, squaresSumBigInt, length); + sumBigInt, squaresSumBigInt, length.toInt()) + .toDouble(); } else { sum ??= sumBigInt!.toInt(); squaresSum ??= squaresSumBigInt!.toInt(); @@ -665,6 +666,444 @@ class Statistics extends DataEntry { [mean, standardDeviation, length, min, max, sum, squaresSum]; } +/// A statistics summary of a [BigInt] numeric collection. +/// See [Statistics]. +class StatisticsBigInt extends DataEntry { + /// The length/size of the numeric collection. + int length; + + /// Returns `true` if [length] == `0`. + bool get isEmpty => length == 0; + + /// Returns `true` if [length] != `0`. + bool get isNotEmpty => !isEmpty; + + /// The minimal value of the numeric collection. + BigInt min; + + /// The maximum value of the numeric collection. + BigInt max; + + /// The center value of the numeric collection. + /// (Equivalent to [medianHigh]). + BigInt get center => medianHigh; + + /// The center value index of the numeric collection: ([length] ~/ `2`) + /// (Equivalent to [medianHighIndex]). + int get centerIndex => medianHighIndex; + + /// The lower median value. See [median]. + BigInt medianLow; + + /// The [medianLow] value index. + int get medianLowIndex => + length % 2 == 0 ? medianHighIndex - 1 : medianHighIndex; + + /// The higher median value. See [median]. + BigInt medianHigh; + + /// The [medianHigh] value index. + int get medianHighIndex => length ~/ 2; + + /// The median value. Also the average between [medianLow] and [medianHigh]. + /// + /// - For sets of odd size the median is a single value, that separates the + /// higher half from the lower half of the set. (In this case [medianLow] and [medianHigh] are the same value). + /// - For sets of even size the median is the average of a pair of values, the [medianLow] and + /// [medianHigh] values. + Decimal get median { + final medianHigh = this.medianHigh; + // To avoid any unnecessary calculation: + if (medianLow == medianHigh) { + return medianHigh.toDecimal(); + } + + var median = + (medianLow + medianHigh).toDynamicInt().divideBigInt(BigInt.two); + return median.toDecimal(); + } + + /// The total sum of the numeric collection. + BigInt sum; + + /// The total sum of squares of the numeric collection. + BigInt squaresSum; + + /// Returns the mean/average of the numeric collection. + Decimal mean; + + /// The standard deviation of the numeric collection. + Decimal standardDeviation; + + /// Returns the computed [Statistics] of the lower part of the numeric collection, from index `0` (inclusive) to [centerIndex] (exclusive). + StatisticsBigInt? lowerStatistics; + + /// Returns the computed [Statistics] of the upper part of the numeric collection, from index [centerIndex] (inclusive) to [length] (exclusive). + StatisticsBigInt? upperStatistics; + + StatisticsBigInt( + this.length, + this.min, + this.max, { + BigInt? medianLow, + required this.medianHigh, + Decimal? mean, + Decimal? standardDeviation, + BigInt? sum, + BigInt? squaresSum, + this.lowerStatistics, + this.upperStatistics, + }) : medianLow = medianLow ?? medianHigh, + sum = sum ?? mean!.multiplyInt(length).toBigInt(), + squaresSum = squaresSum ?? + standardDeviation! + .multiplyDecimal(standardDeviation) + .multiplyInt(length), + mean = mean ?? sum!.toDecimal().multiplyInt(length), + standardDeviation = standardDeviation ?? + _computeStandardDeviation(sum, squaresSum, length); + + static Decimal _computeStandardDeviation( + BigInt? sum, BigInt? squaresSum, int length) { + return StandardDeviationComputerBigInt._compute(sum!, squaresSum!, length); + } + + factory StatisticsBigInt._empty(Iterable list) { + var zero = BigInt.zero; + return StatisticsBigInt(0, zero, zero, + medianHigh: zero, sum: zero, squaresSum: zero); + } + + factory StatisticsBigInt._single(BigInt n) { + var nDecimal = n.toDecimal(); + return StatisticsBigInt(1, n, n, + medianHigh: n, + sum: n, + squaresSum: n * n, + mean: nDecimal, + standardDeviation: Decimal.zero); + } + + /// Computes a [Statistics] summary from [data]. + /// + /// - [alreadySortedData] if `true` will avoid sorting of [data]. + /// 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 StatisticsBigInt.compute(Iterable data, + {bool alreadySortedData = false, + bool computeLowerAndUpper = true, + bool keepData = false, + bool useBigIntToCompute = false}) { + var length = data.length; + if (length == 0) { + var statistics = StatisticsBigInt._empty(data); + if (keepData) { + statistics.data = data.toList(); + } + return statistics; + } + + if (length == 1) { + var statistics = StatisticsBigInt._single(data.first); + if (keepData) { + statistics.data = data.toList(); + } + return statistics; + } + + var listSorted = data.toList(); + if (!alreadySortedData) { + listSorted.sort(); + } + + var first = listSorted.first; + var min = first; + var max = listSorted.last; + + var evenSet = length % 2 == 0; + var medianHighIndex = length ~/ 2; + var medianHigh = listSorted[medianHighIndex]; + var medianLow = evenSet ? listSorted[medianHighIndex - 1] : medianHigh; + + if (alreadySortedData) { + if (min > max || medianLow > medianHigh) { + throw ArgumentError( + "Inconsistent argument 'alreadySortedData': min:$min > max:$max ; medianLow:$medianLow > medianHigh:$medianHigh"); + } + } + + BigInt sum; + BigInt squaresSum; + + Decimal mean; + Decimal standardDeviation; + + if (true) { + sum = first; + squaresSum = first * first; + + for (var i = 1; i < length; ++i) { + var n = listSorted[i]; + var nBigInt = n; + sum += nBigInt; + squaresSum += nBigInt * nBigInt; + } + + mean = sum.toDecimal().divideInt(length); + + standardDeviation = _computeStandardDeviation(sum, squaresSum, length); + } + + StatisticsBigInt? lowerStatistics; + StatisticsBigInt? upperStatistics; + + if (computeLowerAndUpper) { + List lower; + List upper; + + if (evenSet) { + lower = listSorted.sublist(0, medianHighIndex); + upper = listSorted.sublist(medianHighIndex); + } else { + lower = listSorted.sublist(0, medianHighIndex + 1); + upper = listSorted.sublist(medianHighIndex); + } + + lowerStatistics = StatisticsBigInt.compute(lower, + computeLowerAndUpper: false, keepData: false); + upperStatistics = StatisticsBigInt.compute(upper, + computeLowerAndUpper: false, keepData: false); + } + + var statistics = StatisticsBigInt( + length, + min, + max, + medianLow: medianLow, + medianHigh: medianHigh, + sum: sum, + squaresSum: squaresSum, + mean: mean, + standardDeviation: standardDeviation, + lowerStatistics: lowerStatistics, + upperStatistics: upperStatistics, + ); + + if (keepData) { + statistics.data = data.toList(); + } + + return statistics; + } + + /// Casts this instance to `Statistics`. + Statistics cast() { + if (T == int) { + if (this is Statistics) { + return this as Statistics; + } + + return Statistics( + length, + min.toInt(), + max.toInt(), + medianLow: medianLow.toInt(), + medianHigh: medianHigh.toInt(), + mean: mean.toDouble(), + standardDeviation: standardDeviation.toDouble(), + sum: sum.toInt(), + squaresSum: squaresSum.toInt(), + lowerStatistics: lowerStatistics?.cast(), + upperStatistics: upperStatistics?.cast(), + ) as Statistics; + } else if (T == double) { + if (this is Statistics) { + return this as Statistics; + } + + return Statistics( + length, + min.toDouble(), + max.toDouble(), + medianLow: medianLow.toDouble(), + medianHigh: medianHigh.toDouble(), + mean: mean.toDouble(), + standardDeviation: standardDeviation.toDouble(), + sum: sum.toDouble(), + squaresSum: squaresSum.toDouble(), + lowerStatistics: lowerStatistics?.cast(), + upperStatistics: upperStatistics?.cast(), + ) as Statistics; + } else if (T == num) { + return Statistics( + length, + min.toInt(), + max.toInt(), + medianLow: medianLow.toInt(), + medianHigh: medianHigh.toInt(), + mean: mean.toDouble(), + standardDeviation: standardDeviation.toDouble(), + sum: sum.toInt(), + squaresSum: squaresSum.toInt(), + lowerStatistics: lowerStatistics?.cast(), + upperStatistics: upperStatistics?.cast(), + ) as Statistics; + } else { + return this as Statistics; + } + } + + List? data; + + /// Returns `true` if [mean] is in range of [minMean] and [maxMean]. + /// Also checks if [standardDeviation] is in range of [minDeviation] and [maxDeviation] (if passed). + bool isMeanInRange(DynamicNumber minMean, DynamicNumber maxMean, + [DynamicNumber? minDeviation, DynamicNumber? maxDeviation]) { + if (mean >= minMean && mean <= maxMean) { + if (minDeviation != null || maxDeviation != null) { + var stdv = standardDeviation; + + if (minDeviation != null && stdv < minDeviation) return false; + + if (maxDeviation != null && stdv > maxDeviation) return false; + } + + return true; + } + + return false; + } + + /// Returns the mean of [squares]. + Decimal get squaresMean { + if (length == 0) return Decimal.zero; + return squaresSum.toDecimal().divideInt(length); + } + + @override + String toString({int precision = 4}) { + if (length == 0) { + return '{empty}'; + } + + var minStr = formatDecimal(min, precision: precision); + var maxStr = formatDecimal(max, precision: precision); + var centerStr = formatDecimal(center, precision: precision); + + var meanStr = formatDecimal(mean, precision: precision); + var standardDeviationStr = + formatDecimal(standardDeviation, precision: precision); + + return '{~$meanStr +-$standardDeviationStr [$minStr..($centerStr)..$maxStr] #$length}'; + } + + /// Multiply this statistics fields by [n]. + StatisticsBigInt multiplyBy(DynamicNumber n) { + return StatisticsBigInt( + n.multiplyInt(length).toInt(), + n.moduloBigInt(min).toBigInt(), + n.moduloBigInt(max).toBigInt(), + medianLow: n.moduloBigInt(medianLow).toBigInt(), + medianHigh: n.moduloBigInt(medianHigh).toBigInt(), + sum: n.moduloBigInt(sum).toBigInt(), + squaresSum: n.moduloBigInt(squaresSum).toBigInt(), + mean: n.multiplyDecimal(mean).toDecimal(), + standardDeviation: (standardDeviation * n), + ); + } + + /// Divide this statistics fields by [n]. + StatisticsBigInt divideBy(DynamicNumber n) { + if (isEmpty) return this; + + return StatisticsBigInt( + n.divideInt(length).toInt(), + n.divideBigInt(min).toBigInt(), + n.divideBigInt(max).toBigInt(), + medianLow: n.divideBigInt(medianLow).toBigInt(), + medianHigh: n.divideBigInt(medianHigh).toBigInt(), + sum: n.divideBigInt(sum).toBigInt(), + squaresSum: n.divideBigInt(squaresSum).toBigInt(), + mean: mean / n, + standardDeviation: (standardDeviation / n), + ); + } + + /// Sum this statistics fields with [other] fields. + StatisticsBigInt sumWith(StatisticsBigInt other) { + return StatisticsBigInt( + length + other.length, + min + other.min, + max + other.max, + medianLow: medianLow + other.medianLow, + medianHigh: medianHigh + other.medianHigh, + sum: sum + other.sum, + squaresSum: squaresSum + other.squaresSum, + mean: mean + other.mean, + standardDeviation: (standardDeviation + other.standardDeviation), + ); + } + + StatisticsBigInt operator /(StatisticsBigInt other) { + // TODO: use StatisticsDecimal + return StatisticsBigInt( + (length / other.length).toInt(), + (min.toDynamicInt() / other.min.toDynamicInt()).toBigInt(), + (max.toDynamicInt() / other.max.toDynamicInt()).toBigInt(), + medianLow: (medianLow.toDynamicInt() / other.medianLow.toDynamicInt()) + .toBigInt(), + medianHigh: (medianHigh.toDynamicInt() / other.medianHigh.toDynamicInt()) + .toBigInt(), + sum: (sum.toDynamicInt() / other.sum.toDynamicInt()).toBigInt(), + squaresSum: (squaresSum.toDynamicInt() / other.squaresSum.toDynamicInt()) + .toBigInt(), + mean: mean / other.mean, + standardDeviation: (standardDeviation / other.standardDeviation), + ); + } + + StatisticsBigInt operator +(StatisticsBigInt other) { + return StatisticsBigInt( + length + other.length, + min.min(other.min), + max.max(other.max), + medianLow: (medianLow + other.medianLow) + .toDynamicInt() + .divideAsDynamicInt(DynamicInt.two) + .toBigInt(), + medianHigh: (medianHigh + other.medianHigh) + .toDynamicInt() + .divideAsDynamicInt(DynamicInt.two) + .toBigInt(), + sum: sum + other.sum, + squaresSum: squaresSum + other.squaresSum, + mean: (sum + other.sum) + .toDynamicInt() + .divideInt(length + other.length) + .toDecimal(), + standardDeviation: _computeStandardDeviation(sum + other.sum, + squaresSum + other.squaresSum, length + other.length), + ); + } + + @override + List getDataFields() => [ + 'mean', + 'standardDeviation', + 'length', + 'min', + 'max', + 'sum', + 'squaresSum' + ]; + + @override + List getDataValues() => + [mean, standardDeviation, length, min, max, sum, squaresSum]; +} + /// Interface for data entries. abstract class DataEntry { /// Returns the fields names of this [DataEntry]. @@ -1014,14 +1453,15 @@ class StandardDeviationComputerBigInt double _standardDeviationImpl() { if (isEmpty) return 0.0; - return _compute(_sum, _squaresSum, _length); + return _compute(_sum, _squaresSum, _length).toDouble(); } - static double _compute(BigInt sum, BigInt squaresSum, num length) { - var lengthBigInt = length.toBigInt(); - return math - .sqrt(((squaresSum * lengthBigInt) - (sum * sum)) / lengthBigInt) / - math.sqrt(length); + static Decimal _compute(BigInt sum, BigInt squaresSum, int length) { + if (length == 0) return Decimal.zero; + + var a = ((squaresSum * length.toBigInt()) - (sum * sum)).toDecimal(); + var x = a.divide(length); + return x.squareRoot / length.toDecimal().squareRoot; } @override diff --git a/lib/src/statistics_extension_num.dart b/lib/src/statistics_extension_num.dart index 8817d6a..5dd3592 100644 --- a/lib/src/statistics_extension_num.dart +++ b/lib/src/statistics_extension_num.dart @@ -5,8 +5,8 @@ import 'package:collection/collection.dart'; import 'package:intl/intl.dart'; import 'statistics_base.dart'; -import 'statistics_dynamic_int.dart'; import 'statistics_decimal.dart'; +import 'statistics_dynamic_int.dart'; /// Extension for [Type]. extension NumericTypeExtension on Type { @@ -116,6 +116,35 @@ extension IterableNumExtension on Iterable { return total; } + /// Returns the [sum] of all elements in the iterable, along with an optional + /// `allEqualsTo` value that is set only if all elements are equal. + /// + /// If the iterable is empty, `sum` is `0.0` and `allEqualsTo` is `null`. + /// Otherwise, `sum` is the total of all elements, and `allEqualsTo` holds + /// the common value if all elements are the same; otherwise, it is `null`. + ({num sum, num? allEqualsTo}) get sum2 { + var itr = iterator; + + if (!itr.moveNext()) { + return (sum: 0.0, allEqualsTo: null); + } + + var first = itr.current; + var total = first; + + var allEqualsTo = true; + + while (itr.moveNext()) { + var current = itr.current; + total += current; + if (current != first) { + allEqualsTo = false; + } + } + + return (sum: total, allEqualsTo: allEqualsTo ? first : null); + } + /// Returns the sum of squares of this numeric collection. num get sumSquares { var itr = iterator; @@ -136,7 +165,40 @@ extension IterableNumExtension on Iterable { } /// Returns the mean/average of this numeric collection. - double get mean => sum / length; + double get mean { + var sumInfo = sum2; + + var sum = sumInfo.sum; + var allEqualsTo = sumInfo.allEqualsTo; + + if (allEqualsTo != null) { + return allEqualsTo.toDouble(); + } else { + return sum / length; + } + } + + /// Returns the mean (average) of this numeric collection, along with an optional + /// `allEqualsTo` value if all elements are the same. + /// + /// If all elements are equal, `mean` and `allEqualsTo` will both be set to that value, + /// avoiding floating-point precision issues. Otherwise, `mean` is computed normally, + /// and `allEqualsTo` is `null`. + ({double mean, num? allEqualsTo}) get mean2 { + var sumInfo = sum2; + + var sum = sumInfo.sum; + var allEqualsTo = sumInfo.allEqualsTo; + + // If all elements are the same, + // avoid any floating-point precision issues when summing: + if (allEqualsTo != null) { + return (mean: allEqualsTo.toDouble(), allEqualsTo: allEqualsTo); + } else { + var mean = sum / length; + return (mean: mean, allEqualsTo: null); + } + } /// Returns the standard deviation of this numeric collection. double get standardDeviation { @@ -146,7 +208,14 @@ extension IterableNumExtension on Iterable { return 0.0; } - var average = mean; + var meanInfo = mean2; + + var average = meanInfo.mean; + var allEqualsTo = meanInfo.allEqualsTo; + + if (allEqualsTo != null) { + return 0.0; + } var first = itr.current - average; var total = first * first; @@ -391,6 +460,8 @@ extension IterableDoubleExtension on Iterable { Statistics.compute(this, keepData: true); /// Returns the sum of this numeric collection. + /// + /// If the iterable is empty, `sum` is `0.0`. double get sum { var itr = iterator; @@ -407,6 +478,35 @@ extension IterableDoubleExtension on Iterable { return total; } + /// Returns the [sum] of all elements in the iterable, along with an optional + /// `allEqualsTo` value that is set only if all elements are equal. + /// + /// If the iterable is empty, `sum` is `0.0` and `allEqualsTo` is `null`. + /// Otherwise, `sum` is the total of all elements, and `allEqualsTo` holds + /// the common value if all elements are the same; otherwise, it is `null`. + ({double sum, double? allEqualsTo}) get sum2 { + var itr = iterator; + + if (!itr.moveNext()) { + return (sum: 0.0, allEqualsTo: null); + } + + var first = itr.current; + var total = first; + + var allEquals = true; + + while (itr.moveNext()) { + var current = itr.current; + total += current; + if (current != first) { + allEquals = false; + } + } + + return (sum: total, allEqualsTo: allEquals ? first : null); + } + /// Returns the sum of squares of this numeric collection. double get sumSquares { var itr = iterator; @@ -427,7 +527,42 @@ extension IterableDoubleExtension on Iterable { } /// Returns the mean/average of this numeric collection. - double get mean => sum / length; + double get mean { + var sumInfo = sum2; + + var sum = sumInfo.sum; + var allEqualsTo = sumInfo.allEqualsTo; + + // If all elements are the same, + // avoid any floating-point precision issues when summing: + if (allEqualsTo != null) { + return allEqualsTo; + } else { + return sum / length; + } + } + + /// Returns the mean (average) of this numeric collection, along with an optional + /// `allEqualsTo` value if all elements are the same. + /// + /// If all elements are equal, `mean` and `allEqualsTo` will both be set to that value, + /// avoiding floating-point precision issues. Otherwise, `mean` is computed normally, + /// and `allEqualsTo` is `null`. + ({double mean, double? allEqualsTo}) get mean2 { + var sumInfo = sum2; + + var sum = sumInfo.sum; + var allEqualsTo = sumInfo.allEqualsTo; + + // If all elements are the same, + // avoid any floating-point precision issues when summing: + if (allEqualsTo != null) { + return (mean: allEqualsTo, allEqualsTo: allEqualsTo); + } else { + var mean = sum / length; + return (mean: mean, allEqualsTo: null); + } + } /// Returns the standard deviation of this numeric collection. double get standardDeviation { @@ -437,7 +572,14 @@ extension IterableDoubleExtension on Iterable { return 0.0; } - var average = mean; + var meanInfo = mean2; + + var average = meanInfo.mean; + var allEqualsTo = meanInfo.allEqualsTo; + + if (allEqualsTo != null) { + return 0.0; + } var first = itr.current - average; var total = first * first; @@ -699,6 +841,35 @@ extension IterableIntExtension on Iterable { return total; } + /// Returns the [sum] of all elements in the iterable, along with an optional + /// `allEqualsTo` value that is set only if all elements are equal. + /// + /// If the iterable is empty, `sum` is `0.0` and `allEqualsTo` is `null`. + /// Otherwise, `sum` is the total of all elements, and `allEqualsTo` holds + /// the common value if all elements are the same; otherwise, it is `null`. + ({int sum, int? allEqualsTo}) get sum2 { + var itr = iterator; + + if (!itr.moveNext()) { + return (sum: 0, allEqualsTo: null); + } + + var first = itr.current; + var total = first; + + var allEqualsTo = true; + + while (itr.moveNext()) { + var current = itr.current; + total += current; + if (current != first) { + allEqualsTo = false; + } + } + + return (sum: total, allEqualsTo: allEqualsTo ? first : null); + } + /// Returns the sum of squares of this numeric collection. int get sumSquares { var itr = iterator; @@ -719,7 +890,40 @@ extension IterableIntExtension on Iterable { } /// Returns the mean/average of this numeric collection. - double get mean => sum / length; + double get mean { + var sumInfo = sum2; + + var sum = sumInfo.sum; + var allEqualsTo = sumInfo.allEqualsTo; + + if (allEqualsTo != null) { + return allEqualsTo.toDouble(); + } else { + return sum / length; + } + } + + /// Returns the mean (average) of this numeric collection, along with an optional + /// `allEqualsTo` value if all elements are the same. + /// + /// If all elements are equal, `mean` and `allEqualsTo` will both be set to that value, + /// avoiding floating-point precision issues. Otherwise, `mean` is computed normally, + /// and `allEqualsTo` is `null`. + ({double mean, int? allEqualsTo}) get mean2 { + var sumInfo = sum2; + + var sum = sumInfo.sum; + var allEqualsTo = sumInfo.allEqualsTo; + + // If all elements are the same, + // avoid any floating-point precision issues when summing: + if (allEqualsTo != null) { + return (mean: allEqualsTo.toDouble(), allEqualsTo: allEqualsTo); + } else { + var mean = sum / length; + return (mean: mean, allEqualsTo: null); + } + } /// Returns the standard deviation of this numeric collection. double get standardDeviation { @@ -729,7 +933,14 @@ extension IterableIntExtension on Iterable { return 0.0; } - var average = mean; + var meanInfo = mean2; + + var average = meanInfo.mean; + var allEqualsTo = meanInfo.allEqualsTo; + + if (allEqualsTo != null) { + return 0.0; + } var first = itr.current - average; var total = (first * first).toDouble(); @@ -939,6 +1150,369 @@ extension IterableIntExtension on Iterable { } } +/// extension for `Iterable`. +extension IterableBigIntExtension on Iterable { + /// Maps this numeric collection to a `List` using [f] to map each element. + List mapToList(T Function(BigInt n) f) => map(f).toList(); + + /// Maps this numeric collection to a `Set` using [f] to map each element. + Set mapToSet(T Function(BigInt n) f) => map(f).toSet(); + + /// Maps this numeric collection to a `List`. + List toIntsList() => mapToList((e) => e.toInt()); + + /// Maps this numeric collection to a `List`. + List toDoublesList() => mapToList((e) => e.toDouble()); + + /// Maps this numeric collection to a `List`. + List toBigIntList() => toList(); + + /// Maps this numeric collection to a `List`. + List toDynamicIntList() => + mapToList((e) => DynamicInt.fromBigInt(e)); + + /// Maps this numeric collection to a `List`. + List toDecimalList() => mapToList((e) => Decimal.fromBigInt(e)); + + /// Maps this numeric collection to a `List`. + List toStringsList() => mapToList((e) => e.toString()); + + /// Returns a [Statistics] of this numeric collection. + StatisticsBigInt get statistics => StatisticsBigInt.compute(this); + + /// Returns a [Statistics] of this numeric collection (with populated field [Statistics.data]). + StatisticsBigInt get statisticsWithData => + StatisticsBigInt.compute(this, keepData: true); + + /// Returns the sum of this numeric collection. + BigInt get sum { + var itr = iterator; + + if (!itr.moveNext()) { + return BigInt.zero; + } + + var total = itr.current; + + while (itr.moveNext()) { + total += itr.current; + } + + return total; + } + + /// Returns the [sum] of all elements in the iterable, along with an optional + /// `allEqualsTo` value that is set only if all elements are equal. + /// + /// If the iterable is empty, `sum` is `0.0` and `allEqualsTo` is `null`. + /// Otherwise, `sum` is the total of all elements, and `allEqualsTo` holds + /// the common value if all elements are the same; otherwise, it is `null`. + ({BigInt sum, BigInt? allEqualsTo}) get sum2 { + var itr = iterator; + + if (!itr.moveNext()) { + return (sum: BigInt.zero, allEqualsTo: null); + } + + var first = itr.current; + var total = first; + + var allEqualsTo = true; + + while (itr.moveNext()) { + var current = itr.current; + total += current; + if (current != first) { + allEqualsTo = false; + } + } + + return (sum: total, allEqualsTo: allEqualsTo ? first : null); + } + + /// Returns the sum of squares of this numeric collection. + BigInt get sumSquares { + var itr = iterator; + + if (!itr.moveNext()) { + return BigInt.zero; + } + + var first = itr.current; + var total = first * first; + + while (itr.moveNext()) { + var n = itr.current; + total += n * n; + } + + return total; + } + + /// Returns the mean/average of this numeric collection. + Decimal get mean { + var sumInfo = sum2; + + var sum = sumInfo.sum; + var allEqualsTo = sumInfo.allEqualsTo; + + if (allEqualsTo != null) { + return allEqualsTo.toDecimal(); + } else { + return sum.toDynamicInt().divideIntAsDecimal(length); + } + } + + /// Returns the mean (average) of this numeric collection, along with an optional + /// `allEqualsTo` value if all elements are the same. + /// + /// If all elements are equal, `mean` and `allEqualsTo` will both be set to that value, + /// avoiding floating-point precision issues. Otherwise, `mean` is computed normally, + /// and `allEqualsTo` is `null`. + ({Decimal mean, BigInt? allEqualsTo}) get mean2 { + var sumInfo = sum2; + + var sum = sumInfo.sum; + var allEqualsTo = sumInfo.allEqualsTo; + + if (allEqualsTo != null) { + return (mean: allEqualsTo.toDecimal(), allEqualsTo: allEqualsTo); + } else { + var mean = sum.toDynamicInt().divideIntAsDecimal(length); + return (mean: mean, allEqualsTo: null); + } + } + + /// Returns the standard deviation of this numeric collection. + Decimal get standardDeviation { + var itr = iterator; + + if (!itr.moveNext()) { + return Decimal.zero; + } + + var meanInfo = mean2; + + var average = meanInfo.mean; + var allEqualsTo = meanInfo.allEqualsTo; + + if (allEqualsTo != null) { + return Decimal.zero; + } + + var first = itr.current.toDynamicInt() - average; + var total = (first * first); + + while (itr.moveNext()) { + var n = itr.current.toDynamicInt() - average; + total += (n * n).toDynamicInt(); + } + + var deviation = (total / length.toDynamicInt()).squareRoot; + + return deviation; + } + + /// Returns `true` if this numeric collection is sorted. + bool get isSorted { + var itr = iterator; + + if (!itr.moveNext()) { + return false; + } + + var prev = itr.current; + + while (itr.moveNext()) { + var n = itr.current; + if (n < prev) return false; + prev = n; + } + + return true; + } + + /// Returns a sorted `List` of this numeric collection. + /// If this instance is already sorted and already a `List`, + /// returns `this` instance. + List asSortedList() { + List list; + if (this is List && isSorted) { + list = this as List; + } else { + list = toList(); + list.sort(); + } + return list; + } + + /// Return the median (middle value) of this numeric collection. + /// If [data] is empty, returns `null`. + Decimal? get median { + var data = asSortedList(); + + var n = data.length; + if (n < 1) { + return null; + } + + var halfN = (n ~/ 2); + + if (n % 2 == 1) { + return data[halfN].toDecimal(); + } else { + return (data[halfN - 1] + data[halfN]).toDynamicInt() / DynamicInt.two; + } + } + + /// Return the low median (middle value) this collection. + /// If [data] is empty, returns `null`. + BigInt? get medianLow { + var data = asSortedList(); + + var n = data.length; + if (n < 1) { + return null; + } + + var halfN = (n ~/ 2); + + if (n % 2 == 1) { + return data[halfN]; + } else { + return data[halfN - 1]; + } + } + + /// Return the low median (middle value) this collection. + /// If [data] is empty, returns `null`. + BigInt? get medianHigh { + var data = asSortedList(); + + var n = data.length; + if (n < 1) { + return null; + } + + var halfN = (n ~/ 2); + return data[halfN]; + } + + /// Returns the mean/average of squares of this numeric collection. + Decimal get squaresMean => + sumSquares.toDynamicInt().divideIntAsDecimal(length); + + /// Returns the squares of this numeric collection. + List get square => map((n) => n * n).toList(); + + /// Returns the square roots of this numeric collection. + List get squareRoot => + map((n) => n.toDynamicInt().squareRoot).toList(); + + /// Returns the absolute values of this numeric collection. + List get abs => map((n) => n.abs()).toList(); + + /// Returns the moving average of [samplesSize] of this numeric collection. + List movingAverage(int samplesSize) { + var length = this.length; + if (length == 0) return []; + + if (samplesSize >= length) return [mean]; + + var list = this is List ? (this as List) : toList(); + + var movingAverage = []; + for (var i = 0; i < length; ++i) { + var end = i + samplesSize; + if (end > length) break; + + BigInt total = BigInt.zero; + for (var j = i; j < end; ++j) { + var e = list[j]; + total += e; + } + + var average = total.toDynamicInt().divideIntAsDecimal(samplesSize); + movingAverage.add(average); + } + + return movingAverage; + } + + /// Merges this numeric collection with [other] using the [merge] function. + List merge( + Iterable other, BigInt Function(BigInt a, BigInt b) merger) => + mergeTo(other, merger, []); + + /// Merges this numeric collection with [other] using the [merge] function to [destiny]. + List mergeTo(Iterable other, + R Function(BigInt a, BigInt b) merger, List destiny) { + var itr1 = iterator; + if (!itr1.moveNext()) { + return destiny; + } + + var itr2 = other.iterator; + if (!itr2.moveNext()) { + return destiny; + } + + do { + var n1 = itr1.current; + var n2 = itr2.current; + var d = merger(n1, n2); + destiny.add(d); + } while (itr1.moveNext() && itr2.moveNext()); + + return destiny; + } + + /// Subtracts elements of `this` instance with [other] instance elements. + List operator -(Iterable other) => + merge(other, (a, b) => a - b); + + /// Multiplies elements of `this` instance with [other] instance elements. + List operator *(Iterable other) => + merge(other, (a, b) => a * b); + + /// Divides elements of `this` instance with [other] instance elements. + List operator /(Iterable other) => mergeTo( + other, (a, b) => a.toDynamicInt().divideBigIntAsDecimal(b), []); + + /// Divides (as `int`) elements of `this` instance with [other] instance elements. + List operator ~/(List other) => + mergeTo(other, (a, b) => a ~/ b, []); + + static final ListEquality _listEquality = ListEquality(); + + /// Returns `true` if [other] values are all equals, regarding the [tolerance]. + bool equalsValues(List other, {num tolerance = 0}) { + if (tolerance != 0) { + var length = this.length; + if (length != other.length) return false; + + tolerance = tolerance.abs(); + + var list = this is List ? (this as List) : toList(); + + for (var i = 0; i < length; ++i) { + var a = list[i]; + var b = other[i]; + var diff = (a - b).abs(); + + if (diff.toDouble() > tolerance) { + return false; + } + } + + return true; + } else { + var list = this is List ? (this as List) : toList(); + return _listEquality.equals(list, other); + } + } +} + /// extension for `num`. extension NumExtension on num { /// Cast this number to [N], where [N] extends [num]. @@ -1119,6 +1693,10 @@ extension IntExtension on int { /// extension for [BigInt]. extension BigIntExtension on BigInt { + BigInt min(BigInt other) => this <= other ? this : other; + + BigInt max(BigInt other) => this >= other ? this : other; + String get thousands => toInt().thousands; } diff --git a/pubspec.yaml b/pubspec.yaml index d381c08..84feb0a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: statistics description: Statistics package for easy and efficient data manipulation with built-in Bayesian Network (Bayes Net), many mathematical functions and tools. -version: 1.2.0 +version: 1.2.1 homepage: https://github.com/gmpassos/statistics environment: diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index 59a5480..b0c8cc5 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -152,160 +152,323 @@ void main() { test('int(4)', () { var data = [10, 20, 30, 40]; + var dataBigInt = data.toBigIntList(); + var statistics = data.statistics; + var statisticsBigInt = dataBigInt.statistics; expect(statistics.length, equals(4)); expect(statistics.isEmpty, isFalse); expect(statistics.isNotEmpty, isTrue); + expect(statisticsBigInt.length, equals(4)); + expect(statisticsBigInt.isEmpty, isFalse); + expect(statisticsBigInt.isNotEmpty, isTrue); + expect(statistics.sum, equals(100)); expect(statistics.mean, equals(25)); expect(statistics.standardDeviation, equals(11.180339887498949)); expect(statistics.squaresSum, equals(3000)); expect(statistics.squaresMean, equals(750.0)); + expect(statisticsBigInt.sum, equals(100.toBigInt())); + expect(statisticsBigInt.mean, equals(25.toDecimal())); + expect(statisticsBigInt.standardDeviation, + equals(Decimal.parse('11.180339887498948'))); + expect(statisticsBigInt.squaresSum, equals(3000.toBigInt())); + expect(statisticsBigInt.squaresMean, equals(750.toDecimal())); + expect(statistics.centerIndex, equals(2)); expect(statistics.center, equals(30)); + expect(statisticsBigInt.centerIndex, equals(2)); + expect(statisticsBigInt.center, equals(30.toBigInt())); + expect(statistics.medianLow, equals(20)); expect(statistics.medianHigh, equals(30)); expect(statistics.median, equals(25)); + expect(statisticsBigInt.medianLow, equals(20.toBigInt())); + expect(statisticsBigInt.medianHigh, equals(30.toBigInt())); + expect(statisticsBigInt.median, equals(25.toDecimal())); + expect(statistics.medianLowIndex, equals(1)); expect(statistics.medianHighIndex, equals(2)); + expect(statisticsBigInt.medianLowIndex, equals(1)); + expect(statisticsBigInt.medianHighIndex, equals(2)); + expect(statistics.isMeanInRange(10, 30), isTrue); expect(statistics.isMeanInRange(10, 30, 11, 12), isTrue); + expect( + statisticsBigInt.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt()), + isTrue); + + expect( + statisticsBigInt.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt(), + 11.toDynamicInt(), 12.toDynamicInt()), + isTrue); + expect(statistics.isMeanInRange(10, 19), isFalse); expect(statistics.isMeanInRange(10, 30, 10, 11), isFalse); + expect( + statisticsBigInt.isMeanInRange(10.toDynamicInt(), 19.toDynamicInt()), + isFalse); + expect( + statisticsBigInt.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt(), + 10.toDynamicInt(), 11.toDynamicInt()), + isFalse); + expect(statistics.toString(precision: 2), equals('{~25 +-11.18 [10..(30)..40] #4}')); expect(statistics.toString(precision: 0), equals('{~25 +-11 [10..(30)..40] #4}')); + expect(statisticsBigInt.toString(precision: 2), + equals('{~25 +-11.18 [10..(30)..40] #4}')); + expect(statisticsBigInt.toString(precision: 0), + equals('{~25 +-11 [10..(30)..40] #4}')); + expect(data.statisticsWithData.data, equals(data)); + expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); }); test('int(3)', () { var data = [10, 20, 30]; + var dataBigInt = data.toBigIntList(); + var statistics = data.statistics; + var statisticsBigInt = dataBigInt.statistics; expect(statistics.length, equals(3)); expect(statistics.isEmpty, isFalse); expect(statistics.isNotEmpty, isTrue); + expect(statisticsBigInt.length, equals(3)); + expect(statisticsBigInt.isEmpty, isFalse); + expect(statisticsBigInt.isNotEmpty, isTrue); + expect(statistics.sum, equals(60)); expect(statistics.mean, equals(20)); expect(statistics.standardDeviation, equals(8.16496580927726)); expect(statistics.squaresSum, equals(1400)); expect(statistics.squaresMean, equals(466.6666666666667)); + expect(statisticsBigInt.sum, equals(60.toBigInt())); + expect(statisticsBigInt.mean, equals(20.toDecimal())); + expect( + statisticsBigInt.standardDeviation, + equals( + Decimal.parse('8.1649658092772614292692508470403064717305671'))); + expect(statisticsBigInt.squaresSum, equals(1400.toBigInt())); + expect(statisticsBigInt.squaresMean, + equals(Decimal.parse('466.6666666666666666'))); + expect(statistics.centerIndex, equals(1)); expect(statistics.center, equals(20)); + expect(statisticsBigInt.centerIndex, equals(1)); + expect(statisticsBigInt.center, equals(20.toBigInt())); + expect(statistics.medianLow, equals(20)); expect(statistics.medianHigh, equals(20)); expect(statistics.median, equals(20)); + expect(statisticsBigInt.medianLow, equals(20.toBigInt())); + expect(statisticsBigInt.medianHigh, equals(20.toBigInt())); + expect(statisticsBigInt.median, equals(20.toDecimal())); + expect(statistics.medianLowIndex, equals(1)); expect(statistics.medianHighIndex, equals(1)); + expect(statisticsBigInt.medianLowIndex, equals(1)); + expect(statisticsBigInt.medianHighIndex, equals(1)); + expect(statistics.isMeanInRange(10, 30), isTrue); expect(statistics.isMeanInRange(10, 30, 7, 9), isTrue); + expect( + statisticsBigInt.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt()), + isTrue); + expect( + statisticsBigInt.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt(), + 7.toDynamicInt(), 9.toDynamicInt()), + isTrue); + expect(statistics.isMeanInRange(10, 19), isFalse); expect(statistics.isMeanInRange(10, 30, 7, 8), isFalse); + expect( + statisticsBigInt.isMeanInRange(10.toDynamicInt(), 19.toDynamicInt()), + isFalse); + expect( + statisticsBigInt.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt(), + 7.toDynamicInt(), 8.toDynamicInt()), + isFalse); + expect(statistics.toString(), equals('{~20 +-8.1649 [10..(20)..30] #3}')); expect(statistics.toString(precision: 0), equals('{~20 +-8 [10..(20)..30] #3}')); + expect(statisticsBigInt.toString(), + equals('{~20 +-8.1649 [10..(20)..30] #3}')); + expect(statisticsBigInt.toString(precision: 0), + equals('{~20 +-8 [10..(20)..30] #3}')); + expect(data.statisticsWithData.data, equals(data)); + expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); }); test('int(2)', () { var data = [10, 20]; + var dataBigInt = data.toBigIntList(); + var statistics = data.statistics; + var statisticsBigInt = dataBigInt.statistics; expect(statistics.length, equals(2)); expect(statistics.isEmpty, isFalse); expect(statistics.isNotEmpty, isTrue); + expect(statisticsBigInt.length, equals(2)); + expect(statisticsBigInt.isEmpty, isFalse); + expect(statisticsBigInt.isNotEmpty, isTrue); + expect(statistics.sum, equals(30)); expect(statistics.mean, equals(15)); expect(statistics.standardDeviation, equals(5.0)); expect(statistics.squaresSum, equals(500)); expect(statistics.squaresMean, equals(250.0)); + expect(statisticsBigInt.sum, equals(30.toBigInt())); + expect(statisticsBigInt.mean, equals(15.toDecimal())); + expect(statisticsBigInt.standardDeviation, equals(Decimal.parse('5.0'))); + expect(statisticsBigInt.squaresSum, equals(500.toBigInt())); + expect(statisticsBigInt.squaresMean, equals(Decimal.parse('250.0'))); + expect(statistics.centerIndex, equals(1)); expect(statistics.center, equals(20)); + expect(statisticsBigInt.centerIndex, equals(1)); + expect(statisticsBigInt.center, equals(20.toBigInt())); + expect(statistics.medianLow, equals(10)); expect(statistics.medianHigh, equals(20)); expect(statistics.median, equals(15)); + expect(statisticsBigInt.medianLow, equals(10.toBigInt())); + expect(statisticsBigInt.medianHigh, equals(20.toBigInt())); + expect(statisticsBigInt.median, equals(15.toDecimal())); + expect(statistics.medianLowIndex, equals(0)); expect(statistics.medianHighIndex, equals(1)); + expect(statisticsBigInt.medianLowIndex, equals(0)); + expect(statisticsBigInt.medianHighIndex, equals(1)); + expect(data.statisticsWithData.data, equals(data)); + expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); }); test('int(1)', () { var data = [10]; + var dataBigInt = data.toBigIntList(); + var statistics = data.statistics; + var statisticsBigInt = dataBigInt.statistics; expect(statistics.length, equals(1)); expect(statistics.isEmpty, isFalse); expect(statistics.isNotEmpty, isTrue); + expect(statisticsBigInt.length, equals(1)); + expect(statisticsBigInt.isEmpty, isFalse); + expect(statisticsBigInt.isNotEmpty, isTrue); + expect(statistics.sum, equals(10)); expect(statistics.mean, equals(10)); expect(statistics.standardDeviation, equals(0)); expect(statistics.squaresSum, equals(100)); expect(statistics.squaresMean, equals(100)); - expect(statistics.centerIndex, equals(0)); - expect(statistics.center, equals(10)); + expect(statisticsBigInt.sum, equals(10.toBigInt())); + expect(statisticsBigInt.mean, equals(10.toDecimal())); + expect(statisticsBigInt.standardDeviation, equals(Decimal.zero)); + expect(statisticsBigInt.squaresSum, equals(100.toBigInt())); + expect(statisticsBigInt.squaresMean, equals(100.toDecimal())); expect(statistics.centerIndex, equals(0)); expect(statistics.center, equals(10)); + expect(statisticsBigInt.centerIndex, equals(0)); + expect(statisticsBigInt.center, equals(10.toBigInt())); + expect(statistics.medianLow, equals(10)); expect(statistics.medianHigh, equals(10)); expect(statistics.median, equals(10)); + expect(statisticsBigInt.medianLow, equals(10.toBigInt())); + expect(statisticsBigInt.medianHigh, equals(10.toBigInt())); + expect(statisticsBigInt.median, equals(10.toDecimal())); + expect(statistics.medianLowIndex, equals(0)); expect(statistics.medianHighIndex, equals(0)); + expect(statisticsBigInt.medianLowIndex, equals(0)); + expect(statisticsBigInt.medianHighIndex, equals(0)); + expect(data.statisticsWithData.data, equals(data)); + expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); }); test('int(0)', () { var data = []; + var dataBigInt = data.toBigIntList(); + var statistics = data.statistics; + var statisticsBigInt = dataBigInt.statistics; expect(statistics.length, equals(0)); expect(statistics.isEmpty, isTrue); expect(statistics.isNotEmpty, isFalse); + expect(statisticsBigInt.length, equals(0)); + expect(statisticsBigInt.isEmpty, isTrue); + expect(statisticsBigInt.isNotEmpty, isFalse); + expect(statistics.sum, equals(0)); expect(statistics.mean, isNaN); expect(statistics.standardDeviation, isNaN); expect(statistics.squaresSum, equals(0)); expect(statistics.squaresMean, equals(isNaN)); + expect(statisticsBigInt.sum, equals(0.toBigInt())); + expect(statisticsBigInt.mean, Decimal.zero); + expect(statisticsBigInt.standardDeviation, Decimal.zero); + expect(statisticsBigInt.squaresSum, equals(0.toBigInt())); + expect(statisticsBigInt.squaresMean, equals(Decimal.zero)); + expect(statistics.centerIndex, equals(0)); expect(statistics.center, equals(0)); + expect(statisticsBigInt.centerIndex, equals(0)); + expect(statisticsBigInt.center, equals(0.toBigInt())); + expect(statistics.medianLow, equals(0)); expect(statistics.medianHigh, equals(0)); expect(statistics.median, equals(0)); + + expect(statisticsBigInt.medianLow, equals(0.toBigInt())); + expect(statisticsBigInt.medianHigh, equals(0.toBigInt())); + expect(statisticsBigInt.median, equals(Decimal.zero)); + expect(data.statisticsWithData.data, equals(data)); + expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); }); - test('BigInt', () { + test('useBigIntToCompute: true', () { var statistics = Statistics.compute([10, 20, 30], useBigIntToCompute: true); @@ -320,7 +483,7 @@ void main() { expect(statistics.standardDeviation, equals(8.16496580927726)); }); - test('BigInt (maxSafeInt)', () { + test('useBigIntToCompute: true (maxSafeInt)', () { var statistics = Statistics.compute([ Statistics.maxSafeInt ~/ 2, Statistics.maxSafeInt, @@ -340,6 +503,34 @@ void main() { .toDouble())); }); + test('standardDeviation = 0 (int)', () { + final data1 = [94.97, 94.97, 94.97, 94.97, 94.97, 94.97, 94.97]; + var standardDeviation = data1.standardDeviation; + final stdDev1 = standardDeviation; + print('Standard Deviation of data1: $stdDev1'); + + expect(standardDeviation, equals(0.0)); + }); + + test('standardDeviation = 0 (BigInt)', () { + final data = + [94.97, 94.97, 94.97, 94.97, 94.97, 94.97, 94.97].toBigIntList(); + var standardDeviation = data.standardDeviation; + final stdDev = standardDeviation; + print('Standard Deviation of data: $stdDev'); + + expect(standardDeviation, equals(Decimal.zero)); + }); + + test('standardDeviation = 0 (num)', () { + final data = [53.97, 53.97, 53.97, 53.97, 53.97, 53.97, 53.97]; + var standardDeviation = data.standardDeviation; + final stdDev = standardDeviation; + print('Standard Deviation of data: $stdDev'); + + expect(standardDeviation, equals(0.0)); + }); + test('error(alreadySortedData)', () { expect(() => Statistics.compute([10, 20, 30], alreadySortedData: false), isNot(throwsArgumentError)); From dfe9f55a8e62ab86f4fe9e589e42b6771c509911 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 8 Apr 2025 20:11:29 -0300 Subject: [PATCH 02/23] - `DynamicNumber`: - Added `min`, `max` and `cast``. - New `StatisticsDynamicNumber`. --- CHANGELOG.md | 5 +- lib/src/statistics_base.dart | 460 +++++++++++++++++++++++++++- lib/src/statistics_decimal.dart | 32 ++ lib/src/statistics_dynamic_int.dart | 48 +++ test/statistics_base_test.dart | 132 +++++++- 5 files changed, 657 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a0aac..9192878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## 1.2.1 -- New `StatisticsBigInt`. +- `DynamicNumber`: + - Added `min`, `max` and `cast()`. + +- New `StatisticsBigInt` and `StatisticsDynamicNumber`. - New `IterableBigIntExtension`. - Numeric extensions: diff --git a/lib/src/statistics_base.dart b/lib/src/statistics_base.dart index 63a9875..0096681 100644 --- a/lib/src/statistics_base.dart +++ b/lib/src/statistics_base.dart @@ -761,11 +761,11 @@ class StatisticsBigInt extends DataEntry { .multiplyInt(length), mean = mean ?? sum!.toDecimal().multiplyInt(length), standardDeviation = standardDeviation ?? - _computeStandardDeviation(sum, squaresSum, length); + _computeStandardDeviation(sum!, squaresSum!, length); static Decimal _computeStandardDeviation( - BigInt? sum, BigInt? squaresSum, int length) { - return StandardDeviationComputerBigInt._compute(sum!, squaresSum!, length); + BigInt sum, BigInt squaresSum, int length) { + return StandardDeviationComputerBigInt._compute(sum, squaresSum, length); } factory StatisticsBigInt._empty(Iterable list) { @@ -846,9 +846,8 @@ class StatisticsBigInt extends DataEntry { for (var i = 1; i < length; ++i) { var n = listSorted[i]; - var nBigInt = n; - sum += nBigInt; - squaresSum += nBigInt * nBigInt; + sum += n; + squaresSum += n * n; } mean = sum.toDecimal().divideInt(length); @@ -1003,12 +1002,12 @@ class StatisticsBigInt extends DataEntry { StatisticsBigInt multiplyBy(DynamicNumber n) { return StatisticsBigInt( n.multiplyInt(length).toInt(), - n.moduloBigInt(min).toBigInt(), - n.moduloBigInt(max).toBigInt(), - medianLow: n.moduloBigInt(medianLow).toBigInt(), - medianHigh: n.moduloBigInt(medianHigh).toBigInt(), - sum: n.moduloBigInt(sum).toBigInt(), - squaresSum: n.moduloBigInt(squaresSum).toBigInt(), + n.multiplyBigInt(min).toBigInt(), + n.multiplyBigInt(max).toBigInt(), + medianLow: n.multiplyBigInt(medianLow).toBigInt(), + medianHigh: n.multiplyBigInt(medianHigh).toBigInt(), + sum: n.multiplyBigInt(sum).toBigInt(), + squaresSum: n.multiplyBigInt(squaresSum).toBigInt(), mean: n.multiplyDecimal(mean).toDecimal(), standardDeviation: (standardDeviation * n), ); @@ -1104,6 +1103,443 @@ class StatisticsBigInt extends DataEntry { [mean, standardDeviation, length, min, max, sum, squaresSum]; } +/// A statistics summary of a [DynamicNumber] numeric collection. +/// See [Statistics]. +class StatisticsDynamicNumber> extends DataEntry { + /// The length/size of the numeric collection. + int length; + + /// Returns `true` if [length] == `0`. + bool get isEmpty => length == 0; + + /// Returns `true` if [length] != `0`. + bool get isNotEmpty => !isEmpty; + + /// The minimal value of the numeric collection. + N min; + + /// The maximum value of the numeric collection. + N max; + + /// The center value of the numeric collection. + /// (Equivalent to [medianHigh]). + N get center => medianHigh; + + /// The center value index of the numeric collection: ([length] ~/ `2`) + /// (Equivalent to [medianHighIndex]). + int get centerIndex => medianHighIndex; + + /// The lower median value. See [median]. + N medianLow; + + /// The [medianLow] value index. + int get medianLowIndex => + length % 2 == 0 ? medianHighIndex - 1 : medianHighIndex; + + /// The higher median value. See [median]. + N medianHigh; + + /// The [medianHigh] value index. + int get medianHighIndex => length ~/ 2; + + /// The median value. Also the average between [medianLow] and [medianHigh]. + /// + /// - For sets of odd size the median is a single value, that separates the + /// higher half from the lower half of the set. (In this case [medianLow] and [medianHigh] are the same value). + /// - For sets of even size the median is the average of a pair of values, the [medianLow] and + /// [medianHigh] values. + Decimal get median { + final medianHigh = this.medianHigh; + // To avoid any unnecessary calculation: + if (medianLow == medianHigh) { + return medianHigh.toDecimal(); + } + + var median = + (medianLow + medianHigh).toDynamicInt().divideBigInt(BigInt.two); + return median.toDecimal(); + } + + /// The total sum of the numeric collection. + N sum; + + /// The total sum of squares of the numeric collection. + N squaresSum; + + /// Returns the mean/average of the numeric collection. + Decimal mean; + + /// The standard deviation of the numeric collection. + Decimal standardDeviation; + + /// Returns the computed [Statistics] of the lower part of the numeric collection, from index `0` (inclusive) to [centerIndex] (exclusive). + StatisticsDynamicNumber? lowerStatistics; + + /// Returns the computed [Statistics] of the upper part of the numeric collection, from index [centerIndex] (inclusive) to [length] (exclusive). + StatisticsDynamicNumber? upperStatistics; + + StatisticsDynamicNumber( + this.length, + this.min, + this.max, { + N? medianLow, + required this.medianHigh, + Decimal? mean, + Decimal? standardDeviation, + N? sum, + N? squaresSum, + this.lowerStatistics, + this.upperStatistics, + }) : medianLow = medianLow ?? medianHigh, + sum = sum ?? mean!.multiplyInt(length).cast(), + squaresSum = squaresSum ?? + standardDeviation! + .multiplyDecimal(standardDeviation) + .multiplyInt(length), + mean = mean ?? sum!.toDecimal().multiplyInt(length), + standardDeviation = standardDeviation ?? + _computeStandardDeviation(sum!, squaresSum!, length); + + static Decimal _computeStandardDeviation>( + N sum, N squaresSum, int length) { + return StandardDeviationComputerDynamicNumber._compute( + sum, squaresSum, length); + } + + factory StatisticsDynamicNumber._empty(Iterable list) { + var zero = DynamicInt.zero.cast(); + return StatisticsDynamicNumber(0, zero, zero, + medianHigh: zero, sum: zero, squaresSum: zero); + } + + factory StatisticsDynamicNumber._single(N n) { + var nDecimal = n.toDecimal(); + return StatisticsDynamicNumber(1, n, n, + medianHigh: n, + sum: n, + squaresSum: (n * n).cast(), + mean: nDecimal, + standardDeviation: Decimal.zero); + } + + /// Computes a [Statistics] summary from [data]. + /// + /// - [alreadySortedData] if `true` will avoid sorting of [data]. + /// 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 StatisticsDynamicNumber.compute(Iterable data, + {bool alreadySortedData = false, + bool computeLowerAndUpper = true, + bool keepData = false, + bool useBigIntToCompute = false}) { + var length = data.length; + if (length == 0) { + var statistics = StatisticsDynamicNumber._empty(data); + if (keepData) { + statistics.data = data.toList(); + } + return statistics; + } + + if (length == 1) { + var statistics = StatisticsDynamicNumber._single(data.first); + if (keepData) { + statistics.data = data.toList(); + } + return statistics; + } + + var listSorted = data.toList(); + if (!alreadySortedData) { + listSorted.sort(); + } + + var first = listSorted.first; + var min = first; + var max = listSorted.last; + + var evenSet = length % 2 == 0; + var medianHighIndex = length ~/ 2; + var medianHigh = listSorted[medianHighIndex]; + var medianLow = evenSet ? listSorted[medianHighIndex - 1] : medianHigh; + + if (alreadySortedData) { + if (min > max || medianLow > medianHigh) { + throw ArgumentError( + "Inconsistent argument 'alreadySortedData': min:$min > max:$max ; medianLow:$medianLow > medianHigh:$medianHigh"); + } + } + + N sum; + N squaresSum; + + Decimal mean; + Decimal standardDeviation; + + if (true) { + sum = first; + squaresSum = (first * first).cast(); + + for (var i = 1; i < length; ++i) { + var n = listSorted[i]; + sum = (sum + n).cast(); + squaresSum = (squaresSum + (n * n)).cast(); + } + + mean = sum.divideIntAsDecimal(length); + + standardDeviation = _computeStandardDeviation(sum, squaresSum, length); + } + + StatisticsDynamicNumber? lowerStatistics; + StatisticsDynamicNumber? upperStatistics; + + if (computeLowerAndUpper) { + List lower; + List upper; + + if (evenSet) { + lower = listSorted.sublist(0, medianHighIndex); + upper = listSorted.sublist(medianHighIndex); + } else { + lower = listSorted.sublist(0, medianHighIndex + 1); + upper = listSorted.sublist(medianHighIndex); + } + + lowerStatistics = StatisticsDynamicNumber.compute(lower, + computeLowerAndUpper: false, keepData: false); + upperStatistics = StatisticsDynamicNumber.compute(upper, + computeLowerAndUpper: false, keepData: false); + } + + var statistics = StatisticsDynamicNumber( + length, + min, + max, + medianLow: medianLow, + medianHigh: medianHigh, + sum: sum, + squaresSum: squaresSum, + mean: mean, + standardDeviation: standardDeviation, + lowerStatistics: lowerStatistics, + upperStatistics: upperStatistics, + ); + + if (keepData) { + statistics.data = data.toList(); + } + + return statistics; + } + + /// Casts this instance to `Statistics`. + Statistics cast() { + if (T == int) { + if (this is Statistics) { + return this as Statistics; + } + + return Statistics( + length, + min.toInt(), + max.toInt(), + medianLow: medianLow.toInt(), + medianHigh: medianHigh.toInt(), + mean: mean.toDouble(), + standardDeviation: standardDeviation.toDouble(), + sum: sum.toInt(), + squaresSum: squaresSum.toInt(), + lowerStatistics: lowerStatistics?.cast(), + upperStatistics: upperStatistics?.cast(), + ) as Statistics; + } else if (T == double) { + if (this is Statistics) { + return this as Statistics; + } + + return Statistics( + length, + min.toDouble(), + max.toDouble(), + medianLow: medianLow.toDouble(), + medianHigh: medianHigh.toDouble(), + mean: mean.toDouble(), + standardDeviation: standardDeviation.toDouble(), + sum: sum.toDouble(), + squaresSum: squaresSum.toDouble(), + lowerStatistics: lowerStatistics?.cast(), + upperStatistics: upperStatistics?.cast(), + ) as Statistics; + } else if (T == num) { + return Statistics( + length, + min.toInt(), + max.toInt(), + medianLow: medianLow.toInt(), + medianHigh: medianHigh.toInt(), + mean: mean.toDouble(), + standardDeviation: standardDeviation.toDouble(), + sum: sum.toInt(), + squaresSum: squaresSum.toInt(), + lowerStatistics: lowerStatistics?.cast(), + upperStatistics: upperStatistics?.cast(), + ) as Statistics; + } else { + return this as Statistics; + } + } + + List? data; + + /// Returns `true` if [mean] is in range of [minMean] and [maxMean]. + /// Also checks if [standardDeviation] is in range of [minDeviation] and [maxDeviation] (if passed). + bool isMeanInRange(DynamicNumber minMean, DynamicNumber maxMean, + [DynamicNumber? minDeviation, DynamicNumber? maxDeviation]) { + if (mean >= minMean && mean <= maxMean) { + if (minDeviation != null || maxDeviation != null) { + var stdv = standardDeviation; + + if (minDeviation != null && stdv < minDeviation) return false; + + if (maxDeviation != null && stdv > maxDeviation) return false; + } + + return true; + } + + return false; + } + + /// Returns the mean of [squares]. + Decimal get squaresMean { + if (length == 0) return Decimal.zero; + return squaresSum.toDecimal().divideInt(length); + } + + @override + String toString({int precision = 4}) { + if (length == 0) { + return '{empty}'; + } + + var minStr = formatDecimal(min, precision: precision); + var maxStr = formatDecimal(max, precision: precision); + var centerStr = formatDecimal(center, precision: precision); + + var meanStr = formatDecimal(mean, precision: precision); + var standardDeviationStr = + formatDecimal(standardDeviation, precision: precision); + + return '{~$meanStr +-$standardDeviationStr [$minStr..($centerStr)..$maxStr] #$length}'; + } + + /// Multiply this statistics fields by [n]. + StatisticsDynamicNumber multiplyBy(DynamicNumber n) { + return StatisticsDynamicNumber( + n.multiplyInt(length).toInt(), + (n * min).cast(), + (n * max).cast(), + medianLow: (n * medianLow).cast(), + medianHigh: (n * medianHigh).cast(), + sum: (n * sum).cast(), + squaresSum: (n * squaresSum).cast(), + mean: (mean * n), + standardDeviation: (standardDeviation * n), + ); + } + + /// Divide this statistics fields by [n]. + StatisticsDynamicNumber divideBy(DynamicNumber n) { + if (isEmpty) return this; + + return StatisticsDynamicNumber( + n.divideInt(length).toInt(), + (n / min).cast(), + (n / max).cast(), + medianLow: (n / medianLow).cast(), + medianHigh: (n / medianHigh).cast(), + sum: (n / sum).cast(), + squaresSum: (n / squaresSum).cast(), + mean: mean / n, + standardDeviation: (standardDeviation / n), + ); + } + + /// Sum this statistics fields with [other] fields. + StatisticsDynamicNumber sumWith(StatisticsDynamicNumber other) { + return StatisticsDynamicNumber( + length + other.length, + (min + other.min).cast(), + (max + other.max).cast(), + medianLow: (medianLow + other.medianLow).cast(), + medianHigh: (medianHigh + other.medianHigh).cast(), + sum: (sum + other.sum).cast(), + squaresSum: (squaresSum + other.squaresSum).cast(), + mean: mean + other.mean, + standardDeviation: (standardDeviation + other.standardDeviation), + ); + } + + StatisticsDynamicNumber operator /(StatisticsDynamicNumber other) { + return StatisticsDynamicNumber( + (length / other.length).toInt(), + (min.toDynamicInt() / other.min.toDynamicInt()).cast(), + (max.toDynamicInt() / other.max.toDynamicInt()).cast(), + medianLow: + (medianLow.toDynamicInt() / other.medianLow.toDynamicInt()).cast(), + medianHigh: (medianHigh.toDynamicInt() / other.medianHigh.toDynamicInt()) + .cast(), + sum: (sum.toDynamicInt() / other.sum.toDynamicInt()).cast(), + squaresSum: (squaresSum.toDynamicInt() / other.squaresSum.toDynamicInt()) + .cast(), + mean: mean / other.mean, + standardDeviation: (standardDeviation / other.standardDeviation), + ); + } + + StatisticsDynamicNumber operator +(StatisticsDynamicNumber other) { + return StatisticsDynamicNumber( + length + other.length, + min.min(other.min), + max.max(other.max), + medianLow: (medianLow + other.medianLow) + .divideDynamicIntAsDecimal(DynamicInt.two) + .cast(), + medianHigh: (medianHigh + other.medianHigh) + .divideDynamicIntAsDecimal(DynamicInt.two) + .cast(), + sum: (sum + other.sum).cast(), + squaresSum: (squaresSum + other.squaresSum).cast(), + mean: (sum + other.sum) + .toDynamicInt() + .divideInt(length + other.length) + .toDecimal(), + standardDeviation: _computeStandardDeviation( + (sum + other.sum).cast(), + (squaresSum + other.squaresSum).cast(), + length + other.length), + ); + } + + @override + List getDataFields() => [ + 'mean', + 'standardDeviation', + 'length', + 'min', + 'max', + 'sum', + 'squaresSum' + ]; + + @override + List getDataValues() => + [mean, standardDeviation, length, min, max, sum, squaresSum]; +} + /// Interface for data entries. abstract class DataEntry { /// Returns the fields names of this [DataEntry]. diff --git a/lib/src/statistics_decimal.dart b/lib/src/statistics_decimal.dart index bcc598a..4393dec 100644 --- a/lib/src/statistics_decimal.dart +++ b/lib/src/statistics_decimal.dart @@ -1,5 +1,6 @@ import 'dart:math' as math; +import 'statistics_base.dart'; import 'statistics_dynamic_int.dart'; /// [Decimal] is a fast implementation of decimal numbers, @@ -938,6 +939,30 @@ class Decimal implements DynamicNumber { Decimal operator *(DynamicNumber other) => _multiplyOperation(other.toDecimal()); + @override + N min>(DynamicNumber other) { + return (this <= other ? this : other).cast(); + } + + @override + N max>(DynamicNumber other) { + return (this >= other ? this : other).cast(); + } + + @override + N cast>() { + var self = this; + if (self is N) { + return self as N; + } else if (N == DynamicInt) { + return toDynamicInt() as N; + } else if (N == Decimal) { + return toDecimal() as N; + } else { + throw TypeError(); + } + } + @override Decimal multiply(num n2) { if (n2 is int) { @@ -1630,6 +1655,13 @@ extension DecimalOnIterableBigIntExtension on Iterable { } extension DecimalOnIterableDecimalExtension on Iterable { + StatisticsDynamicNumber get statistics => + StatisticsDynamicNumber.compute(this); + + /// Returns a [Statistics] of this numeric collection (with populated field [StatisticsDynamicNumber.data]). + StatisticsDynamicNumber get statisticsWithData => + StatisticsDynamicNumber.compute(this, keepData: true); + Iterable get asDynamicInt => map((e) => e.toDynamicInt()); /// Returns the sum of this numeric collection. diff --git a/lib/src/statistics_dynamic_int.dart b/lib/src/statistics_dynamic_int.dart index c177e98..ae062a1 100644 --- a/lib/src/statistics_dynamic_int.dart +++ b/lib/src/statistics_dynamic_int.dart @@ -245,6 +245,23 @@ abstract class DynamicNumber> DynamicNumber operator -(DynamicNumber other); + N min>(DynamicNumber other); + + N max>(DynamicNumber other); + + N cast>() { + var self = this; + if (self is N) { + return self as N; + } else if (N == DynamicInt) { + return toDynamicInt() as N; + } else if (N == Decimal) { + return toDecimal() as N; + } else { + throw TypeError(); + } + } + T multiplyBigInt(BigInt n2); T multiplyInt(int n2); @@ -681,6 +698,30 @@ abstract class DynamicInt implements DynamicNumber { } } + @override + N min>(DynamicNumber other) { + return (this <= other ? this : other).cast(); + } + + @override + N max>(DynamicNumber other) { + return (this >= other ? this : other).cast(); + } + + @override + N cast>() { + var self = this; + if (self is N) { + return self as N; + } else if (N == DynamicInt) { + return toDynamicInt() as N; + } else if (N == Decimal) { + return toDecimal() as N; + } else { + throw TypeError(); + } + } + @override DynamicInt abs() => isNegative ? -this : this; @@ -1651,6 +1692,13 @@ extension DynamicIntOnIterableDynamicNumberExtension } extension DynamicIntOnIterableDynamicIntExtension on Iterable { + StatisticsDynamicNumber get statistics => + StatisticsDynamicNumber.compute(this); + + /// Returns a [Statistics] of this numeric collection (with populated field [StatisticsDynamicNumber.data]). + StatisticsDynamicNumber get statisticsWithData => + StatisticsDynamicNumber.compute(this, keepData: true); + Iterable get asDecimal => map((e) => e.toDecimal()); /// Returns the sum of this numeric collection. diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index b0c8cc5..58b7fcb 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -153,9 +153,13 @@ void main() { test('int(4)', () { var data = [10, 20, 30, 40]; var dataBigInt = data.toBigIntList(); + var dataDynamicInt = data.toDynamicIntList(); + var dataDecimal = data.toDecimalList(); var statistics = data.statistics; var statisticsBigInt = dataBigInt.statistics; + var statisticsDynamicInt = dataDynamicInt.statistics; + var statisticsDecimal = dataDecimal.statistics; expect(statistics.length, equals(4)); expect(statistics.isEmpty, isFalse); @@ -165,6 +169,14 @@ void main() { expect(statisticsBigInt.isEmpty, isFalse); expect(statisticsBigInt.isNotEmpty, isTrue); + expect(statisticsDynamicInt.length, equals(4)); + expect(statisticsDynamicInt.isEmpty, isFalse); + expect(statisticsDynamicInt.isNotEmpty, isTrue); + + expect(statisticsDecimal.length, equals(4)); + expect(statisticsDecimal.isEmpty, isFalse); + expect(statisticsDecimal.isNotEmpty, isTrue); + expect(statistics.sum, equals(100)); expect(statistics.mean, equals(25)); expect(statistics.standardDeviation, equals(11.180339887498949)); @@ -178,12 +190,32 @@ void main() { expect(statisticsBigInt.squaresSum, equals(3000.toBigInt())); expect(statisticsBigInt.squaresMean, equals(750.toDecimal())); + expect(statisticsDynamicInt.sum, equals(100.toDynamicInt())); + expect(statisticsDynamicInt.mean, equals(25.toDecimal())); + expect(statisticsDynamicInt.standardDeviation, + equals(Decimal.parse('11.180339887498948'))); + expect(statisticsDynamicInt.squaresSum, equals(3000.toDynamicInt())); + expect(statisticsDynamicInt.squaresMean, equals(750.toDecimal())); + + expect(statisticsDecimal.sum, equals(100.toDynamicInt())); + expect(statisticsDecimal.mean, equals(25.toDecimal())); + expect(statisticsDecimal.standardDeviation, + equals(Decimal.parse('11.180339887498948'))); + expect(statisticsDecimal.squaresSum, equals(3000.toDynamicInt())); + expect(statisticsDecimal.squaresMean, equals(750.toDecimal())); + expect(statistics.centerIndex, equals(2)); expect(statistics.center, equals(30)); expect(statisticsBigInt.centerIndex, equals(2)); expect(statisticsBigInt.center, equals(30.toBigInt())); + expect(statisticsDynamicInt.centerIndex, equals(2)); + expect(statisticsDynamicInt.center, equals(30.toDynamicInt())); + + expect(statisticsDecimal.centerIndex, equals(2)); + expect(statisticsDecimal.center, equals(30.toDynamicInt())); + expect(statistics.medianLow, equals(20)); expect(statistics.medianHigh, equals(30)); expect(statistics.median, equals(25)); @@ -192,24 +224,54 @@ void main() { expect(statisticsBigInt.medianHigh, equals(30.toBigInt())); expect(statisticsBigInt.median, equals(25.toDecimal())); + expect(statisticsDynamicInt.medianLow, equals(20.toDynamicInt())); + expect(statisticsDynamicInt.medianHigh, equals(30.toDynamicInt())); + expect(statisticsDynamicInt.median, equals(25.toDecimal())); + + expect(statisticsDecimal.medianLow, equals(20.toDynamicInt())); + expect(statisticsDecimal.medianHigh, equals(30.toDynamicInt())); + expect(statisticsDecimal.median, equals(25.toDecimal())); + expect(statistics.medianLowIndex, equals(1)); expect(statistics.medianHighIndex, equals(2)); expect(statisticsBigInt.medianLowIndex, equals(1)); expect(statisticsBigInt.medianHighIndex, equals(2)); + expect(statisticsDynamicInt.medianLowIndex, equals(1)); + expect(statisticsDynamicInt.medianHighIndex, equals(2)); + + expect(statisticsDecimal.medianLowIndex, equals(1)); + expect(statisticsDecimal.medianHighIndex, equals(2)); + expect(statistics.isMeanInRange(10, 30), isTrue); expect(statistics.isMeanInRange(10, 30, 11, 12), isTrue); expect( statisticsBigInt.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt()), isTrue); - expect( statisticsBigInt.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt(), 11.toDynamicInt(), 12.toDynamicInt()), isTrue); + expect( + statisticsDynamicInt.isMeanInRange( + 10.toDynamicInt(), 30.toDynamicInt()), + isTrue); + expect( + statisticsDynamicInt.isMeanInRange(10.toDynamicInt(), + 30.toDynamicInt(), 11.toDynamicInt(), 12.toDynamicInt()), + isTrue); + + expect( + statisticsDecimal.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt()), + isTrue); + expect( + statisticsDecimal.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt(), + 11.toDynamicInt(), 12.toDynamicInt()), + isTrue); + expect(statistics.isMeanInRange(10, 19), isFalse); expect(statistics.isMeanInRange(10, 30, 10, 11), isFalse); @@ -221,6 +283,23 @@ void main() { 10.toDynamicInt(), 11.toDynamicInt()), isFalse); + expect( + statisticsDynamicInt.isMeanInRange( + 10.toDynamicInt(), 19.toDynamicInt()), + isFalse); + expect( + statisticsDynamicInt.isMeanInRange(10.toDynamicInt(), + 30.toDynamicInt(), 10.toDynamicInt(), 11.toDynamicInt()), + isFalse); + + expect( + statisticsDecimal.isMeanInRange(10.toDynamicInt(), 19.toDynamicInt()), + isFalse); + expect( + statisticsDecimal.isMeanInRange(10.toDynamicInt(), 30.toDynamicInt(), + 10.toDynamicInt(), 11.toDynamicInt()), + isFalse); + expect(statistics.toString(precision: 2), equals('{~25 +-11.18 [10..(30)..40] #4}')); expect(statistics.toString(precision: 0), @@ -231,8 +310,20 @@ void main() { expect(statisticsBigInt.toString(precision: 0), equals('{~25 +-11 [10..(30)..40] #4}')); + expect(statisticsDynamicInt.toString(precision: 2), + equals('{~25 +-11.18 [10..(30)..40] #4}')); + expect(statisticsDynamicInt.toString(precision: 0), + equals('{~25 +-11 [10..(30)..40] #4}')); + + expect(statisticsDecimal.toString(precision: 2), + equals('{~25 +-11.18 [10..(30)..40] #4}')); + expect(statisticsDecimal.toString(precision: 0), + equals('{~25 +-11 [10..(30)..40] #4}')); + expect(data.statisticsWithData.data, equals(data)); expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); + expect(dataDynamicInt.statisticsWithData.data, equals(dataDynamicInt)); + expect(dataDecimal.statisticsWithData.data, equals(dataDecimal)); }); test('int(3)', () { @@ -504,7 +595,16 @@ void main() { }); test('standardDeviation = 0 (int)', () { - final data1 = [94.97, 94.97, 94.97, 94.97, 94.97, 94.97, 94.97]; + final data1 = [94, 94, 94, 94, 94, 94, 94]; + var standardDeviation = data1.standardDeviation; + final stdDev1 = standardDeviation; + print('Standard Deviation of data1: $stdDev1'); + + expect(standardDeviation, equals(0.0)); + }); + + test('standardDeviation = 0 (double)', () { + final data1 = [94.97, 94.97, 94.97, 94.97, 94.97, 94.97, 94.97]; var standardDeviation = data1.standardDeviation; final stdDev1 = standardDeviation; print('Standard Deviation of data1: $stdDev1'); @@ -512,9 +612,17 @@ void main() { expect(standardDeviation, equals(0.0)); }); + test('standardDeviation = 0 (num)', () { + final data = [53.97, 53.97, 53.97, 53.97, 53.97, 53.97, 53.97]; + var standardDeviation = data.standardDeviation; + final stdDev = standardDeviation; + print('Standard Deviation of data: $stdDev'); + + expect(standardDeviation, equals(0.0)); + }); + test('standardDeviation = 0 (BigInt)', () { - final data = - [94.97, 94.97, 94.97, 94.97, 94.97, 94.97, 94.97].toBigIntList(); + final data = [94, 94, 94, 94, 94, 94, 94].toBigIntList(); var standardDeviation = data.standardDeviation; final stdDev = standardDeviation; print('Standard Deviation of data: $stdDev'); @@ -522,13 +630,23 @@ void main() { expect(standardDeviation, equals(Decimal.zero)); }); - test('standardDeviation = 0 (num)', () { - final data = [53.97, 53.97, 53.97, 53.97, 53.97, 53.97, 53.97]; + test('standardDeviation = 0 (DynamicInt)', () { + final data = [94, 94, 94, 94, 94, 94, 94].toDynamicIntList(); var standardDeviation = data.standardDeviation; final stdDev = standardDeviation; print('Standard Deviation of data: $stdDev'); - expect(standardDeviation, equals(0.0)); + expect(standardDeviation, equals(Decimal.zero)); + }); + + test('standardDeviation = 0 (Decimal)', () { + final data = + [94.97, 94.97, 94.97, 94.97, 94.97, 94.97, 94.97].toDecimalList(); + var standardDeviation = data.standardDeviation; + final stdDev = standardDeviation; + print('Standard Deviation of data: $stdDev'); + + expect(standardDeviation, equals(Decimal.zero)); }); test('error(alreadySortedData)', () { From aa732a2e1c544b38dc1c83b43a5e702d04026103 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 8 Apr 2025 20:33:06 -0300 Subject: [PATCH 03/23] castDynamicNumber --- lib/src/statistics_base.dart | 47 +++++++++++++++++++++++++++++++++ test/statistics_base_test.dart | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/lib/src/statistics_base.dart b/lib/src/statistics_base.dart index 0096681..4149df5 100644 --- a/lib/src/statistics_base.dart +++ b/lib/src/statistics_base.dart @@ -1335,6 +1335,53 @@ class StatisticsDynamicNumber> extends DataEntry { return statistics; } + /// Casts this instance to `StatisticsDynamicNumber`. + StatisticsDynamicNumber castDynamicNumber>() { + if (T == DynamicInt) { + if (this is StatisticsDynamicNumber) { + return this as StatisticsDynamicNumber; + } + + return StatisticsDynamicNumber( + length, + min.toDynamicInt(), + max.toDynamicInt(), + medianLow: medianLow.toDynamicInt(), + medianHigh: medianHigh.toDynamicInt(), + mean: mean, + standardDeviation: standardDeviation, + sum: sum.toDynamicInt(), + squaresSum: squaresSum.toDynamicInt(), + lowerStatistics: lowerStatistics?.castDynamicNumber() + as StatisticsDynamicNumber?, + upperStatistics: upperStatistics?.castDynamicNumber() + as StatisticsDynamicNumber?, + ) as StatisticsDynamicNumber; + } else if (T == Decimal) { + if (this is StatisticsDynamicNumber) { + return this as StatisticsDynamicNumber; + } + + return StatisticsDynamicNumber( + length, + min.toDecimal(), + max.toDecimal(), + medianLow: medianLow.toDecimal(), + medianHigh: medianHigh.toDecimal(), + mean: mean, + standardDeviation: standardDeviation, + sum: sum.toDecimal(), + squaresSum: squaresSum.toDecimal(), + lowerStatistics: lowerStatistics?.castDynamicNumber() + as StatisticsDynamicNumber?, + upperStatistics: upperStatistics?.castDynamicNumber() + as StatisticsDynamicNumber?, + ) as StatisticsDynamicNumber; + } else { + return this as StatisticsDynamicNumber; + } + } + /// Casts this instance to `Statistics`. Statistics cast() { if (T == int) { diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index 58b7fcb..c67d18b 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -320,6 +320,54 @@ void main() { expect(statisticsDecimal.toString(precision: 0), equals('{~25 +-11 [10..(30)..40] #4}')); + /////// + + expect(statistics.cast(), isA>()); + expect(statistics.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + expect(statistics.cast(), isA>()); + expect(statistics.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + expect(statisticsBigInt.cast(), isA>()); + expect(statisticsBigInt.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + /////// + + expect(statisticsDynamicInt.cast(), isA>()); + expect(statisticsDynamicInt.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + expect(statisticsDynamicInt.castDynamicNumber(), + isA>()); + expect(statisticsDynamicInt.castDynamicNumber().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + expect(statisticsDynamicInt.castDynamicNumber(), + isA>()); + expect(statisticsDynamicInt.castDynamicNumber().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + /////// + + expect(statisticsDecimal.cast(), isA>()); + expect(statisticsDecimal.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + expect(statisticsDecimal.castDynamicNumber(), + isA>()); + expect(statisticsDecimal.castDynamicNumber().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + expect(statisticsDecimal.castDynamicNumber(), + isA>()); + expect(statisticsDecimal.castDynamicNumber().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + + /////// + expect(data.statisticsWithData.data, equals(data)); expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); expect(dataDynamicInt.statisticsWithData.data, equals(dataDynamicInt)); From 5951b40a80d4e807074277a2702b0fd7b62de7bb Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 8 Apr 2025 20:34:08 -0300 Subject: [PATCH 04/23] castDynamicNumber --- test/statistics_base_test.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index c67d18b..992c70d 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -340,6 +340,10 @@ void main() { expect(statisticsDynamicInt.cast().toString(), equals('{~25 +-11.1803 [10..(30)..40] #4}')); + expect(statisticsDynamicInt.cast(), isA>()); + expect(statisticsDynamicInt.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + expect(statisticsDynamicInt.castDynamicNumber(), isA>()); expect(statisticsDynamicInt.castDynamicNumber().toString(), @@ -356,6 +360,10 @@ void main() { expect(statisticsDecimal.cast().toString(), equals('{~25 +-11.1803 [10..(30)..40] #4}')); + expect(statisticsDecimal.cast(), isA>()); + expect(statisticsDecimal.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + expect(statisticsDecimal.castDynamicNumber(), isA>()); expect(statisticsDecimal.castDynamicNumber().toString(), From efbf635f1ca749e4d2c8cbb31c6b47e6db385ec1 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 8 Apr 2025 20:47:42 -0300 Subject: [PATCH 05/23] fix divideBy --- lib/src/statistics_base.dart | 28 ++++++++++++++-------------- test/statistics_base_test.dart | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/lib/src/statistics_base.dart b/lib/src/statistics_base.dart index 4149df5..b602af2 100644 --- a/lib/src/statistics_base.dart +++ b/lib/src/statistics_base.dart @@ -1018,13 +1018,13 @@ class StatisticsBigInt extends DataEntry { if (isEmpty) return this; return StatisticsBigInt( - n.divideInt(length).toInt(), - n.divideBigInt(min).toBigInt(), - n.divideBigInt(max).toBigInt(), - medianLow: n.divideBigInt(medianLow).toBigInt(), - medianHigh: n.divideBigInt(medianHigh).toBigInt(), - sum: n.divideBigInt(sum).toBigInt(), - squaresSum: n.divideBigInt(squaresSum).toBigInt(), + (length.toDynamicInt() / n).toInt(), + (min.toDynamicInt() / n).toBigInt(), + (max.toDynamicInt() / n).toBigInt(), + medianLow: (medianLow.toDynamicInt() / n).toBigInt(), + medianHigh: (medianHigh.toDynamicInt() / n).toBigInt(), + sum: (sum.toDynamicInt() / n).toBigInt(), + squaresSum: (squaresSum.toDynamicInt() / n).toBigInt(), mean: mean / n, standardDeviation: (standardDeviation / n), ); @@ -1503,13 +1503,13 @@ class StatisticsDynamicNumber> extends DataEntry { if (isEmpty) return this; return StatisticsDynamicNumber( - n.divideInt(length).toInt(), - (n / min).cast(), - (n / max).cast(), - medianLow: (n / medianLow).cast(), - medianHigh: (n / medianHigh).cast(), - sum: (n / sum).cast(), - squaresSum: (n / squaresSum).cast(), + (length.toDynamicInt() / n).toInt(), + (min / n).cast(), + (max / n).cast(), + medianLow: (medianLow / n).cast(), + medianHigh: (medianHigh / n).cast(), + sum: (sum / n).cast(), + squaresSum: (squaresSum / n).cast(), mean: mean / n, standardDeviation: (standardDeviation / n), ); diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index 992c70d..5cffd75 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -376,6 +376,34 @@ void main() { /////// + expect(statistics.multiplyBy(2).toString(), + equals('{~50 +-22.3606 [20..(60)..80] #8}')); + + expect(statisticsBigInt.multiplyBy(DynamicInt.two).toString(), + equals('{~50 +-22.3606 [20..(60)..80] #8}')); + + expect(statisticsDynamicInt.multiplyBy(DynamicInt.two).toString(), + equals('{~50 +-22.3606 [20..(60)..80] #8}')); + + expect(statisticsDecimal.multiplyBy(DynamicInt.two).toString(), + equals('{~50 +-22.3606 [20..(60)..80] #8}')); + + /////// + + expect(statistics.divideBy(2).toString(), + equals('{~12.5 +-5.5901 [5..(15)..20] #2.0}')); + + expect(statisticsBigInt.divideBy(DynamicInt.two).toString(), + equals('{~12.5 +-5.5901 [5..(15)..20] #2}')); + + expect(statisticsDynamicInt.divideBy(DynamicInt.two).toString(), + equals('{~12.5 +-5.5901 [5..(15)..20] #2}')); + + expect(statisticsDecimal.divideBy(DynamicInt.two).toString(), + equals('{~12.5 +-5.5901 [5..(15)..20] #2}')); + + /////// + expect(data.statisticsWithData.data, equals(data)); expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); expect(dataDynamicInt.statisticsWithData.data, equals(dataDynamicInt)); From 8a0ce8a2a305c838b1eb565476f4202cc0c6dc13 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 8 Apr 2025 20:53:29 -0300 Subject: [PATCH 06/23] statistics_base_test.dart --- test/statistics_base_test.dart | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index 5cffd75..f3b7d70 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -390,17 +390,25 @@ void main() { /////// - expect(statistics.divideBy(2).toString(), - equals('{~12.5 +-5.5901 [5..(15)..20] #2.0}')); + expect( + statistics.divideBy(2).toString(), + anyOf(equals('{~12.5 +-5.5901 [5..(15)..20] #2.0}'), + equals('{~12.5 +-5.5901 [5..(15)..20] #2}'))); - expect(statisticsBigInt.divideBy(DynamicInt.two).toString(), - equals('{~12.5 +-5.5901 [5..(15)..20] #2}')); + expect( + statisticsBigInt.divideBy(DynamicInt.two).toString(), + anyOf(equals('{~12.5 +-5.5901 [5..(15)..20] #2.0}'), + equals('{~12.5 +-5.5901 [5..(15)..20] #2}'))); - expect(statisticsDynamicInt.divideBy(DynamicInt.two).toString(), - equals('{~12.5 +-5.5901 [5..(15)..20] #2}')); + expect( + statisticsDynamicInt.divideBy(DynamicInt.two).toString(), + anyOf(equals('{~12.5 +-5.5901 [5..(15)..20] #2.0}'), + equals('{~12.5 +-5.5901 [5..(15)..20] #2}'))); - expect(statisticsDecimal.divideBy(DynamicInt.two).toString(), - equals('{~12.5 +-5.5901 [5..(15)..20] #2}')); + expect( + statisticsDecimal.divideBy(DynamicInt.two).toString(), + anyOf(equals('{~12.5 +-5.5901 [5..(15)..20] #2.0}'), + equals('{~12.5 +-5.5901 [5..(15)..20] #2}'))); /////// From e8a7f99d752e6cf5aa79370ed14d96521c454305 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Tue, 8 Apr 2025 21:43:17 -0300 Subject: [PATCH 07/23] statistics_base_test.dart --- test/statistics_base_test.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index f3b7d70..d05b126 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -334,6 +334,10 @@ void main() { expect(statisticsBigInt.cast().toString(), equals('{~25 +-11.1803 [10..(30)..40] #4}')); + expect(statisticsBigInt.cast(), isA>()); + expect(statisticsBigInt.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + /////// expect(statisticsDynamicInt.cast(), isA>()); @@ -390,6 +394,20 @@ void main() { /////// + expect(statistics.sumWith(statistics).toString(), + equals('{~50 +-22.3606 [20..(60)..80] #8}')); + + expect(statisticsBigInt.sumWith(statisticsBigInt).toString(), + equals('{~50 +-22.3606 [20..(60)..80] #8}')); + + expect(statisticsDynamicInt.sumWith(statisticsDynamicInt).toString(), + equals('{~50 +-22.3606 [20..(60)..80] #8}')); + + expect(statisticsDecimal.sumWith(statisticsDecimal).toString(), + equals('{~50 +-22.3606 [20..(60)..80] #8}')); + + /////// + expect( statistics.divideBy(2).toString(), anyOf(equals('{~12.5 +-5.5901 [5..(15)..20] #2.0}'), From 382266c0231b0fd10c555c417d7c0d186538364c Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 00:06:20 -0300 Subject: [PATCH 08/23] - `IterableExtension`: - Added `counts`, `countsSorted` and `mostFrequent`. --- CHANGELOG.md | 3 ++ lib/src/statistics_extension.dart | 27 ++++++++++++++++ pubspec.yaml | 4 +-- test/statistics_extension_test.dart | 50 +++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9192878..fd4f252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - Added `sum2` and `mean2`: returns a record with extra parameter `allEqualsTo`. - `mean` and `standardDeviation` are computed in a way that minimizes floating-point precision errors. +- `IterableExtension`: + - Added `counts`, `countsSorted` and `mostFrequent`. + - `statistics_base_test.dart`: improved numeric tests. ## 1.2.0 diff --git a/lib/src/statistics_extension.dart b/lib/src/statistics_extension.dart index d88d0fc..5aa01f0 100644 --- a/lib/src/statistics_extension.dart +++ b/lib/src/statistics_extension.dart @@ -817,6 +817,33 @@ extension IterableExtension on Iterable { int computeHashcode() { return IterableEquality().hash(this); } + + /// Returns a map counting how many times each element appears in the iterable. + /// + /// Each key in the returned map is a distinct element from the iterable, and + /// the corresponding value is the number of occurrences of that element. + /// + /// If the iterable is empty, an empty map is returned. + Map counts() { + var count = {}; + for (var e in this) { + var c = count[e] ?? 0; + count[e] = c + 1; + } + return count; + } + + /// Returns the element counts sorted by occurrence count in ascending order. + /// See [counts]. + List> countsSorted() { + var counts = this.counts(); + var entriesSorted = + counts.entries.sorted((a, b) => a.value.compareTo(b.value)).toList(); + return entriesSorted; + } + + /// Returns the element with the highest occurrence count, or `null` if empty. + T? mostFrequent() => countsSorted().lastOrNull?.key; } /// extension for `Map>`. diff --git a/pubspec.yaml b/pubspec.yaml index 84feb0a..fa1b721 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,9 +13,9 @@ dependencies: dev_dependencies: lints: ^5.1.1 - test: ^1.25.15 + test: ^1.28.0 dependency_validator: ^3.2.3 - coverage: ^1.11.1 + coverage: ^1.15.0 #dependency_overrides: # data_serializer: diff --git a/test/statistics_extension_test.dart b/test/statistics_extension_test.dart index da712b9..7516481 100644 --- a/test/statistics_extension_test.dart +++ b/test/statistics_extension_test.dart @@ -548,6 +548,56 @@ void main() { expect(comparator2(itr2.elementAt(1), itr2.elementAt(0)), equals(1)); }); + group('Iterable counts extensions', () { + test('counts() with empty iterable', () { + expect([].counts(), isEmpty); + }); + + test('counts() with single element', () { + expect([1].counts(), equals({1: 1})); + }); + + test('counts() with repeated elements', () { + expect( + [1, 2, 1, 3, 2, 1].counts(), + equals({1: 3, 2: 2, 3: 1}), + ); + }); + + test('countsSorted() sorts by count ascending', () { + final result = [1, 2, 1, 3, 2, 1].countsSorted(); + + expect( + result.map((e) => (e.key, e.value)), + equals([ + (3, 1), + (2, 2), + (1, 3), + ]), + ); + }); + + test('countsSorted() with empty iterable', () { + expect([].countsSorted(), isEmpty); + }); + + test('mostFrequent() returns null for empty iterable', () { + expect([].mostFrequent(), isNull); + }); + + test('mostFrequent() returns the most frequent element', () { + expect([1, 2, 1, 3, 2, 1].mostFrequent(), equals(1)); + }); + + test('mostFrequent() with tie returns one of the most frequent', () { + final value = [1, 2, 1, 2].mostFrequent(); + expect(value, anyOf(1, 2)); + + final value2 = [2, 1, 2, 1].mostFrequent(); + expect(value2, anyOf(1, 2)); + }); + }); + test('MapExtension', () { var m = { 'a': 1, From 88a2e20afddcb97d77424aec6bc0b7f773aef4d9 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 00:18:22 -0300 Subject: [PATCH 09/23] statistics_base_test.dart --- test/statistics_base_test.dart | 41 ++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index d05b126..fb4b521 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -669,6 +669,47 @@ void main() { expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); }); + test('num(3)', () { + var data = [10, 20.2, 30]; + + var statistics = data.statistics; + + expect(statistics.length, equals(3)); + expect(statistics.isEmpty, isFalse); + expect(statistics.isNotEmpty, isTrue); + + expect(statistics.sum, equals(60.2)); + expect(statistics.mean, equals(20.066666666666666)); + expect(statistics.standardDeviation, equals(8.165510122188053)); + expect(statistics.squaresSum, equals(1408.04)); + expect(statistics.squaresMean, equals(469.34666666666664)); + + expect(statistics.centerIndex, equals(1)); + expect(statistics.center, equals(20.2)); + + expect(statistics.medianLow, equals(20.2)); + expect(statistics.medianHigh, equals(20.2)); + expect(statistics.median, equals(20.2)); + + expect(statistics.medianLowIndex, equals(1)); + expect(statistics.medianHighIndex, equals(1)); + + expect(statistics.isMeanInRange(10, 30), isTrue); + expect(statistics.isMeanInRange(10, 30, 7, 9), isTrue); + + expect(statistics.isMeanInRange(10, 19), isFalse); + expect(statistics.isMeanInRange(10, 30, 7, 8), isFalse); + + expect(statistics.toString(), + equals('{~20.0666 +-8.1655 [10..(20.2)..30] #3}')); + expect(statistics.toString(precision: 0), + equals('{~20 +-8 [10..(20)..30] #3}')); + expect(statistics.toString(precision: 1), + equals('{~20.0 +-8.1 [10..(20.2)..30] #3}')); + + expect(data.statisticsWithData.data, equals(data)); + }); + test('useBigIntToCompute: true', () { var statistics = Statistics.compute([10, 20, 30], useBigIntToCompute: true); From facabb61881303ac9b87f7d76484ddc8a60cb93d Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 00:24:54 -0300 Subject: [PATCH 10/23] Update operator `/` to use `StatisticsDynamicNumber`. --- lib/src/statistics_base.dart | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/src/statistics_base.dart b/lib/src/statistics_base.dart index b602af2..b0f2256 100644 --- a/lib/src/statistics_base.dart +++ b/lib/src/statistics_base.dart @@ -1045,19 +1045,15 @@ class StatisticsBigInt extends DataEntry { ); } - StatisticsBigInt operator /(StatisticsBigInt other) { - // TODO: use StatisticsDecimal - return StatisticsBigInt( + StatisticsDynamicNumber operator /(StatisticsBigInt other) { + return StatisticsDynamicNumber( (length / other.length).toInt(), - (min.toDynamicInt() / other.min.toDynamicInt()).toBigInt(), - (max.toDynamicInt() / other.max.toDynamicInt()).toBigInt(), - medianLow: (medianLow.toDynamicInt() / other.medianLow.toDynamicInt()) - .toBigInt(), - medianHigh: (medianHigh.toDynamicInt() / other.medianHigh.toDynamicInt()) - .toBigInt(), - sum: (sum.toDynamicInt() / other.sum.toDynamicInt()).toBigInt(), - squaresSum: (squaresSum.toDynamicInt() / other.squaresSum.toDynamicInt()) - .toBigInt(), + (min.toDynamicInt() / other.min.toDynamicInt()), + (max.toDynamicInt() / other.max.toDynamicInt()), + medianLow: (medianLow.toDynamicInt() / other.medianLow.toDynamicInt()), + medianHigh: (medianHigh.toDynamicInt() / other.medianHigh.toDynamicInt()), + sum: (sum.toDynamicInt() / other.sum.toDynamicInt()), + squaresSum: (squaresSum.toDynamicInt() / other.squaresSum.toDynamicInt()), mean: mean / other.mean, standardDeviation: (standardDeviation / other.standardDeviation), ); From 2e6e100e750c429b6c2863cfafe3d5a3c7f56c9e Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 00:30:19 -0300 Subject: [PATCH 11/23] statistics_base_test.dart --- test/statistics_base_test.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index fb4b521..d1ba757 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -571,6 +571,19 @@ void main() { expect(data.statisticsWithData.data, equals(data)); expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); + + var statistics2 = statistics + statistics; + expect(statistics2.toString(), equals('{~15 +-5 [10..(20)..20] #4}')); + + var statisticsBigInt2 = statisticsBigInt + statisticsBigInt; + expect( + statisticsBigInt2.toString(), equals('{~15 +-5 [10..(20)..20] #4}')); + + var statistics3 = statistics / statistics; + expect(statistics3.toString(), equals('{~1 +-1 [1..(1)..1] #1.0}')); + + var statisticsBigInt3 = statisticsBigInt / statisticsBigInt; + expect(statisticsBigInt3.toString(), equals('{~1 +-1 [1..(1)..1] #1}')); }); test('int(1)', () { From b43ad3ed31b8f632900f215fd2b8ab71212ef553 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 01:07:15 -0300 Subject: [PATCH 12/23] Refactor type check and conversion of values for equality comparison. --- lib/src/statistics_extension_num.dart | 59 +++++- test/statistics_tools_test.dart | 249 +++++++++++++++++++++++++- 2 files changed, 304 insertions(+), 4 deletions(-) diff --git a/lib/src/statistics_extension_num.dart b/lib/src/statistics_extension_num.dart index 5dd3592..77a5092 100644 --- a/lib/src/statistics_extension_num.dart +++ b/lib/src/statistics_extension_num.dart @@ -1486,7 +1486,7 @@ extension IterableBigIntExtension on Iterable { static final ListEquality _listEquality = ListEquality(); /// Returns `true` if [other] values are all equals, regarding the [tolerance]. - bool equalsValues(List other, {num tolerance = 0}) { + bool equalsValues(List other, {num tolerance = 0}) { if (tolerance != 0) { var length = this.length; if (length != other.length) return false; @@ -1498,7 +1498,30 @@ extension IterableBigIntExtension on Iterable { for (var i = 0; i < length; ++i) { var a = list[i]; var b = other[i]; - var diff = (a - b).abs(); + + BigInt n; + if (b is BigInt) { + n = b; + } else if (b is int) { + n = BigInt.from(b); + } else if (b is double) { + var d = b.toInt(); + if (d == b) { + n = BigInt.from(b); + } else { + var diff = b.toDecimal().subtractBigInt(a).abs(); + if (diff.toDouble() > tolerance) { + return false; + } + continue; + } + } else if (b is DynamicNumber) { + n = b.toBigInt(); + } else { + return false; + } + + var diff = (a - n).abs(); if (diff.toDouble() > tolerance) { return false; @@ -1508,7 +1531,37 @@ extension IterableBigIntExtension on Iterable { return true; } else { var list = this is List ? (this as List) : toList(); - return _listEquality.equals(list, other); + var list2 = other is List + ? other + : other + .map((n) { + if (n is BigInt) { + return n; + } else if (n is int) { + return BigInt.from(n); + } else if (n is double) { + var d = n.toInt(); + if (d == n) { + return BigInt.from(n); + } else { + // Can't convert to `BigInt` without lose precision: + return null; + } + } else if (n is DynamicInt) { + return n.toBigInt(); + } else if (n is Decimal) { + var d = n.toDecimal(); + if (d.isDecimalPartZero) { + return n.toBigInt(); + } + // Can't convert to `BigInt` without lose precision: + return null; + } + return null; + }) + .nonNulls + .toList(); + return _listEquality.equals(list, list2); } } } diff --git a/test/statistics_tools_test.dart b/test/statistics_tools_test.dart index 079e5b3..7371e45 100644 --- a/test/statistics_tools_test.dart +++ b/test/statistics_tools_test.dart @@ -7,7 +7,7 @@ void main() { test('toIntsList', () { expect([].toIntsList(), isEmpty); - expect([].toIntsList(), isEmpty); + expect([].toBigIntList(), isEmpty); expect([10].toIntsList(), equals([10])); expect([10, 20].toIntsList(), equals([10, 20])); }); @@ -208,6 +208,253 @@ void main() { }); }); + group('BigInt', () { + setUp(() {}); + + test('toIntsList', () { + expect([].toIntsList(), isEmpty); + expect([].toBigIntList(), isEmpty); + expect([10.toBigInt()].toIntsList(), equals([10])); + expect([10.toBigInt(), 20.toBigInt()].toIntsList(), + equals([10, 20])); + }); + + test('toDoublesList', () { + expect([].toDoublesList(), isEmpty); + expect([].toDoublesList(), isEmpty); + expect([10.toBigInt()].toDoublesList(), equals([10.0])); + expect([10.toBigInt(), 20.toBigInt()].toDoublesList(), + equals([10.0, 20.0])); + }); + + test('toStringsList', () { + expect([].toStringsList(), isEmpty); + expect([].toStringsList(), isEmpty); + expect([10.toBigInt()].toStringsList(), equals(['10'])); + expect([10.toBigInt(), 20.toBigInt()].toStringsList(), + equals(['10', '20'])); + }); + + test('mean', () { + expect(() => [].mean, throwsUnsupportedError); + expect([0].mean, equals(0)); + expect([10].mean, equals(10)); + expect([10, 20].mean, equals(15)); + expect([10, 20, 30].mean, equals(20)); + }); + + test('sum', () { + expect([].sum, equals(0.toBigInt())); + expect([0].sum, equals(0)); + expect([10].sum, equals(10)); + expect([10, 20].sum, equals(30)); + expect([10, 20, 30].sum, equals(60)); + }); + + test('sumSquares', () { + expect([].sumSquares, equals(0.toBigInt())); + expect([0].sumSquares, equals(0)); + expect([10].sumSquares, equals(100)); + expect([10, 20].sumSquares, equals(500)); + expect([10, 20, 30].sumSquares, equals(1400)); + }); + + test('square', () { + expect([].square, isEmpty); + expect([0].square, equals([0])); + expect([10].square, equals([100])); + expect([10, 20].square, equals([100, 400])); + expect([10, 20, 30].square, equals([100, 400, 900])); + + expect(11.square, equals(121)); + }); + + test('squareRoot', () { + expect([].squareRoot, isEmpty); + expect([0].squareRoot, equals([0])); + expect([9].squareRoot, equals([3])); + expect([100, 121].squareRoot, equals([10, 11])); + expect([10000, 400, 900].squareRoot, equals([100, 20, 30])); + + expect(100.squareRoot, equals(10)); + }); + + test('squaresMean', () { + expect(() => [].squaresMean, throwsUnsupportedError); + expect([0].squaresMean, equals(0)); + expect([10].squaresMean, equals(100)); + expect([10, 20].squaresMean, equals(250)); + expect([10, 20, 30].squaresMean, equals(466.6666666666667)); + }); + + test('standardDeviation', () { + expect([].standardDeviation, equals(0.toDecimal())); + expect([0].standardDeviation, equals(0)); + expect([10].standardDeviation, equals(0)); + expect([10, 20].standardDeviation, equals(5)); + expect([10, 20, 30].standardDeviation, equals(8.16496580927726)); + }); + + test('median', () { + expect([].median, isNull); + expect([0].median, equals(0)); + expect([10].median, equals(10)); + expect([10, 20].median, equals(15)); + expect([10, 20, 30].median, equals(20)); + expect([30, 20, 10].median, equals(20)); + expect([5, 10, 20, 30].median, equals(15)); + expect([30, 20, 10, 5].median, equals(15)); + }); + + test('medianLow', () { + expect([].medianLow, isNull); + expect([0].medianLow, equals(0)); + expect([10].medianLow, equals(10)); + expect([10, 20].medianLow, equals(10)); + expect([10, 20, 30].medianLow, equals(20)); + expect([30, 20, 10].medianLow, equals(20)); + expect([5, 10, 20, 30].medianLow, equals(10)); + expect([30, 20, 10, 5].medianLow, equals(10)); + }); + + test('medianHigh', () { + expect([].medianHigh, isNull); + expect([0].medianHigh, equals(0)); + expect([10].medianHigh, equals(10)); + expect([10, 20].medianHigh, equals(20)); + expect([10, 20, 30].medianHigh, equals(20)); + expect([30, 20, 10].medianHigh, equals(20)); + expect([5, 10, 20, 30].medianHigh, equals(20)); + expect([30, 20, 10, 5].medianHigh, equals(20)); + }); + + test('abs', () { + expect([].abs, isEmpty); + expect([0].abs, equals([0])); + expect([10].abs, equals([10])); + expect([-10, 20].abs, equals([10, 20])); + expect([10, -20, 30].abs, equals([10, 20, 30])); + }); + + test('movingAverage', () { + expect([].movingAverage(2), isEmpty); + expect([0].movingAverage(2), equals([0])); + expect([10].movingAverage(3), equals([10])); + expect([-10, 20].movingAverage(3), equals([5.0])); + expect([10, -20, 30].movingAverage(3), equals([6.666666666666667])); + expect([10, -20, 30, 40, 50, 60].movingAverage(3), + equals([6.666666666666667, 16.666666666666668, 40.0, 50.0])); + }); + + test('operator +', () { + expect([] + [], isEmpty); + expect([10.toBigInt()] + [20.toBigInt()], + equals([10.toBigInt(), 20.toBigInt()])); + expect( + [100.toBigInt(), 200.toBigInt()] + + [10.toBigInt(), 20.toBigInt()], + equals([100, 200, 10, 20].toBigIntList())); + }); + + test('operator -', () { + expect([] - [], isEmpty); + expect([10.toBigInt()] - [20.toBigInt()], + equals([-10.toBigInt()])); + expect( + [100.toBigInt(), 200.toBigInt()] - + [10.toBigInt(), 20.toBigInt()], + equals([90, 180].toBigIntList())); + expect( + [100.toBigInt(), 200.toBigInt(), 300.toBigInt()] - + [10.toBigInt(), 20.toBigInt()], + equals([90, 180].toBigIntList())); + expect( + [100.toBigInt(), 200.toBigInt(), 300.toBigInt()] - + [10.toBigInt(), 20.toBigInt(), 30.toBigInt()], + equals([90, 180, 270].toBigIntList())); + }); + + test('operator *', () { + expect([] * [], isEmpty); + expect([10.toBigInt()] * [20.toBigInt()], + equals([200].toBigIntList())); + expect([100, 200].toBigIntList() * [10, 20].toBigIntList(), + equals([1000, 4000].toBigIntList())); + expect([100, 200, 300].toBigIntList() * [10, 20].toBigIntList(), + equals([1000, 4000].toBigIntList())); + expect([100, 200, 300].toBigIntList() * [10, 20, 30].toBigIntList(), + equals([1000, 4000, 9000].toBigIntList())); + }); + + test('operator /', () { + expect([] / [], isEmpty); + expect([10].toBigIntList() / [20].toBigIntList(), + equals([0.5].toDecimalList())); + expect([100, 200].toBigIntList() / [10, 20].toBigIntList(), + equals([10, 10].toDecimalList())); + expect([100, 200, 300].toBigIntList() / [10, 20].toBigIntList(), + equals([10, 10].toDecimalList())); + expect([100, 200, 300].toBigIntList() / [10, 20, 30].toBigIntList(), + equals([10, 10, 10].toDecimalList())); + expect([100, 200, 300].toBigIntList() / [40, 50, 30].toBigIntList(), + equals([2.5, 4, 10].toDecimalList())); + }); + + test('operator ~/', () { + expect([] ~/ [], isEmpty); + expect([10].toBigIntList() ~/ [20].toBigIntList(), + equals([0].toBigIntList())); + expect([100, 200].toBigIntList() ~/ [10, 20].toBigIntList(), + equals([10, 10].toBigIntList())); + expect([100, 200, 300].toBigIntList() ~/ [10, 20].toBigIntList(), + equals([10, 10].toBigIntList())); + expect([100, 200, 300].toBigIntList() ~/ [10, 20, 30].toBigIntList(), + equals([10, 10, 10].toBigIntList())); + expect([100, 200, 300].toBigIntList() ~/ [40, 50, 30].toBigIntList(), + equals([2, 4, 10].toBigIntList())); + }); + + test('isSorted', () { + expect([].isSorted, isFalse); + + expect([0.toBigInt()].isSorted, isTrue); + expect([10.toBigInt()].isSorted, isTrue); + expect([-10.toBigInt(), 20.toBigInt()].isSorted, isTrue); + expect([10.toBigInt(), 20.toBigInt(), 30.toBigInt()].isSorted, + isTrue); + + expect([10.toBigInt(), 5.toBigInt()].isSorted, isFalse); + expect([10.toBigInt(), 200.toBigInt(), 30.toBigInt()].isSorted, + isFalse); + }); + + test('equalsValues', () { + expect([].equalsValues([]), isTrue); + expect([].equalsValues([10]), isFalse); + + expect([10.toBigInt()].equalsValues([10.toBigInt()]), isTrue); + expect([10.toBigInt()].equalsValues([10.0], tolerance: 0.000001), + isTrue); + expect([10.toBigInt()].equalsValues([10.0001]), isFalse); + expect([10.toBigInt()].equalsValues([10.0], tolerance: 0.01), + isTrue); + expect([10.toBigInt()].equalsValues([10.0001], tolerance: 0.01), + isTrue); + expect( + [10.toBigInt()].equalsValues([10.0001], tolerance: 0.000001), + isFalse); + + expect( + [10.toBigInt(), 20.toBigInt()] + .equalsValues([10.0001, 20.001], tolerance: 0.01), + isTrue); + expect( + [10.toBigInt(), 20.toBigInt()] + .equalsValues([10.0001, 20.1], tolerance: 0.01), + isFalse); + }); + }); + group('tools', () { setUp(() {}); From 9aacca2fb3da4482b7e343a760e6d4518f3d1c0b Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 01:13:23 -0300 Subject: [PATCH 13/23] statistics_base_test.dart --- test/statistics_base_test.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index d1ba757..3230dc1 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -580,7 +580,10 @@ void main() { statisticsBigInt2.toString(), equals('{~15 +-5 [10..(20)..20] #4}')); var statistics3 = statistics / statistics; - expect(statistics3.toString(), equals('{~1 +-1 [1..(1)..1] #1.0}')); + expect( + statistics3.toString(), + anyOf(equals('{~1 +-1 [1..(1)..1] #1.0}'), + equals('{~1 +-1 [1..(1)..1] #1}'))); var statisticsBigInt3 = statisticsBigInt / statisticsBigInt; expect(statisticsBigInt3.toString(), equals('{~1 +-1 [1..(1)..1] #1}')); From 1829ab25388f5ac54544d77bf9a14d82a0971b8e Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 01:25:03 -0300 Subject: [PATCH 14/23] statistics_base_test.dart --- test/statistics_base_test.dart | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index 3230dc1..411b62e 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -434,6 +434,40 @@ void main() { expect(dataBigInt.statisticsWithData.data, equals(dataBigInt)); expect(dataDynamicInt.statisticsWithData.data, equals(dataDynamicInt)); expect(dataDecimal.statisticsWithData.data, equals(dataDecimal)); + + /////// + + var statistics2 = statistics + statistics; + expect( + statistics2.toString(), equals('{~25 +-11.1803 [10..(30)..40] #8}')); + + var statisticsBigInt2 = statisticsBigInt + statisticsBigInt; + expect(statisticsBigInt2.toString(), + equals('{~25 +-11.1803 [10..(30)..40] #8}')); + + var statisticsDynamicInt2 = statisticsDynamicInt + statisticsDynamicInt; + expect(statisticsDynamicInt2.toString(), + equals('{~25 +-11.1803 [10..(30)..40] #8}')); + + var statisticsDecimal2 = statisticsDecimal + statisticsDecimal; + expect(statisticsDecimal2.toString(), + equals('{~25 +-11.1803 [10..(30)..40] #8}')); + + var statistics3 = statistics / statistics; + expect( + statistics3.toString(), + anyOf(equals('{~1 +-1 [1..(1)..1] #1.0}'), + equals('{~1 +-1 [1..(1)..1] #1}'))); + + var statisticsBigInt3 = statisticsBigInt / statisticsBigInt; + expect(statisticsBigInt3.toString(), equals('{~1 +-1 [1..(1)..1] #1}')); + + var statisticsDynamicInt3 = statisticsDynamicInt / statisticsDynamicInt; + expect( + statisticsDynamicInt3.toString(), equals('{~1 +-1 [1..(1)..1] #1}')); + + var statisticsDecimal3 = statisticsDecimal / statisticsDecimal; + expect(statisticsDecimal3.toString(), equals('{~1 +-1 [1..(1)..1] #1}')); }); test('int(3)', () { From 6b62fdbadf058fbaed1181832f29d8e1686aa4c3 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 01:32:50 -0300 Subject: [PATCH 15/23] `DynamicNumber.cast` as abstract --- lib/src/statistics_dynamic_int.dart | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/src/statistics_dynamic_int.dart b/lib/src/statistics_dynamic_int.dart index ae062a1..6475edd 100644 --- a/lib/src/statistics_dynamic_int.dart +++ b/lib/src/statistics_dynamic_int.dart @@ -249,18 +249,7 @@ abstract class DynamicNumber> N max>(DynamicNumber other); - N cast>() { - var self = this; - if (self is N) { - return self as N; - } else if (N == DynamicInt) { - return toDynamicInt() as N; - } else if (N == Decimal) { - return toDecimal() as N; - } else { - throw TypeError(); - } - } + N cast>(); T multiplyBigInt(BigInt n2); From c9b58074a1272c9ab44459051df0ad88109cae0e Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 01:38:50 -0300 Subject: [PATCH 16/23] statistics_extension_num_test.dart --- test/statistics_extension_num_test.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/statistics_extension_num_test.dart b/test/statistics_extension_num_test.dart index cde73bd..f78e816 100644 --- a/test/statistics_extension_num_test.dart +++ b/test/statistics_extension_num_test.dart @@ -211,6 +211,22 @@ void main() { test('mapToSet', () { expect([].mapToSet((n) => n), equals({})); expect([10, 20].mapToSet((n) => n), equals({10, 20})); + + expect([].toBigIntList().mapToSet((n) => n), equals({})); + expect([10, 20].toBigIntList().mapToSet((n) => n), + equals({10.toBigInt(), 20.toBigInt()})); + }); + + test('toDynamicIntList', () { + expect([].toDynamicIntList(), equals([])); + expect([10, 20].toDynamicIntList(), + equals([10.toDynamicInt(), 20.toDynamicInt()])); + }); + + test('toDecimalList', () { + expect([].toDecimalList(), equals([])); + expect([10, 20].toDecimalList(), + equals([10.toDecimal(), 20.toDecimal()])); }); test('mean', () { From 83d0cc954d36264cd6eebdd397c02e0f426c5023 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 01:43:31 -0300 Subject: [PATCH 17/23] statistics_extension_num_test.dart --- test/statistics_extension_num_test.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/statistics_extension_num_test.dart b/test/statistics_extension_num_test.dart index f78e816..ebe1110 100644 --- a/test/statistics_extension_num_test.dart +++ b/test/statistics_extension_num_test.dart @@ -221,12 +221,20 @@ void main() { expect([].toDynamicIntList(), equals([])); expect([10, 20].toDynamicIntList(), equals([10.toDynamicInt(), 20.toDynamicInt()])); + + expect([].toBigIntList().toDynamicIntList(), equals([])); + expect([10, 20].toBigIntList().toDynamicIntList(), + equals([10.toDynamicInt(), 20.toDynamicInt()])); }); test('toDecimalList', () { expect([].toDecimalList(), equals([])); expect([10, 20].toDecimalList(), equals([10.toDecimal(), 20.toDecimal()])); + + expect([].toBigIntList().toDecimalList(), equals([])); + expect([10, 20].toBigIntList().toDecimalList(), + equals([10.toDecimal(), 20.toDecimal()])); }); test('mean', () { @@ -243,6 +251,12 @@ void main() { expect([10].sum, equals(10)); expect([10, 20].sum, equals(30)); expect([10, 20, 30].sum, equals(60)); + + expect([].sum, equals(0.toBigInt())); + expect([0].toBigIntList().sum, equals(0.toBigInt())); + expect([10].toBigIntList().sum, equals(10.toBigInt())); + expect([10, 20].toBigIntList().sum, equals(30.toBigInt())); + expect([10, 20, 30].toBigIntList().sum, equals(60.toBigInt())); }); test('sumSquares', () { From 98010d87526eec6665da103abdbccb9be2583289 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 02:05:08 -0300 Subject: [PATCH 18/23] Add Decimal operations and BigInt formatting in BigIntExtension. --- lib/src/statistics_extension_num.dart | 22 ++- test/statistics_extension_num_test.dart | 195 +++++++++++++++++++++++- 2 files changed, 214 insertions(+), 3 deletions(-) diff --git a/lib/src/statistics_extension_num.dart b/lib/src/statistics_extension_num.dart index 77a5092..2f6871e 100644 --- a/lib/src/statistics_extension_num.dart +++ b/lib/src/statistics_extension_num.dart @@ -1515,8 +1515,18 @@ extension IterableBigIntExtension on Iterable { } continue; } - } else if (b is DynamicNumber) { + } else if (b is DynamicInt) { n = b.toBigInt(); + } else if (b is Decimal) { + if (b.isDecimalPartZero) { + n = b.toBigInt(); + } else { + var diff = b.subtractBigInt(a).abs(); + if (diff.toDouble() > tolerance) { + return false; + } + continue; + } } else { return false; } @@ -1746,10 +1756,20 @@ extension IntExtension on int { /// extension for [BigInt]. extension BigIntExtension on BigInt { + /// Returns the square of `this` number. + BigInt get square => this * this; + + /// Returns the square root of `this` number. + Decimal get squareRoot => toDynamicInt().squareRoot; + + /// Returns `true` for [int] numbers. + bool get isWholeNumber => true; + BigInt min(BigInt other) => this <= other ? this : other; BigInt max(BigInt other) => this >= other ? this : other; + /// Formats this `int` to a [String] with thousands separator. String get thousands => toInt().thousands; } diff --git a/test/statistics_extension_num_test.dart b/test/statistics_extension_num_test.dart index ebe1110..d37954b 100644 --- a/test/statistics_extension_num_test.dart +++ b/test/statistics_extension_num_test.dart @@ -265,6 +265,12 @@ void main() { expect([10].sumSquares, equals(100)); expect([10, 20].sumSquares, equals(500)); expect([10, 20, 30].sumSquares, equals(1400)); + + expect([].sumSquares, equals(0.toBigInt())); + expect([0].toBigIntList().sumSquares, equals(0.toBigInt())); + expect([10].toBigIntList().sumSquares, equals(100.toBigInt())); + expect([10, 20].toBigIntList().sumSquares, equals(500.toBigInt())); + expect([10, 20, 30].toBigIntList().sumSquares, equals(1400.toBigInt())); }); test('square', () { @@ -273,8 +279,15 @@ void main() { expect([10].square, equals([100])); expect([10, 20].square, equals([100, 400])); expect([10, 20, 30].square, equals([100, 400, 900])); - expect(11.square, equals(121)); + + expect([].square, isEmpty); + expect([0].toBigIntList().square, equals([0].toBigIntList())); + expect([10].toBigIntList().square, equals([100].toBigIntList())); + expect([10, 20].toBigIntList().square, equals([100, 400].toBigIntList())); + expect([10, 20, 30].toBigIntList().square, + equals([100, 400, 900].toBigIntList())); + expect(11.toBigInt().square, equals(121.toBigInt())); }); test('squareRoot', () { @@ -283,8 +296,16 @@ void main() { expect([9].squareRoot, equals([3])); expect([100, 121].squareRoot, equals([10, 11])); expect([10000, 400, 900].squareRoot, equals([100, 20, 30])); - expect(100.squareRoot, equals(10)); + + expect([].squareRoot, isEmpty); + expect([0].toBigIntList().squareRoot, equals([0].toDecimalList())); + expect([9].toBigIntList().squareRoot, equals([3].toDecimalList())); + expect([100, 121].toBigIntList().squareRoot, + equals([10, 11].toDecimalList())); + expect([10000, 400, 900].toBigIntList().squareRoot, + equals([100, 20, 30].toDecimalList())); + expect(100.toBigInt().squareRoot, equals(10.toDecimal())); }); test('squaresMean', () { @@ -301,6 +322,15 @@ void main() { expect([10].standardDeviation, equals(0)); expect([10, 20].standardDeviation, equals(5)); expect([10, 20, 30].standardDeviation, equals(8.16496580927726)); + + expect([].standardDeviation, equals(0.toDecimal())); + expect([0].toBigIntList().standardDeviation, equals(0.toDecimal())); + expect([10].toBigIntList().standardDeviation, equals(0.toDecimal())); + expect([10, 20].toBigIntList().standardDeviation, equals(5.toDecimal())); + expect( + [10, 20, 30].toBigIntList().standardDeviation, + equals(Decimal.parse( + '8.1649658092772603232417973443810078085370640848527572334065506955'))); }); test('median', () { @@ -312,6 +342,15 @@ void main() { expect([30, 20, 10].median, equals(20)); expect([5, 10, 20, 30].median, equals(15)); expect([30, 20, 10, 5].median, equals(15)); + + expect([].median, isNull); + expect([0].toBigIntList().median, equals(0.toDecimal())); + expect([10].toBigIntList().median, equals(10.toDecimal())); + expect([10, 20].toBigIntList().median, equals(15.toDecimal())); + expect([10, 20, 30].toBigIntList().median, equals(20.toDecimal())); + expect([30, 20, 10].toBigIntList().median, equals(20.toDecimal())); + expect([5, 10, 20, 30].toBigIntList().median, equals(15.toDecimal())); + expect([30, 20, 10, 5].toBigIntList().median, equals(15.toDecimal())); }); test('medianLow', () { @@ -323,6 +362,15 @@ void main() { expect([30, 20, 10].medianLow, equals(20)); expect([5, 10, 20, 30].medianLow, equals(10)); expect([30, 20, 10, 5].medianLow, equals(10)); + + expect([].medianLow, isNull); + expect([0].toBigIntList().medianLow, equals(0.toBigInt())); + expect([10].toBigIntList().medianLow, equals(10.toBigInt())); + expect([10, 20].toBigIntList().medianLow, equals(10.toBigInt())); + expect([10, 20, 30].toBigIntList().medianLow, equals(20.toBigInt())); + expect([30, 20, 10].toBigIntList().medianLow, equals(20.toBigInt())); + expect([5, 10, 20, 30].toBigIntList().medianLow, equals(10.toBigInt())); + expect([30, 20, 10, 5].toBigIntList().medianLow, equals(10.toBigInt())); }); test('medianHigh', () { @@ -334,6 +382,15 @@ void main() { expect([30, 20, 10].medianHigh, equals(20)); expect([5, 10, 20, 30].medianHigh, equals(20)); expect([30, 20, 10, 5].medianHigh, equals(20)); + + expect([].medianHigh, isNull); + expect([0].toBigIntList().medianHigh, equals(0.toBigInt())); + expect([10].toBigIntList().medianHigh, equals(10.toBigInt())); + expect([10, 20].toBigIntList().medianHigh, equals(20.toBigInt())); + expect([10, 20, 30].toBigIntList().medianHigh, equals(20.toBigInt())); + expect([30, 20, 10].toBigIntList().medianHigh, equals(20.toBigInt())); + expect([5, 10, 20, 30].toBigIntList().medianHigh, equals(20.toBigInt())); + expect([30, 20, 10, 5].toBigIntList().medianHigh, equals(20.toBigInt())); }); test('abs', () { @@ -342,6 +399,14 @@ void main() { expect([10].abs, equals([10])); expect([-10, 20].abs, equals([10, 20])); expect([10, -20, 30].abs, equals([10, 20, 30])); + + expect([].abs, isEmpty); + expect([0].toBigIntList().abs, equals([0.toBigInt()])); + expect([10].toBigIntList().abs, equals([10.toBigInt()])); + expect( + [-10, 20].toBigIntList().abs, equals([10.toBigInt(), 20.toBigInt()])); + expect([10, -20, 30].toBigIntList().abs, + equals([10.toBigInt(), 20.toBigInt(), 30.toBigInt()])); }); test('movingAverage', () { @@ -352,17 +417,50 @@ void main() { expect([10, -20, 30].movingAverage(3), equals([6.666666666666667])); expect([10, -20, 30, 40, 50, 60].movingAverage(3), equals([6.666666666666667, 16.666666666666668, 40.0, 50.0])); + + expect([].movingAverage(2), isEmpty); + expect([0].toBigIntList().movingAverage(2), equals([0.toDecimal()])); + expect([10].toBigIntList().movingAverage(3), equals([10.toDecimal()])); + expect( + [-10, 20].toBigIntList().movingAverage(3), equals([5.toDecimal()])); + expect([10, -20, 30].toBigIntList().movingAverage(3), + equals([6.666666666666667.toDecimal()])); + expect( + [10, -20, 30, 40, 50, 60].toBigIntList().movingAverage(3), + equals([ + 6.666666666666667.toDecimal(), + 16.666666666666668.toDecimal(), + 40.toDecimal(), + 50.toDecimal(), + ])); }); test('statistics', () { expect([10, 20, 30].statistics.mean, equals(20)); expect([10, 20, 30].statisticsWithData.data, equals([10, 20, 30])); + + expect( + [10, 20, 30].toBigIntList().statistics.mean, equals(20.toDecimal())); + expect([10, 20, 30].toBigIntList().statisticsWithData.data, + equals([10.toBigInt(), 20.toBigInt(), 30.toBigInt()])); }); test('operator +', () { expect([] + [], isEmpty); expect([10] + [20], equals([10, 20])); expect([100, 200] + [10, 20], equals([100, 200, 10, 20])); + + expect([] + [], isEmpty); + expect([10].toBigIntList() + [20].toBigIntList(), + equals([10.toBigInt(), 20.toBigInt()])); + expect( + [100, 200].toBigIntList() + [10, 20].toBigIntList(), + equals([ + 100.toBigInt(), + 200.toBigInt(), + 10.toBigInt(), + 20.toBigInt(), + ])); }); test('operator -', () { @@ -371,6 +469,16 @@ void main() { expect([100, 200] - [10, 20], equals([90, 180])); expect([100, 200, 300] - [10, 20], equals([90, 180])); expect([100, 200, 300] - [10, 20, 30], equals([90, 180, 270])); + + expect([] - [], isEmpty); + expect( + [10].toBigIntList() - [20].toBigIntList(), equals([-10.toBigInt()])); + expect([100, 200].toBigIntList() - [10, 20].toBigIntList(), + equals([90.toBigInt(), 180.toBigInt()])); + expect([100, 200, 300].toBigIntList() - [10, 20].toBigIntList(), + equals([90.toBigInt(), 180.toBigInt()])); + expect([100, 200, 300].toBigIntList() - [10, 20, 30].toBigIntList(), + equals([90.toBigInt(), 180.toBigInt(), 270.toBigInt()])); }); test('operator *', () { @@ -380,6 +488,16 @@ void main() { expect([100, 200, 300] * [10, 20], equals([1000, 4000])); expect( [100, 200, 300] * [10, 20, 30], equals([1000, 4000, 9000])); + + expect([] * [], isEmpty); + expect( + [10].toBigIntList() * [20].toBigIntList(), equals([200.toBigInt()])); + expect([100, 200].toBigIntList() * [10, 20].toBigIntList(), + equals([1000.toBigInt(), 4000.toBigInt()])); + expect([100, 200, 300].toBigIntList() * [10, 20].toBigIntList(), + equals([1000.toBigInt(), 4000.toBigInt()])); + expect([100, 200, 300].toBigIntList() * [10, 20, 30].toBigIntList(), + equals([1000.toBigInt(), 4000.toBigInt(), 9000.toBigInt()])); }); test('operator /', () { @@ -389,6 +507,18 @@ void main() { expect([100, 200, 300] / [10, 20], equals([10, 10])); expect([100, 200, 300] / [10, 20, 30], equals([10, 10, 10])); expect([100, 200, 300] / [40, 50, 30], equals([2.5, 4, 10])); + + expect([] / [], isEmpty); + expect( + [10].toBigIntList() / [20].toBigIntList(), equals([0.5.toDecimal()])); + expect([100, 200].toBigIntList() / [10, 20].toBigIntList(), + equals([10.toDecimal(), 10.toDecimal()])); + expect([100, 200, 300].toBigIntList() / [10, 20].toBigIntList(), + equals([10.toDecimal(), 10.toDecimal()])); + expect([100, 200, 300].toBigIntList() / [10, 20, 30].toBigIntList(), + equals([10.toDecimal(), 10.toDecimal(), 10.toDecimal()])); + expect([100, 200, 300].toBigIntList() / [40, 50, 30].toBigIntList(), + equals([2.5.toDecimal(), 4.toDecimal(), 10.toDecimal()])); }); test('operator ~/', () { @@ -398,9 +528,22 @@ void main() { expect([100, 200, 300] ~/ [10, 20], equals([10, 10])); expect([100, 200, 300] ~/ [10, 20, 30], equals([10, 10, 10])); expect([100, 200, 300] ~/ [40, 50, 30], equals([2, 4, 10])); + + expect([] ~/ [], isEmpty); + expect( + [10].toBigIntList() ~/ [20].toBigIntList(), equals([0.toBigInt()])); + expect([100, 200].toBigIntList() ~/ [10, 20].toBigIntList(), + equals([10.toBigInt(), 10.toBigInt()])); + expect([100, 200, 300].toBigIntList() ~/ [10, 20].toBigIntList(), + equals([10.toBigInt(), 10.toBigInt()])); + expect([100, 200, 300].toBigIntList() ~/ [10, 20, 30].toBigIntList(), + equals([10.toBigInt(), 10.toBigInt(), 10.toBigInt()])); + expect([100, 200, 300].toBigIntList() ~/ [40, 50, 30].toBigIntList(), + equals([2.toBigInt(), 4.toBigInt(), 10.toBigInt()])); }); test('isSorted', () { + // int expect([].isSorted, isFalse); expect([0].isSorted, isTrue); @@ -410,9 +553,21 @@ void main() { expect([10, 5].isSorted, isFalse); expect([10, 200, 30].isSorted, isFalse); + + // BigInt + expect([].isSorted, isFalse); + + expect([0].toBigIntList().isSorted, isTrue); + expect([10].toBigIntList().isSorted, isTrue); + expect([-10, 20].toBigIntList().isSorted, isTrue); + expect([10, 20, 30].toBigIntList().isSorted, isTrue); + + expect([10, 5].toBigIntList().isSorted, isFalse); + expect([10, 200, 30].toBigIntList().isSorted, isFalse); }); test('equalsValues', () { + // int expect([].equalsValues([]), isTrue); expect([].equalsValues([10]), isFalse); @@ -427,6 +582,42 @@ void main() { isTrue); expect([10, 20].equalsValues([10.0001, 20.1], tolerance: 0.01), isFalse); + + // BigInt + expect([].equalsValues([]), isTrue); + expect([].equalsValues([10]), isFalse); + + expect([10].toBigIntList().equalsValues([10]), isTrue); + expect( + [10] + .toBigIntList() + .equalsValues([10.toDecimal()], tolerance: 0.000001), + isTrue); + expect([10].toBigIntList().equalsValues([10.0001.toDecimal()]), isFalse); + expect( + [10].toBigIntList().equalsValues([10.toDecimal()], tolerance: 0.01), + isTrue); + expect( + [10] + .toBigIntList() + .equalsValues([10.0001.toDecimal()], tolerance: 0.01), + isTrue); + expect( + [10] + .toBigIntList() + .equalsValues([10.0001.toDecimal()], tolerance: 0.000001), + isFalse); + + expect( + [10, 20].toBigIntList().equalsValues( + [10.0001.toDecimal(), 20.001.toDecimal()], + tolerance: 0.01), + isTrue); + expect( + [10, 20].toBigIntList().equalsValues( + [10.0001.toDecimal(), 20.1.toDecimal()], + tolerance: 0.01), + isFalse); }); }); From 7fb715f505f87885e0d6a6d5a695eb518c3a0af9 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 02:12:46 -0300 Subject: [PATCH 19/23] statistics_extension_num_test.dart --- test/statistics_extension_num_test.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/statistics_extension_num_test.dart b/test/statistics_extension_num_test.dart index d37954b..b65cf13 100644 --- a/test/statistics_extension_num_test.dart +++ b/test/statistics_extension_num_test.dart @@ -160,7 +160,7 @@ void main() { setUp(() {}); test('toBigInt', () { - expect(123.isWholeNumber, isTrue); + expect(123.toBigInt().isWholeNumber, isTrue); expect(-123.toBigInt(), equals(BigInt.from(-123))); }); @@ -588,11 +588,18 @@ void main() { expect([].equalsValues([10]), isFalse); expect([10].toBigIntList().equalsValues([10]), isTrue); + expect([10].toBigIntList().equalsValues([10.toDecimal()]), isTrue); + expect([10].toBigIntList().equalsValues([10.toDynamicInt()]), isTrue); expect( [10] .toBigIntList() .equalsValues([10.toDecimal()], tolerance: 0.000001), isTrue); + expect( + [10] + .toBigIntList() + .equalsValues([10.toDynamicInt()], tolerance: 0.000001), + isTrue); expect([10].toBigIntList().equalsValues([10.0001.toDecimal()]), isFalse); expect( [10].toBigIntList().equalsValues([10.toDecimal()], tolerance: 0.01), From 65ca9d84ebc31e16d4eaf41ae3620375bb66d300 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 02:15:12 -0300 Subject: [PATCH 20/23] statistics_extension_num_test.dart --- test/statistics_extension_num_test.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/statistics_extension_num_test.dart b/test/statistics_extension_num_test.dart index b65cf13..97af2f9 100644 --- a/test/statistics_extension_num_test.dart +++ b/test/statistics_extension_num_test.dart @@ -225,6 +225,11 @@ void main() { expect([].toBigIntList().toDynamicIntList(), equals([])); expect([10, 20].toBigIntList().toDynamicIntList(), equals([10.toDynamicInt(), 20.toDynamicInt()])); + + expect([].toBigIntList().map((e) => e).toDynamicIntList(), + equals([])); + expect([10, 20].toBigIntList().map((e) => e).toDynamicIntList(), + equals([10.toDynamicInt(), 20.toDynamicInt()])); }); test('toDecimalList', () { @@ -235,6 +240,11 @@ void main() { expect([].toBigIntList().toDecimalList(), equals([])); expect([10, 20].toBigIntList().toDecimalList(), equals([10.toDecimal(), 20.toDecimal()])); + + expect([].toBigIntList().map((e) => e).toDecimalList(), + equals([])); + expect([10, 20].toBigIntList().map((e) => e).toDecimalList(), + equals([10.toDecimal(), 20.toDecimal()])); }); test('mean', () { From dbe46643082f83995f6727c66fbc55690b2370e5 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 02:22:46 -0300 Subject: [PATCH 21/23] statistics_base_test.dart --- test/statistics_base_test.dart | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index 411b62e..f50562f 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -862,6 +862,50 @@ void main() { expect(() => Statistics.compute([30, 20, 10], alreadySortedData: true), throwsArgumentError); + + // BigInt: + + expect( + () => StatisticsBigInt.compute([10, 20, 30].toBigIntList(), + alreadySortedData: false), + isNot(throwsArgumentError)); + + expect( + () => StatisticsBigInt.compute([30, 20, 10].toBigIntList(), + alreadySortedData: false), + isNot(throwsArgumentError)); + + expect( + () => StatisticsBigInt.compute([10, 20, 30].toBigIntList(), + alreadySortedData: false), + isNot(throwsArgumentError)); + + expect( + () => StatisticsBigInt.compute([30, 20, 10].toBigIntList(), + alreadySortedData: true), + throwsArgumentError); + + // + + expect( + () => StatisticsDynamicNumber.compute([10, 20, 30].toDecimalList(), + alreadySortedData: false), + isNot(throwsArgumentError)); + + expect( + () => StatisticsDynamicNumber.compute([30, 20, 10].toDecimalList(), + alreadySortedData: false), + isNot(throwsArgumentError)); + + expect( + () => StatisticsDynamicNumber.compute([10, 20, 30].toDecimalList(), + alreadySortedData: false), + isNot(throwsArgumentError)); + + expect( + () => StatisticsDynamicNumber.compute([30, 20, 10].toDecimalList(), + alreadySortedData: true), + throwsArgumentError); }); test('multiplyBy', () { From d99b20e9b3d8a57e8aa00e104fd75683905fbf30 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 02:28:49 -0300 Subject: [PATCH 22/23] statistics_base_test.dart --- test/statistics_base_test.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index f50562f..d5488c9 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -338,6 +338,10 @@ void main() { expect(statisticsBigInt.cast().toString(), equals('{~25 +-11.1803 [10..(30)..40] #4}')); + expect(statisticsBigInt.cast(), isA>()); + expect(statisticsBigInt.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + /////// expect(statisticsDynamicInt.cast(), isA>()); @@ -368,6 +372,10 @@ void main() { expect(statisticsDecimal.cast().toString(), equals('{~25 +-11.1803 [10..(30)..40] #4}')); + expect(statisticsDecimal.cast(), isA>()); + expect(statisticsDecimal.cast().toString(), + equals('{~25 +-11.1803 [10..(30)..40] #4}')); + expect(statisticsDecimal.castDynamicNumber(), isA>()); expect(statisticsDecimal.castDynamicNumber().toString(), From bb72f26d4172b8290e986e63559ccbaf8cd549c8 Mon Sep 17 00:00:00 2001 From: gmpassos Date: Fri, 19 Dec 2025 02:35:56 -0300 Subject: [PATCH 23/23] statistics_base_test.dart --- test/statistics_base_test.dart | 85 ++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/test/statistics_base_test.dart b/test/statistics_base_test.dart index d5488c9..abc7d59 100644 --- a/test/statistics_base_test.dart +++ b/test/statistics_base_test.dart @@ -388,6 +388,91 @@ void main() { /////// + expect( + statistics.getDataFields(), + equals([ + 'mean', + 'standardDeviation', + 'length', + 'min', + 'max', + 'sum', + 'squaresSum' + ])); + expect(statistics.getDataValues(), + equals([25.0, 11.180339887498949, 4, 10, 40, 100, 3000])); + + expect( + statisticsDecimal.getDataFields(), + equals([ + 'mean', + 'standardDeviation', + 'length', + 'min', + 'max', + 'sum', + 'squaresSum' + ])); + expect( + statisticsDecimal.getDataValues(), + equals([ + 25.toDecimal(), + Decimal.parse('11.180339887498948'), + 4, + 10.toDecimal(), + 40.toDecimal(), + 100.toDecimal(), + 3000.toDecimal() + ])); + + expect( + statisticsDynamicInt.getDataFields(), + equals([ + 'mean', + 'standardDeviation', + 'length', + 'min', + 'max', + 'sum', + 'squaresSum' + ])); + expect( + statisticsDynamicInt.getDataValues(), + equals([ + 25.toDecimal(), + Decimal.parse('11.180339887498948'), + 4, + 10.toDynamicInt(), + 40.toDynamicInt(), + 100.toDynamicInt(), + 3000.toDynamicInt() + ])); + + expect( + statisticsBigInt.getDataFields(), + equals([ + 'mean', + 'standardDeviation', + 'length', + 'min', + 'max', + 'sum', + 'squaresSum' + ])); + expect( + statisticsBigInt.getDataValues(), + equals([ + 25.toDecimal(), + Decimal.parse('11.180339887498948'), + 4, + 10.toBigInt(), + 40.toBigInt(), + 100.toBigInt(), + 3000.toBigInt() + ])); + + /////// + expect(statistics.multiplyBy(2).toString(), equals('{~50 +-22.3606 [20..(60)..80] #8}'));