diff --git a/lib/big_decimal.dart b/lib/big_decimal.dart index e02b799..1382e3b 100644 --- a/lib/big_decimal.dart +++ b/lib/big_decimal.dart @@ -1 +1,2 @@ export 'src/big_decimal.dart'; +export 'src/big_decimal_infinity.dart'; diff --git a/lib/src/big_decimal.dart b/lib/src/big_decimal.dart index 07c3df5..437333e 100644 --- a/lib/src/big_decimal.dart +++ b/lib/src/big_decimal.dart @@ -1,5 +1,7 @@ // ignore_for_file: constant_identifier_names +import 'big_decimal_infinity.dart'; + enum RoundingMode { UP, DOWN, @@ -20,13 +22,13 @@ const zeroCode = 48; const nineCode = 57; class BigDecimal implements Comparable { - BigDecimal._({ + BigDecimal({ required this.intVal, required this.scale, }); factory BigDecimal.fromBigInt(BigInt value) { - return BigDecimal._( + return BigDecimal( intVal: value, scale: 0, ); @@ -35,6 +37,8 @@ class BigDecimal implements Comparable { static BigDecimal zero = BigDecimal.fromBigInt(BigInt.zero); static BigDecimal one = BigDecimal.fromBigInt(BigInt.one); static BigDecimal two = BigDecimal.fromBigInt(BigInt.two); + static BigDecimal infinity = BigDecimalInfinity(); + static BigDecimal negativeInifinity = BigDecimalInfinity(isNegative: true); static int nextNonDigit(String value, [int start = 0]) { var index = start; @@ -56,6 +60,14 @@ class BigDecimal implements Comparable { } factory BigDecimal.parse(String value) { + if (value == double.infinity.toString()) { + return BigDecimal.infinity; + } + + if (value == double.negativeInfinity.toString()) { + return BigDecimal.negativeInifinity; + } + var sign = ''; var index = 0; var nextIndex = 0; @@ -88,7 +100,7 @@ class BigDecimal implements Comparable { index = nextIndex; if (index >= value.length) { - return BigDecimal._( + return BigDecimal( intVal: BigInt.parse('$integerPart$decimalPart'), scale: decimalPart.length, ); @@ -100,7 +112,7 @@ class BigDecimal implements Comparable { case capitalECode: index++; final exponent = int.parse(value.substring(index)); - return BigDecimal._( + return BigDecimal( intVal: BigInt.parse('$integerPart$decimalPart'), scale: decimalPart.length - exponent, ); @@ -117,20 +129,15 @@ class BigDecimal implements Comparable { final int scale; @override - bool operator ==(dynamic other) => - other is BigDecimal && compareTo(other) == 0; + bool operator ==(dynamic other) => other is BigDecimal && compareTo(other) == 0; - bool exactlyEquals(dynamic other) => - other is BigDecimal && intVal == other.intVal && scale == other.scale; + bool exactlyEquals(dynamic other) => other is BigDecimal && intVal == other.intVal && scale == other.scale; - BigDecimal operator +(BigDecimal other) => - _add(intVal, other.intVal, scale, other.scale); + BigDecimal operator +(BigDecimal other) => _add(intVal, other.intVal, scale, other.scale); - BigDecimal operator *(BigDecimal other) => - BigDecimal._(intVal: intVal * other.intVal, scale: scale + other.scale); + BigDecimal operator *(BigDecimal other) => BigDecimal(intVal: intVal * other.intVal, scale: scale + other.scale); - BigDecimal operator -(BigDecimal other) => - _add(intVal, -other.intVal, scale, other.scale); + BigDecimal operator -(BigDecimal other) => _add(intVal, -other.intVal, scale, other.scale); bool operator <(BigDecimal other) => compareTo(other) < 0; @@ -140,34 +147,30 @@ class BigDecimal implements Comparable { bool operator >=(BigDecimal other) => compareTo(other) >= 0; - BigDecimal operator -() => BigDecimal._(intVal: -intVal, scale: scale); + BigDecimal operator -() => BigDecimal(intVal: -intVal, scale: scale); - BigDecimal abs() => BigDecimal._(intVal: intVal.abs(), scale: scale); + BigDecimal abs() => BigDecimal(intVal: intVal.abs(), scale: scale); BigDecimal divide( BigDecimal divisor, { RoundingMode roundingMode = RoundingMode.UNNECESSARY, int? scale, }) => - _divide(intVal, this.scale, divisor.intVal, divisor.scale, - scale ?? this.scale, roundingMode); + _divide(intVal, this.scale, divisor.intVal, divisor.scale, scale ?? this.scale, roundingMode); BigDecimal pow(int n) { if (n >= 0 && n <= 999999999) { // TODO: Check scale of this multiplication final newScale = scale * n; - return BigDecimal._(intVal: intVal.pow(n), scale: newScale); + return BigDecimal(intVal: intVal.pow(n), scale: newScale); } - throw Exception( - 'Invalid operation: Exponent should be between 0 and 999999999'); + throw Exception('Invalid operation: Exponent should be between 0 and 999999999'); } - double toDouble() => - intVal.toDouble() / BigInt.from(10).pow(scale).toDouble(); + double toDouble() => intVal.toDouble() / BigInt.from(10).pow(scale).toDouble(); BigInt toBigInt({RoundingMode roundingMode = RoundingMode.UNNECESSARY}) => withScale(0, roundingMode: roundingMode).intVal; - int toInt({RoundingMode roundingMode = RoundingMode.UNNECESSARY}) => - toBigInt(roundingMode: roundingMode).toInt(); + int toInt({RoundingMode roundingMode = RoundingMode.UNNECESSARY}) => toBigInt(roundingMode: roundingMode).toInt(); BigDecimal withScale( int newScale, { @@ -176,16 +179,15 @@ class BigDecimal implements Comparable { if (scale == newScale) { return this; } else if (intVal.sign == 0) { - return BigDecimal._(intVal: BigInt.zero, scale: newScale); + return BigDecimal(intVal: BigInt.zero, scale: newScale); } else { if (newScale > scale) { final drop = sumScale(newScale, -scale); final intResult = intVal * BigInt.from(10).pow(drop); - return BigDecimal._(intVal: intResult, scale: newScale); + return BigDecimal(intVal: intResult, scale: newScale); } else { final drop = sumScale(scale, -newScale); - return _divideAndRound(intVal, BigInt.from(10).pow(drop), newScale, - roundingMode, newScale); + return _divideAndRound(intVal, BigInt.from(10).pow(drop), newScale, roundingMode, newScale); } } } @@ -198,17 +200,16 @@ class BigDecimal implements Comparable { return intVal.abs().compareTo(BigInt.from(10).pow(r)) < 0 ? r : r + 1; } - static BigDecimal _add( - BigInt intValA, BigInt intValB, int scaleA, int scaleB) { + static BigDecimal _add(BigInt intValA, BigInt intValB, int scaleA, int scaleB) { final scaleDiff = scaleA - scaleB; if (scaleDiff == 0) { - return BigDecimal._(intVal: intValA + intValB, scale: scaleA); + return BigDecimal(intVal: intValA + intValB, scale: scaleA); } else if (scaleDiff < 0) { final scaledX = intValA * BigInt.from(10).pow(-scaleDiff); - return BigDecimal._(intVal: scaledX + intValB, scale: scaleB); + return BigDecimal(intVal: scaledX + intValB, scale: scaleB); } else { final scaledY = intValB * BigInt.from(10).pow(scaleDiff); - return BigDecimal._(intVal: intValA + scaledY, scale: scaleA); + return BigDecimal(intVal: intValA + scaledY, scale: scaleA); } } @@ -221,20 +222,18 @@ class BigDecimal implements Comparable { RoundingMode roundingMode, ) { if (dividend == BigInt.zero) { - return BigDecimal._(intVal: BigInt.zero, scale: scale); + return BigDecimal(intVal: BigInt.zero, scale: scale); } if (sumScale(scale, divisorScale) > dividendScale) { final newScale = scale + divisorScale; final raise = newScale - dividendScale; final scaledDividend = dividend * BigInt.from(10).pow(raise); - return _divideAndRound( - scaledDividend, divisor, scale, roundingMode, scale); + return _divideAndRound(scaledDividend, divisor, scale, roundingMode, scale); } else { final newScale = sumScale(dividendScale, -scale); final raise = newScale - divisorScale; final scaledDivisor = divisor * BigInt.from(10).pow(raise); - return _divideAndRound( - dividend, scaledDivisor, scale, roundingMode, scale); + return _divideAndRound(dividend, scaledDivisor, scale, roundingMode, scale); } } @@ -249,18 +248,16 @@ class BigDecimal implements Comparable { final remainder = dividend.remainder(divisor).abs(); final quotientPositive = dividend.sign == divisor.sign; if (remainder != BigInt.zero) { - if (_needIncrement( - divisor, roundingMode, quotientPositive, quotient, remainder)) { - final intResult = - quotient + (quotientPositive ? BigInt.one : -BigInt.one); - return BigDecimal._(intVal: intResult, scale: scale); + if (_needIncrement(divisor, roundingMode, quotientPositive, quotient, remainder)) { + final intResult = quotient + (quotientPositive ? BigInt.one : -BigInt.one); + return BigDecimal(intVal: intResult, scale: scale); } - return BigDecimal._(intVal: quotient, scale: scale); + return BigDecimal(intVal: quotient, scale: scale); } else { if (preferredScale != scale) { return createAndStripZerosForScale(quotient, scale, preferredScale); } else { - return BigDecimal._(intVal: quotient, scale: scale); + return BigDecimal(intVal: quotient, scale: scale); } } } @@ -287,7 +284,7 @@ class BigDecimal implements Comparable { scaleMut = sumScale(scaleMut, -1); } - return BigDecimal._(intVal: intValMut, scale: scaleMut); + return BigDecimal(intVal: intValMut, scale: scaleMut); } static bool _needIncrement( @@ -297,8 +294,7 @@ class BigDecimal implements Comparable { BigInt quotient, BigInt remainder, ) { - final remainderComparisonToHalfDivisor = - (remainder * BigInt.from(2)).compareTo(divisor); + final remainderComparisonToHalfDivisor = (remainder * BigInt.from(2)).compareTo(divisor); switch (roundingMode) { case RoundingMode.UNNECESSARY: throw Exception('Rounding necessary'); diff --git a/lib/src/big_decimal_infinity.dart b/lib/src/big_decimal_infinity.dart new file mode 100644 index 0000000..790c8a4 --- /dev/null +++ b/lib/src/big_decimal_infinity.dart @@ -0,0 +1,160 @@ +import 'big_decimal.dart'; + +class BigDecimalInfinity extends BigDecimal { + final bool isNegative; + + BigDecimalInfinity({this.isNegative = false}) : super(intVal: BigInt.from(double.maxFinite), scale: 1); + + @override + BigDecimal operator +(BigDecimal other) { + if (other is BigDecimalInfinity) { + if (isNegative && !other.isNegative) { + throw Exception('Invalid operation: NaN'); + } + + if (!isNegative && other.isNegative) { + throw Exception('Invalid operation: NaN'); + } + } + + if (isNegative) return BigDecimal.negativeInifinity; + + return BigDecimal.infinity; + } + + @override + BigDecimal operator *(BigDecimal other) { + if (other is BigDecimalInfinity) { + if (other.isNegative) { + return BigDecimal.negativeInifinity; + } + + return BigDecimal.infinity; + } + + if (other == BigDecimal.zero) { + throw Exception('Invalid operation: NaN'); + } + + return BigDecimal.infinity; + } + + @override + BigDecimal operator -(BigDecimal other) { + if (other is BigDecimalInfinity) { + if (isNegative && other.isNegative) { + throw Exception('Invalid operation: NaN'); + } + + if (!isNegative && !other.isNegative) { + throw Exception('Invalid operation: NaN'); + } + } + + if (other.intVal < BigInt.zero) { + return BigDecimal.negativeInifinity; + } + + return BigDecimal.infinity; + } + + @override + BigDecimal operator -() => isNegative ? BigDecimal.infinity : BigDecimal.negativeInifinity; + + @override + BigDecimal abs() { + return BigDecimal.infinity; + } + + @override + BigDecimal divide( + BigDecimal divisor, { + RoundingMode roundingMode = RoundingMode.UNNECESSARY, + int? scale, + }) { + if (divisor is BigDecimalInfinity) { + throw Exception('Invalid operation: NaN'); + } + + return BigDecimal.infinity; + } + + @override + BigDecimal pow(int n) { + if (isNegative) { + switch (n) { + case 0: + return BigDecimal.fromBigInt(BigInt.one); + case -1: + return BigDecimal.fromBigInt(BigInt.zero); + case 1: + return BigDecimal.negativeInifinity; + } + + if (n < -1) { + return BigDecimal.fromBigInt(BigInt.zero); + } + + return BigDecimal.infinity; + } + + switch (n) { + case 0: + return BigDecimal.fromBigInt(BigInt.one); + case -1: + return BigDecimal.fromBigInt(BigInt.zero); + case 1: + return BigDecimal.negativeInifinity; + } + + if (n < -1) { + return BigDecimal.fromBigInt(BigInt.zero); + } + + return BigDecimal.infinity; + } + + @override + double toDouble() { + return isNegative ? double.negativeInfinity : double.infinity; + } + + @override + BigInt toBigInt({RoundingMode roundingMode = RoundingMode.UNNECESSARY}) { + throw UnsupportedError('Value must be finite: ${isNegative ? "-Infinity" : "Infinity"}'); + } + + @override + int toInt({RoundingMode roundingMode = RoundingMode.UNNECESSARY}) { + throw UnsupportedError('Value must be finite: ${isNegative ? "-Infinity" : "Infinity"}'); + } + + @override + BigDecimal withScale(int newScale, {RoundingMode roundingMode = RoundingMode.UNNECESSARY}) { + throw UnsupportedError('Value must be finite: ${isNegative ? "-Infinity" : "Infinity"}'); + } + + @override + int compareTo(BigDecimal other) { + if (other is BigDecimalInfinity) { + return 0; + } + + return 1; + } + + @override + bool exactlyEquals(other) { + if (other is BigDecimalInfinity) { + return true; + } + + return false; + } + + @override + String toString() => isNegative ? "-Infinity" : "Infinity"; + + @override + String toPlainString() => isNegative ? "-Infinity" : "Infinity"; +} diff --git a/test/big_decimal_test.dart b/test/big_decimal_test.dart index 3090c1e..404379a 100644 --- a/test/big_decimal_test.dart +++ b/test/big_decimal_test.dart @@ -1,4 +1,5 @@ import 'package:big_decimal/src/big_decimal.dart'; +import 'package:big_decimal/src/big_decimal_infinity.dart'; import 'package:test/test.dart'; import 'helpers/coerce.dart'; @@ -7,6 +8,25 @@ import 'helpers/tabular.dart'; void main() { group('parse', () { + group('infinity parse', () { + test('Should return BigDecimalInfinity', () { + final bigdec = BigDecimal.parse('Infinity'); + + expect(true, bigdec is BigDecimalInfinity); + }); + + test('Should return BigDecimalInfinity', () { + final bigdec = BigDecimal.parse('-Infinity'); + + expect(true, bigdec is BigDecimalInfinity); + }); + + test('Should return BigDecimal', () { + final bigdec = BigDecimal.parse('0'); + + expect(false, bigdec is BigDecimalInfinity); + }); + }); group( 'parses correctly', tabular((String s, int intVal, int scale, int precision) { @@ -22,14 +42,11 @@ void main() { tabCase(['2', 2, 0, 1], 'positive integer with no decimal places'), tabCase(['-2', -2, 0, 1], 'negative integer with no decimal places'), tabCase(['2.000', 2000, 3, 4], 'positive integer with decimal places'), - tabCase( - ['-2.000', -2000, 3, 4], 'negative integer with decimal places'), + tabCase(['-2.000', -2000, 3, 4], 'negative integer with decimal places'), tabCase(['2.01', 201, 2, 3], 'positive number with decimal places'), tabCase(['-2.01', -201, 2, 3], 'negative number with decimal places'), - tabCase( - ['-.2e1', -2, 0, 1], 'negative with decimal places and exponent'), - tabCase(['-.2e-1', -2, 2, 1], - 'negative with decimal places and negative exponent'), + tabCase(['-.2e1', -2, 0, 1], 'negative with decimal places and exponent'), + tabCase(['-.2e-1', -2, 2, 1], 'negative with decimal places and negative exponent'), tabCase(['10.00e2', 1000, 0, 4], 'with exponential'), tabCase(['10e2', 10, -2, 2], 'with exponential and negative scale'), tabCase(['10.e2', 10, -2, 2], 'with exponential and negative scale'), @@ -114,8 +131,7 @@ void main() { group('division', () { group( 'divide', - tabular((Object a, Object b, Object result, - [RoundingMode roundingMode = RoundingMode.UNNECESSARY, int? scale]) { + tabular((Object a, Object b, Object result, [RoundingMode roundingMode = RoundingMode.UNNECESSARY, int? scale]) { expect( a.dec.divide(b.dec, roundingMode: roundingMode, scale: scale), exactly(result.dec), @@ -138,8 +154,7 @@ void main() { ]), ); - test('unable to divide to repeating decimals without proper RoundingMode', - () { + test('unable to divide to repeating decimals without proper RoundingMode', () { // 0.3333333... expect(() => '10'.dec.divide('3'.dec), throwsException); // 7.3828282... @@ -267,8 +282,7 @@ void main() { group('successfully changing the scale', () { group( 'simple cases', - tabular((Object a, int newScale, Object result, - [RoundingMode roundingMode = RoundingMode.UNNECESSARY]) { + tabular((Object a, int newScale, Object result, [RoundingMode roundingMode = RoundingMode.UNNECESSARY]) { expect( a.dec.withScale(newScale, roundingMode: roundingMode), exactly(result.dec), @@ -292,26 +306,18 @@ void main() { String half_even, Object unnecessary, ) { - BigDecimal round(RoundingMode mode) => - input.dec.withScale(0, roundingMode: mode); + BigDecimal round(RoundingMode mode) => input.dec.withScale(0, roundingMode: mode); expect(round(RoundingMode.UP), exactly(up.dec), reason: 'UP'); expect(round(RoundingMode.DOWN), exactly(down.dec), reason: 'DOWN'); - expect(round(RoundingMode.CEILING), exactly(ceiling.dec), - reason: 'CEILING'); - expect(round(RoundingMode.FLOOR), exactly(floor.dec), - reason: 'FLOOR'); - expect(round(RoundingMode.HALF_UP), exactly(half_up.dec), - reason: 'HALF_UP'); - expect(round(RoundingMode.HALF_DOWN), exactly(half_down.dec), - reason: 'HALF_DOWN'); - expect(round(RoundingMode.HALF_EVEN), exactly(half_even.dec), - reason: 'HALF_EVEN'); + expect(round(RoundingMode.CEILING), exactly(ceiling.dec), reason: 'CEILING'); + expect(round(RoundingMode.FLOOR), exactly(floor.dec), reason: 'FLOOR'); + expect(round(RoundingMode.HALF_UP), exactly(half_up.dec), reason: 'HALF_UP'); + expect(round(RoundingMode.HALF_DOWN), exactly(half_down.dec), reason: 'HALF_DOWN'); + expect(round(RoundingMode.HALF_EVEN), exactly(half_even.dec), reason: 'HALF_EVEN'); if (unnecessary is String) { - expect(round(RoundingMode.UNNECESSARY), exactly(unnecessary.dec), - reason: 'UNNECESSARY'); + expect(round(RoundingMode.UNNECESSARY), exactly(unnecessary.dec), reason: 'UNNECESSARY'); } else { - expect(() => round(RoundingMode.UNNECESSARY), unnecessary, - reason: 'UNNECESSARY'); + expect(() => round(RoundingMode.UNNECESSARY), unnecessary, reason: 'UNNECESSARY'); } }, [ // Input UP DOWN CEILING FLOOR HALF_UP HALF_DOWN HALF_EVEN UNNECESSARY @@ -321,50 +327,10 @@ void main() { tabCase(['1.1', '2', '1', '2', '1', '1', '1', '1', throwsException]), tabCase(['1.0', '1', '1', '1', '1', '1', '1', '1', '1']), tabCase(['-1.0', '-1', '-1', '-1', '-1', '-1', '-1', '-1', '-1']), - tabCase([ - '-1.1', - '-2', - '-1', - '-1', - '-2', - '-1', - '-1', - '-1', - throwsException - ]), - tabCase([ - '-1.6', - '-2', - '-1', - '-1', - '-2', - '-2', - '-2', - '-2', - throwsException - ]), - tabCase([ - '-2.5', - '-3', - '-2', - '-2', - '-3', - '-3', - '-2', - '-2', - throwsException - ]), - tabCase([ - '-5.5', - '-6', - '-5', - '-5', - '-6', - '-6', - '-5', - '-6', - throwsException - ]), + tabCase(['-1.1', '-2', '-1', '-1', '-2', '-1', '-1', '-1', throwsException]), + tabCase(['-1.6', '-2', '-1', '-1', '-2', '-2', '-2', '-2', throwsException]), + tabCase(['-2.5', '-3', '-2', '-2', '-3', '-3', '-2', '-2', throwsException]), + tabCase(['-5.5', '-6', '-5', '-5', '-6', '-6', '-5', '-6', throwsException]), ]), ); }); @@ -394,8 +360,7 @@ void main() { group( 'toBigInt', - tabular((BigDecimal bd, BigInt bint, - [RoundingMode roundingMode = RoundingMode.UNNECESSARY]) { + tabular((BigDecimal bd, BigInt bint, [RoundingMode roundingMode = RoundingMode.UNNECESSARY]) { expect(bd.toBigInt(roundingMode: roundingMode), bint); }, [ tabCase(['1.5'.dec, BigInt.from(1), RoundingMode.DOWN]), @@ -412,21 +377,14 @@ void main() { tabCase(['-1.5'.dec, BigInt.from(-2), RoundingMode.HALF_EVEN]), tabCase(['-1.5'.dec, BigInt.from(-2), RoundingMode.HALF_UP]), tabCase(['-1.5'.dec, BigInt.from(-1), RoundingMode.HALF_DOWN]), - tabCase([ - '92233720368547758089999'.dec, - BigInt.parse('92233720368547758089999') - ], 'very large integer'), - tabCase([ - '-92233720368547758089999'.dec, - BigInt.parse('-92233720368547758089999') - ], 'very small integer'), + tabCase(['92233720368547758089999'.dec, BigInt.parse('92233720368547758089999')], 'very large integer'), + tabCase(['-92233720368547758089999'.dec, BigInt.parse('-92233720368547758089999')], 'very small integer'), ]), ); group( 'toInt', - tabular((BigDecimal bd, int i, - [RoundingMode roundingMode = RoundingMode.UNNECESSARY]) { + tabular((BigDecimal bd, int i, [RoundingMode roundingMode = RoundingMode.UNNECESSARY]) { expect(bd.toInt(roundingMode: roundingMode), i); }, [ tabCase(['1.5'.dec, 1, RoundingMode.DOWN]), @@ -443,10 +401,8 @@ void main() { tabCase(['-1.5'.dec, -2, RoundingMode.HALF_EVEN]), tabCase(['-1.5'.dec, -2, RoundingMode.HALF_UP]), tabCase(['-1.5'.dec, -1, RoundingMode.HALF_DOWN]), - tabCase(['92233720368547758089999'.dec, 9223372036854775807], - 'very large integer'), - tabCase(['-92233720368547758089999'.dec, -9223372036854775808], - 'very small integer'), + tabCase(['92233720368547758089999'.dec, 9223372036854775807], 'very large integer'), + tabCase(['-92233720368547758089999'.dec, -9223372036854775808], 'very small integer'), ]), );