diff --git a/lib/utilities/amount.dart b/lib/utilities/amount.dart new file mode 100644 index 000000000..6955e2489 --- /dev/null +++ b/lib/utilities/amount.dart @@ -0,0 +1,43 @@ +import 'package:decimal/decimal.dart'; + +final _ten = BigInt.from(10); + +class Amount { + Amount({ + required BigInt rawValue, + required this.fractionDigits, + }) : assert(fractionDigits >= 0), + _value = rawValue; + + /// truncate double value to [fractionDigits] places + Amount.fromDouble(double amount, {required this.fractionDigits}) + : assert(fractionDigits >= 0), + _value = + Decimal.parse(amount.toString()).shift(fractionDigits).toBigInt(); + + /// truncate decimal value to [fractionDigits] places + Amount.fromDecimal(Decimal amount, {required this.fractionDigits}) + : assert(fractionDigits >= 0), + _value = amount.shift(fractionDigits).toBigInt(); + + // =========================================================================== + // ======= Instance properties =============================================== + + final int fractionDigits; + final BigInt _value; + + // =========================================================================== + // ======= Getters =========================================================== + + /// raw base value + BigInt get raw => _value; + + /// actual decimal vale represented + Decimal get decimal => + (Decimal.fromBigInt(_value) / _ten.pow(fractionDigits).toDecimal()) + .toDecimal(scaleOnInfinitePrecision: fractionDigits); + + /// convenience getter + @Deprecated("provided for convenience only. Use fractionDigits instead.") + int get decimals => fractionDigits; +} diff --git a/pubspec.lock b/pubspec.lock index 6aed3f88c..704bbe10e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1358,7 +1358,7 @@ packages: source: hosted version: "4.0.0" rational: - dependency: transitive + dependency: "direct main" description: name: rational sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf diff --git a/pubspec.yaml b/pubspec.yaml index 1db8de99b..fc75379ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -150,6 +150,7 @@ dependencies: dart_bs58: ^1.0.1 dart_bs58check: ^3.0.2 hex: ^0.2.0 + rational: ^2.2.2 dev_dependencies: flutter_test: diff --git a/test/utilities/amount_test.dart b/test/utilities/amount_test.dart new file mode 100644 index 000000000..0274ea2bb --- /dev/null +++ b/test/utilities/amount_test.dart @@ -0,0 +1,81 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/utilities/amount.dart'; + +void main() { + test("Basic Amount Constructor tests", () { + Amount amount = Amount(rawValue: BigInt.two, fractionDigits: 0); + expect(amount.fractionDigits, 0); + expect(amount.raw, BigInt.two); + expect(amount.decimal, Decimal.fromInt(2)); + + amount = Amount(rawValue: BigInt.two, fractionDigits: 2); + expect(amount.fractionDigits, 2); + expect(amount.raw, BigInt.two); + expect(amount.decimal, Decimal.parse("0.02")); + + amount = Amount(rawValue: BigInt.from(123456789), fractionDigits: 7); + expect(amount.fractionDigits, 7); + expect(amount.raw, BigInt.from(123456789)); + expect(amount.decimal, Decimal.parse("12.3456789")); + + bool didThrow = false; + try { + amount = Amount(rawValue: BigInt.one, fractionDigits: -1); + } catch (_) { + didThrow = true; + } + expect(didThrow, true); + }); + + test("Named fromDouble Amount Constructor tests", () { + Amount amount = Amount.fromDouble(2.0, fractionDigits: 0); + expect(amount.fractionDigits, 0); + expect(amount.raw, BigInt.two); + expect(amount.decimal, Decimal.fromInt(2)); + + amount = Amount.fromDouble(2.0, fractionDigits: 2); + expect(amount.fractionDigits, 2); + expect(amount.raw, BigInt.from(200)); + expect(amount.decimal, Decimal.fromInt(2)); + + amount = Amount.fromDouble(0.0123456789, fractionDigits: 7); + expect(amount.fractionDigits, 7); + expect(amount.raw, BigInt.from(123456)); + expect(amount.decimal, Decimal.parse("0.0123456")); + + bool didThrow = false; + try { + amount = Amount.fromDouble(2.0, fractionDigits: -1); + } catch (_) { + didThrow = true; + } + expect(didThrow, true); + }); + + test("Named fromDecimal Amount Constructor tests", () { + Amount amount = Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 0); + expect(amount.fractionDigits, 0); + expect(amount.raw, BigInt.two); + expect(amount.decimal, Decimal.fromInt(2)); + + amount = Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 2); + expect(amount.fractionDigits, 2); + expect(amount.raw, BigInt.from(200)); + expect(amount.decimal, Decimal.fromInt(2)); + + amount = + Amount.fromDecimal(Decimal.parse("0.0123456789"), fractionDigits: 7); + expect(amount.fractionDigits, 7); + expect(amount.raw, BigInt.from(123456)); + expect(amount.decimal, Decimal.parse("0.0123456")); + + bool didThrow = false; + try { + amount = Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: -1); + } catch (_) { + didThrow = true; + } + expect(didThrow, true); + }); +}