Line data Source code
1 : import 'dart:math';
2 : import 'package:walletkit_dart/src/utils/int.dart';
3 :
4 : class Amount {
5 : final BigInt value;
6 : final int decimals;
7 :
8 16 : const Amount({
9 : required this.value,
10 : required this.decimals,
11 : });
12 :
13 0 : Amount convertTo(int newDecimals) {
14 0 : return Amount(value: value, decimals: newDecimals);
15 : }
16 :
17 : /// Converts the given value to the smallest unit of the currency
18 2 : factory Amount.convert({
19 : required num value,
20 : required int decimals,
21 : }) {
22 2 : var value_s = value.toExactString;
23 :
24 2 : final parts = value_s.split('.');
25 :
26 8 : var dec = parts.length == 1 ? 0 : parts[1].length;
27 :
28 2 : if (dec > decimals) {
29 0 : value_s = value_s.substring(0, value_s.length - (dec - decimals));
30 : dec = decimals;
31 : }
32 :
33 4 : var value_int = BigInt.tryParse(value_s.replaceAll('.', ''));
34 :
35 : if (value_int == null) {
36 0 : throw Exception('Invalid value: $value');
37 : }
38 :
39 10 : value_int = value_int * BigInt.from(10).pow(decimals.toInt() - dec);
40 :
41 2 : return Amount(
42 : value: value_int,
43 : decimals: decimals,
44 : );
45 : }
46 :
47 : /// Converts the given value to a BigInt
48 : /// This is useful when the value is already in the smallest unit of the currency
49 1 : Amount.from({
50 : required int value,
51 : required this.decimals,
52 1 : }) : value = BigInt.from(value);
53 :
54 3 : static Amount get zero => Amount(value: BigInt.from(0), decimals: 0);
55 :
56 0 : static Amount get negativeOne => Amount(value: BigInt.from(-1), decimals: 0);
57 :
58 4 : double get displayDouble {
59 20 : return value / BigInt.from(10).pow(decimals);
60 : }
61 :
62 0 : String get displayValue {
63 0 : final firstBI = value ~/ BigInt.from(10).pow(decimals);
64 :
65 0 : final secondBI = value % BigInt.from(10).pow(decimals);
66 :
67 0 : final string = '$firstBI.${secondBI.toString().padLeft(decimals, '0')}';
68 :
69 0 : final lastNonZeroIndex = string.lastIndexOf(RegExp(r'[^0]'));
70 :
71 0 : if (lastNonZeroIndex == -1) return string;
72 :
73 0 : final formatted = string.substring(0, lastNonZeroIndex < 3 ? 3 : lastNonZeroIndex + 1);
74 :
75 0 : if (formatted.endsWith('.')) {
76 0 : return formatted.substring(0, formatted.length - 1);
77 : }
78 :
79 : return formatted;
80 : }
81 :
82 0 : Amount copyWith({
83 : BigInt? value,
84 : int? decimals,
85 : }) {
86 : if (value == null && decimals == null) return this;
87 :
88 0 : return Amount(
89 0 : value: value ?? this.value,
90 0 : decimals: decimals ?? this.decimals,
91 : );
92 : }
93 :
94 0 : bool isOther(Amount other) {
95 0 : return value == other.value && decimals == other.decimals;
96 : }
97 :
98 0 : @override
99 0 : int get hashCode => value.hashCode ^ decimals.hashCode;
100 :
101 1 : @override
102 : bool operator ==(Object other) {
103 : if (identical(this, other)) return true;
104 :
105 4 : return other is Amount && other.value == value && other.decimals == decimals;
106 : }
107 :
108 2 : @override
109 : String toString() {
110 8 : return 'Amount{value: $value, decimals: $decimals}: $displayDouble';
111 : }
112 :
113 1 : Map<String, dynamic> toJson() {
114 1 : return {
115 2 : 'value': value.toString(),
116 1 : 'decimals': decimals,
117 : };
118 : }
119 :
120 1 : factory Amount.fromJson(Map json) {
121 1 : return Amount(
122 2 : value: BigInt.parse(json['value']),
123 1 : decimals: json['decimals'],
124 : );
125 : }
126 :
127 0 : Amount convertToDecimals(int newDecimals) {
128 0 : if (newDecimals == decimals) return this;
129 :
130 0 : final newAmount = value * BigInt.from(10).pow(newDecimals - decimals);
131 :
132 0 : return Amount(
133 : value: newAmount,
134 : decimals: newDecimals,
135 : );
136 : }
137 :
138 : /// Operators
139 1 : Amount operator *(Amount other) {
140 1 : return Amount(
141 3 : value: value * other.value,
142 3 : decimals: decimals + other.decimals,
143 : );
144 : }
145 :
146 : // Plus operator
147 2 : Amount operator +(Amount other) {
148 : // Determine the maximum decimals between the two amounts
149 6 : int maxDecimals = max(decimals, other.decimals);
150 :
151 : // Scale both amounts to the same decimal level
152 12 : BigInt scaledThisValue = value * BigInt.from(pow(10, maxDecimals - decimals));
153 12 : BigInt scaledOtherValue = other.value * BigInt.from(pow(10, maxDecimals - other.decimals));
154 :
155 : // Perform the addition
156 2 : BigInt resultValue = scaledThisValue + scaledOtherValue;
157 :
158 2 : return Amount(
159 : value: resultValue,
160 : decimals: maxDecimals,
161 : );
162 : }
163 :
164 : // Minus operator
165 1 : Amount operator -(Amount other) {
166 : // Determine the maximum decimals between the two amounts
167 3 : int maxDecimals = max(decimals, other.decimals);
168 :
169 : // Scale both amounts to the same decimal level
170 6 : BigInt scaledThisValue = value * BigInt.from(pow(10, maxDecimals - decimals));
171 6 : BigInt scaledOtherValue = other.value * BigInt.from(pow(10, maxDecimals - other.decimals));
172 :
173 : // Perform the subtraction
174 1 : BigInt resultValue = scaledThisValue - scaledOtherValue;
175 :
176 1 : return Amount(
177 : value: resultValue,
178 : decimals: maxDecimals,
179 : );
180 : }
181 :
182 : // Division operator
183 2 : Amount operator /(Amount other) {
184 6 : if (other.value == BigInt.zero) {
185 1 : throw ArgumentError('Cannot divide by zero.');
186 : }
187 :
188 : // Determine the maximum decimals between the two amounts
189 6 : int maxDecimals = max(decimals, other.decimals);
190 :
191 : // Scale both amounts to the same decimal level
192 12 : BigInt scaledThisValue = value * BigInt.from(pow(10, maxDecimals - decimals));
193 12 : BigInt scaledOtherValue = other.value * BigInt.from(pow(10, maxDecimals - other.decimals));
194 :
195 : // Perform the division
196 8 : BigInt resultValue = (scaledThisValue * BigInt.from(pow(10, maxDecimals))) ~/ scaledOtherValue;
197 :
198 2 : return Amount(
199 : value: resultValue,
200 : decimals: maxDecimals,
201 : );
202 : }
203 :
204 2 : bool operator >(Amount other) {
205 6 : return value > other.value;
206 : }
207 :
208 2 : bool operator <(Amount other) {
209 6 : return value < other.value;
210 : }
211 :
212 2 : Amount multiplyAndCeil(double multiplier) {
213 6 : final result = value.toInt() * multiplier;
214 4 : final result_int = result.ceil().toBI;
215 :
216 4 : return Amount(value: result_int, decimals: decimals);
217 : }
218 : }
219 :
220 : extension AmountUtil on int {
221 0 : Amount get asAmount {
222 0 : return Amount.from(value: this, decimals: 0);
223 : }
224 :
225 0 : Amount toAmount(int decimals) {
226 0 : return Amount.from(value: this, decimals: decimals);
227 : }
228 : }
229 :
230 : extension AmountUtilDouble on double {
231 0 : int get decimals {
232 0 : final parts = toString().split('.');
233 :
234 0 : return parts.length == 1 ? 0 : parts[1].length;
235 : }
236 :
237 : // operator *(Amount other) {
238 : }
239 :
240 : extension AmountUtilNum on num {
241 2 : String get toExactString {
242 : // https://stackoverflow.com/questions/62989638/convert-long-double-to-string-without-scientific-notation-dart
243 2 : double value = toDouble();
244 : var sign = "";
245 2 : if (value < 0) {
246 0 : value = -value;
247 : sign = "-";
248 : }
249 2 : var string = value.toString();
250 2 : var e = string.lastIndexOf('e');
251 4 : if (e < 0) return "$sign$string";
252 2 : var hasComma = string.indexOf('.') == 1;
253 1 : var offset = int.parse(
254 4 : string.substring(e + (string.startsWith('-', e + 1) ? 1 : 2)),
255 : );
256 1 : var digits = string.substring(0, 1);
257 :
258 : if (hasComma) {
259 0 : digits += string.substring(2, e);
260 : }
261 :
262 1 : if (offset < 0) {
263 3 : return "${sign}0.${"0" * ~offset}$digits";
264 : }
265 0 : if (offset > 0) {
266 0 : if (offset >= digits.length) {
267 0 : return sign + digits.padRight(offset + 1, "0");
268 : }
269 0 : return "$sign${digits.substring(0, offset + 1)}"
270 0 : ".${digits.substring(offset + 1)}";
271 : }
272 : return digits;
273 : }
274 : }
275 :
276 : extension AmountUtilBigInt on BigInt {
277 0 : BigInt multiply(double other) {
278 0 : final _other = shiftLeftBigInt(other, other.decimals);
279 0 : final result = this * _other;
280 :
281 0 : return discardRightBigInt(
282 : result,
283 0 : other.decimals,
284 : );
285 : }
286 :
287 0 : BigInt shiftLeft(int decimalPlaces) => this * BigInt.from(pow(10, decimalPlaces));
288 :
289 0 : BigInt shiftRight(int decimalPlaces) => this ~/ BigInt.from(pow(10, decimalPlaces));
290 : }
291 :
292 0 : BigInt shiftLeftBigInt(num num1, int decimalPlaces) => BigInt.from(num1 * pow(10, decimalPlaces));
293 :
294 0 : BigInt discardRightBigInt(BigInt num1, int decimalPlaces) =>
295 0 : num1 ~/ BigInt.from(pow(10, decimalPlaces));
296 :
297 0 : double shiftRightBigInt(BigInt num1, int decimalPlaces) => num1.toInt() / pow(10, decimalPlaces);
|