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 : final formatted =
74 0 : string.substring(0, lastNonZeroIndex < 3 ? 3 : lastNonZeroIndex + 1);
75 :
76 0 : if (formatted.endsWith('.')) {
77 0 : return formatted.substring(0, formatted.length - 1);
78 : }
79 :
80 : return formatted;
81 : }
82 :
83 0 : Amount copyWith({
84 : BigInt? value,
85 : int? decimals,
86 : }) {
87 : if (value == null && decimals == null) return this;
88 :
89 0 : return Amount(
90 0 : value: value ?? this.value,
91 0 : decimals: decimals ?? this.decimals,
92 : );
93 : }
94 :
95 0 : bool isOther(Amount other) {
96 0 : return value == other.value && decimals == other.decimals;
97 : }
98 :
99 0 : @override
100 0 : int get hashCode => value.hashCode ^ decimals.hashCode;
101 :
102 1 : @override
103 : bool operator ==(Object other) {
104 : if (identical(this, other)) return true;
105 :
106 1 : return other is Amount &&
107 3 : other.value == value &&
108 0 : other.decimals == decimals;
109 : }
110 :
111 2 : @override
112 : String toString() {
113 8 : return 'Amount{value: $value, decimals: $decimals}: $displayDouble';
114 : }
115 :
116 1 : Map<String, dynamic> toJson() {
117 1 : return {
118 2 : 'value': value.toString(),
119 1 : 'decimals': decimals,
120 : };
121 : }
122 :
123 1 : factory Amount.fromJson(Map json) {
124 1 : return Amount(
125 2 : value: BigInt.parse(json['value']),
126 1 : decimals: json['decimals'],
127 : );
128 : }
129 :
130 0 : Amount convertToDecimals(int newDecimals) {
131 0 : if (newDecimals == decimals) return this;
132 :
133 0 : final newAmount = value * BigInt.from(10).pow(newDecimals - decimals);
134 :
135 0 : return Amount(
136 : value: newAmount,
137 : decimals: newDecimals,
138 : );
139 : }
140 :
141 : /// Operators
142 1 : Amount operator *(Amount other) {
143 1 : return Amount(
144 3 : value: value * other.value,
145 3 : decimals: decimals + other.decimals,
146 : );
147 : }
148 :
149 : // Plus operator
150 2 : Amount operator +(Amount other) {
151 : // Determine the maximum decimals between the two amounts
152 6 : int maxDecimals = max(decimals, other.decimals);
153 :
154 : // Scale both amounts to the same decimal level
155 : BigInt scaledThisValue =
156 12 : value * BigInt.from(pow(10, maxDecimals - decimals));
157 : BigInt scaledOtherValue =
158 12 : other.value * BigInt.from(pow(10, maxDecimals - other.decimals));
159 :
160 : // Perform the addition
161 2 : BigInt resultValue = scaledThisValue + scaledOtherValue;
162 :
163 2 : return Amount(
164 : value: resultValue,
165 : decimals: maxDecimals,
166 : );
167 : }
168 :
169 : // Minus operator
170 1 : Amount operator -(Amount other) {
171 : // Determine the maximum decimals between the two amounts
172 3 : int maxDecimals = max(decimals, other.decimals);
173 :
174 : // Scale both amounts to the same decimal level
175 : BigInt scaledThisValue =
176 6 : value * BigInt.from(pow(10, maxDecimals - decimals));
177 : BigInt scaledOtherValue =
178 6 : other.value * BigInt.from(pow(10, maxDecimals - other.decimals));
179 :
180 : // Perform the subtraction
181 1 : BigInt resultValue = scaledThisValue - scaledOtherValue;
182 :
183 1 : return Amount(
184 : value: resultValue,
185 : decimals: maxDecimals,
186 : );
187 : }
188 :
189 : // Division operator
190 2 : Amount operator /(Amount other) {
191 6 : if (other.value == BigInt.zero) {
192 1 : throw ArgumentError('Cannot divide by zero.');
193 : }
194 :
195 : // Determine the maximum decimals between the two amounts
196 6 : int maxDecimals = max(decimals, other.decimals);
197 :
198 : // Scale both amounts to the same decimal level
199 : BigInt scaledThisValue =
200 12 : value * BigInt.from(pow(10, maxDecimals - decimals));
201 : BigInt scaledOtherValue =
202 12 : other.value * BigInt.from(pow(10, maxDecimals - other.decimals));
203 :
204 : // Perform the division
205 : BigInt resultValue =
206 8 : (scaledThisValue * BigInt.from(pow(10, maxDecimals))) ~/
207 : scaledOtherValue;
208 :
209 2 : return Amount(
210 : value: resultValue,
211 : decimals: maxDecimals,
212 : );
213 : }
214 :
215 2 : bool operator >(Amount other) {
216 6 : return value > other.value;
217 : }
218 :
219 2 : bool operator <(Amount other) {
220 6 : return value < other.value;
221 : }
222 :
223 2 : Amount multiplyAndCeil(double multiplier) {
224 6 : final result = value.toInt() * multiplier;
225 4 : final result_int = result.ceil().toBI;
226 :
227 4 : return Amount(value: result_int, decimals: decimals);
228 : }
229 : }
230 :
231 : extension AmountUtil on int {
232 0 : Amount get asAmount {
233 0 : return Amount.from(value: this, decimals: 0);
234 : }
235 :
236 0 : Amount toAmount(int decimals) {
237 0 : return Amount.from(value: this, decimals: decimals);
238 : }
239 : }
240 :
241 : extension AmountUtilDouble on double {
242 0 : int get decimals {
243 0 : final parts = toString().split('.');
244 :
245 0 : return parts.length == 1 ? 0 : parts[1].length;
246 : }
247 :
248 : // operator *(Amount other) {
249 : }
250 :
251 : extension AmountUtilNum on num {
252 2 : String get toExactString {
253 : // https://stackoverflow.com/questions/62989638/convert-long-double-to-string-without-scientific-notation-dart
254 2 : double value = toDouble();
255 : var sign = "";
256 2 : if (value < 0) {
257 0 : value = -value;
258 : sign = "-";
259 : }
260 2 : var string = value.toString();
261 2 : var e = string.lastIndexOf('e');
262 4 : if (e < 0) return "$sign$string";
263 2 : var hasComma = string.indexOf('.') == 1;
264 1 : var offset = int.parse(
265 4 : string.substring(e + (string.startsWith('-', e + 1) ? 1 : 2)),
266 : );
267 1 : var digits = string.substring(0, 1);
268 :
269 : if (hasComma) {
270 0 : digits += string.substring(2, e);
271 : }
272 :
273 1 : if (offset < 0) {
274 3 : return "${sign}0.${"0" * ~offset}$digits";
275 : }
276 0 : if (offset > 0) {
277 0 : if (offset >= digits.length) {
278 0 : return sign + digits.padRight(offset + 1, "0");
279 : }
280 0 : return "$sign${digits.substring(0, offset + 1)}"
281 0 : ".${digits.substring(offset + 1)}";
282 : }
283 : return digits;
284 : }
285 : }
286 :
287 : extension AmountUtilBigInt on BigInt {
288 0 : BigInt multiply(double other) {
289 0 : final _other = shiftLeftBigInt(other, other.decimals);
290 0 : final result = this * _other;
291 :
292 0 : return discardRightBigInt(
293 : result,
294 0 : other.decimals,
295 : );
296 : }
297 :
298 0 : BigInt shiftLeft(int decimalPlaces) =>
299 0 : this * BigInt.from(pow(10, decimalPlaces));
300 :
301 0 : BigInt shiftRight(int decimalPlaces) =>
302 0 : this ~/ BigInt.from(pow(10, decimalPlaces));
303 : }
304 :
305 0 : BigInt shiftLeftBigInt(num num1, int decimalPlaces) =>
306 0 : BigInt.from(num1 * pow(10, decimalPlaces));
307 :
308 0 : BigInt discardRightBigInt(BigInt num1, int decimalPlaces) =>
309 0 : num1 ~/ BigInt.from(pow(10, decimalPlaces));
310 :
311 0 : double shiftRightBigInt(BigInt num1, int decimalPlaces) =>
312 0 : num1.toInt() / pow(10, decimalPlaces);
|