yet_another_decimal 1.0.2
yet_another_decimal: ^1.0.2 copied to clipboard
Yet another package for working with decimals without loss of precision, including two versions - one for large numbers, based on BigInt, and a fast version, based on int.
Yet another decimal #
It's yet another package for fixed point decimals.
Table of contents #
-
1.1. What packages are already in place?
1.2. What's it supposed to be?
1.3. Package performance
-
2.2. Performance
2.3.
Decimal
optimization
Why? #
As of February 2025, there are several packages on pub.dev that work with decimals.
What packages are already in place? #
decimal
A wonderful package that works correctly with decimals. It exists since 2014 and is constantly updated. In one of the latest updates (3.2.0), performance has been significantly improved. Before that, speed was the weak point of this package. This was one of the reasons why yet_another_decimal appeared, since I started writing it before 3.2.0. However, I would have written it anyway. More about it below.
fixed
The package has difficulty dividing. You can't just do a 1 / 8 operation and get the expected 0.125:
final a = Fixed.fromInt(1, scale: 0); // 1
final b = Fixed.fromInt(8, scale: 0); // 8
print('$a / $b = ${a / b}'); // 1 / 8 = 0
final c = Fixed.fromInt(10, scale: 1); // 1.0
final d = Fixed.fromInt(80, scale: 1); // 8.0
print('$c / $d = ${c / d}'); // 1.0 / 8.0 = 0.1
final e = Fixed.fromInt(100, scale: 2); // 1.00
final f = Fixed.fromInt(800, scale: 2); // 8.00
print('$e / $f = ${e / f}'); // 1.00 / 8.00 = 0.13
The result depends on the scale of the numerator and denominator. That is, the division method does not calculate the scale of the result. You have to do it yourself.
final a = Fixed.fromInt(1, scale: 0).copyWith(scale: 3); // 1.000
final b = Fixed.fromInt(8, scale: 0); // 8
print('$a / $b = ${a / b}'); // 1.000 / 8 = 0.125
But the main drawback of the package is not even that, but the fact that
double
is used for the division operation under the hood:
final a = Fixed.parse('111111111111111111');
final b = a * a;
print(b); // 12345679012345678987654320987654321
print(b / a); // 111111111111111120 (!)
The reason for the error is that double has limited precision, and in this example we have gone beyond the limits of that precision. But fixed point decimals are used to avoid errors in floating-point operations, not the other way around. In my opinion, this is a very bad solution.
decimal_type
This package also uses to divide double
, but unlike
fixed it doesn't know some corner case:
var a = Decimal(BigInt.parse('644385861467633436300000'), decimalPrecision: 0);
var b = Decimal.fromInt(123);
print('$a / $b = ${a / b}'); // FormatException: Could not parse BigInt 238909442826288e+21
First the result of division is calculated as double
, then it is converted to
a string using double.toStringAsFixed
, and the string is then converted to
BigInt
. But double.toStringAsFixed
does not always return an "asFixed"
result. When the error limit is reached, the method switches to
double.toStringAsExponential
. The author did not notice this feature. But we
found out faster what is hiding under the hood.
big_decimal
This one seems to have been ported over from Java:
A bugless implementation of BigDecimal in Dart based on Java's BigDecimal.
But it doesn't just divide 1 by 8 in it:
final a = BigDecimal.one;
final b = BigDecimal.parse('8');
print('$a / $b = ${a.divide(b)}'); // Exception: Rounding necessary
Numbers can be divided by specifying the rounding mode. But that's not what we wanted to do.
final a = BigDecimal.one;
final b = BigDecimal.parse('8');
print('$a / $b = ${a.divide(b, roundingMode: RoundingMode.FLOOR)}'); // 1 / 8 = 0
Or by changing the scale of the numerator:
final a = BigDecimal.parse('1.000');
final b = BigDecimal.parse('8');
print('$a / $b = ${a.divide(b)}'); // 1.000 / 8 = 0.125
If we don't guess the scale, we get an error.
That's how "a bugless implementation of BigDecimal" works.
What's it supposed to be? #
Three packages out of four did not satisfy me because of bugs in calculations,
incomplete functionality (division) or use of double
under the hood.
The decimal and
yet_another_decimal
does not have the above division problems. No need to calculate scale
yourself, and no double
under the hood.
decimal returns the result as Rational
(rational), since not every division
result can be represented by a decimal. But it can be easily converted
to Decimal
:
final a = Decimal.one;
final b = Decimal.fromInt(256);
print('$a / $b = ${a / b}'); // 1 / 256 = 1/256
print('$a / $b = ${(a / b).toDecimal()}'); // 1 / 256 = 0.00390625
If the result cannot be represented as a decimal, i.e. the number has an
infinite number of decimal places (has infinite precision), an exception will
be thrown. But if you pass scaleOnInfinitePrecision
to toDecimal
to limit
the precision, the number will be converted to decimal with loss of precision
and no exception will be thrown.
final a = Decimal.one;
final b = Decimal.fromInt(3);
print('$a / $b = ${a / b}'); // 1 / 3 = 1/3
print('$a / $b = ${(a / b).toDecimal(scaleOnInfinitePrecision: 6)}'); // 1 / 3 = 0.333333
yet_another_decimal does the opposite and returns the result immediately:
final a = Decimal.one;
final b = Decimal(256);
print('$a / $b = ${a / b}'); // 1 / 256 = 0.00390625
I wanted a package that works with decimal to return the result as decimal by default. I'm counting on the fact that whoever is using the division operation knows what they are doing, and understands in which cases they can get decimal when dividing, and in which cases they will go beyond the capabilities of decimal.
If the result cannot be obtained as a decimal, an exception will be thrown. It can be caught and handled to get the desired result:
final a = Decimal.one;
final b = Decimal(3);
try {
print('$a / $b = ${a / b}');
} on DecimalDivideException catch (e) {
print('${e.dividend} / ${e.divisor} = ${e.fraction}'); // 1 / 3 = 1/3
print('${e.dividend} / ${e.divisor} = ${e.quotientWithRemainder}'); // 1 / 3 = 0 remainder 1
print('${e.dividend} / ${e.divisor} = ${e.round(6)}'); // 1 / 3 = 0.333333
}
It is possible to avoid exceptions by using one of the methods:
divideToDouble
, divideToFraction
, divideWithRemainder
.
This way I tried to avoid different interpretations. If you need the result as
double
, say so explicitly:
print('$a / $b = ${a.divideToDouble(b)}'); // 0.3333333333333333
print('$a / $b = ${a.divideToFraction(b)}'); // 1/3
print('$a / $b = ${a.divideWithRemainder(b)}'); // 0 remainder 1
The approach implemented in decimal is convenient because it allows to perform a number of actions, the intermediate results of which cannot be represented as a decimal, but the final result is still expected to be a decimal. For example: 1 / 3 * 9:
final rational = Decimal.fromInt(1) / Decimal.fromInt(3) * Decimal.fromInt(9).toRational();
final decimal = rational.toDecimal(); // 9
A package that works only with decimals will not be able to solve such an example so elegantly. Or you will have to resort to rounding and lose precision:
1 / 3 = 0.333
0.333 * 9 = 2.997
But you can use additional solutions for working with fractions, such as the fraction, or the already mentioned rational.
yet_another_decimal has its own
Fraction
class, which provides basic functions for working with fraction.
final a = Fraction(BigInt.from(1), BigInt.from(2));
final b = Fraction(BigInt.from(1), BigInt.from(3));
final f1 = a * b;
final f2 = a / b;
final f3 = a + b;
final f4 = a - b;
print('($a) * ($b) = $f1 -> ${f1.round(6)}'); // (1/2) * (1/3) = 1/6 -> 0.166667
print('($a) / ($b) = $f2 -> ${f2.toDecimal()}'); // (1/2) / (1/3) = 3/2 -> 1.5
print('($a) + ($b) = $f3 -> ${f3.round(6)}'); // (1/2) + (1/3) = 5/6 -> 0.833333
print('($a) - ($b) = $f4 -> ${f4.round(6)}'); // (1/2) - (1/3) = 1/6 -> 0.166667
Package performance #
I care about performance, so I wrote tests to check packages. When I did this, I was not yet aware of the bugs I wrote above. So the result might seem strange. It's as if there were only two packages to compare: decimal and yet_another_decimal.
The tests were performed on Apple M2 Pro 32 Gb. The code of the tests was written with the help of benchmark_harness. Each test was run for at least 2 sec, during which time the test exercise was executed until the end time was reached. Each exercise has 100 cycles of performing an operation. A single operation performs a series of identical actions on multiple values. In the table you see the average time to complete one operation in microseconds. Asterisks indicate the best results and close to them (up to 10% difference).
Absolute values are not important because they will differ from computer to computer, from startup to startup. All that matters is comparing the tests with each other.
Running Tests:
dart compile exe example/bin/benchmark.dart && example/bin/benchmark.exe
decimal | decimal_type | fixed | big_decimal | yet_another_decimal | |
---|---|---|---|---|---|
add | 1.863 µs | 3.340 µs | 2.386 µs | 2.276 µs | ★ 1.694 µs |
multiply-large | ★ 0.135 µs | ★ 0.131 µs | 0.175 µs | ★ 0.132 µs | ★ 0.129 µs |
multiply-small | ★ 0.138 µs | ★ 0.129 µs | ERROR | ★ 0.132 µs | ★ 0.129 µs |
divide-large | (▼4x) 7.916 µs | ERROR | ERROR | ★ 1.650 µs | 2.025 µs |
divide-small | (▼38x) 496.494 µs | ERROR | ERROR | ERROR | ★ 12.969 µs |
divide-large-and-view | (▼4x) 8.479 µs | ERROR | ERROR | ★ 1.840 µs | 2.128 µs |
divide-small-and-view | (▼35x) 511.241 µs | ERROR | ERROR | ERROR | ★ 14.372 µs |
raw-view | 21.045 µs | 26.058 µs | (▼3x) 61.210 µs | ★ 18.167 µs | ★ 17.337 µs |
raw-view-zeros | (▼5x) 83.212 µs | (▼4x) 73.963 µs | (▼4x) 70.724 µs | ★ 16.521 µs | ★ 15.681 µs |
prepared-view | ★ 16.845 µs | 25.988 µs | (▼3x) 59.159 µs | ★ 17.779 µs | ★ 16.689 µs |
prepared-view-zeros | ★ 1.567 µs | (▼46x) 72.737 µs | (▼44x) 69.415 µs | (▼10x) 16.105 µs | ★ 1.635 µs |
With this test I did not intend to advertise my package at all. It was only important for me to check myself whether I was doing everything right. I saw my mistakes, and I corrected them. That's why this test is not very fair: I didn't adapt the test to my package, but I “adapted” (i.e. optimized) my package to this test. And I did it with the code of the packages I've given here. Even if some of them didn't work for me or contain bugs, there are some very good solutions in them that I was inspired by.
In early January 2025, the column with the decimal looked quite different. The values were two orders of magnitude higher. We can only be happy for such improvements.
Description of benchmarks
add
Adding numbers:
10000000000000000000 + 1000000000000000000 + 100000000000000000 + 10000000000000000 + 1000000000000000 + 100000000000000 + 10000000000000 + 1000000000000 + 100000000000 + 10000000000 + 1000000000 + 100000000 + 10000000 + 1000000 + 100000 + 10000 + 1000 + 100 + 10 + 1 + 0.1 + 0.01 + 0.001 + 0.0001 + 0.00001 + 0.000001 + 0.0000001 + 0.00000001 + 0.000000001 + 0.0000000001 + 0.00000000001 + 0.000000000001 + 0.0000000000001 + 0.00000000000001 + 0.000000000000001 + 0.0000000000000001 + 0.00000000000000001 + 0.000000000000000001 + 0.0000000000000000001 + 0.00000000000000000001 = 11111111111111111111.11111111111111111111
A very simple operation. But note that in the case of decimal, it is much more complicated than multiplication.
multiply-large
Multiplication of large numbers:
123456789 * 123456789 * 123456789 * 123456789 * 123456789 * 123456789 * 123456789 * 123456789 * 123456789 * 123456789 = 822526259147102579504761143661535547764137892295514168093701699676416207799736601
A simple operation for decimal. It is impossible to make a mistake in it. There is no simpler operation.
multiply-small
Multiplication of small numbers:
0.0123456789 * 0.0123456789 * 0.0123456789 * 0.0123456789 * 0.0123456789 * 0.0123456789 * 0.0123456789 * 0.0123456789 * 0.0123456789 * 0.0123456789 = 0.0000000000000000000822526259147102579504761143661535547764137892295514168093701699676416207799736601
A simple operation, but not all packages are ready to handle numbers that have more than 20 decimal places.
divide-large
Division of large numbers:
822526259147102579504761143661535547764137892295514168093701699676416207799736601 / 123456789 / 123456789 / 123456789 / 123456789 / 123456789 / 123456789 / 123456789 / 123456789 / 123456789 / 123456789 = 1
Division is not the strongest point of most packages. Even integers! Even the result of which is also an integer!
divide-small
Division of small numbers:
1 / 256 / 256 / 256 / 256 / 256 / 256 / 256 / 256 / 256 = 0.000000000000000000000211758236813575084767080625169910490512847900390625
It's a difficult task. It's easy to stumble over. decimal
solves it, but at what cost! Some packages use the double
trick and stumble
over it. And some don't even try.
divide-large-and-view and divide-small-and-view
Division of numbers and converting the result in a readable format.
Packages can use intermediate results in their work, which speed up the speed of operations, but do not have a decimal form understandable to the user. (This is what decimal did until version 3.2.0). Therefore, the divide-large and divide-small test, where only division is performed, may be far from real life. This tests perform the same operation as divide-large and divide-small, but additionally convert the result of the operation (only the operation, not each step in this operation) into a readable form. (And in this tests decimal used to lose a lot of performance before).
I'll be honest, it took me a long time to find a solution that satisfied me in terms of performance.
raw-view
Convert newly created numbers into a readable format:
- 123456789012345678901234567890123456789
- 1234567890123456789012345678901234567.89
- 12345678901234567890123456789012345.6789
- 123456789012345678901234567890123.456789
- 1234567890123456789012345678901.23456789
- 12345678901234567890123456789.0123456789
- 123456789012345678901234567.890123456789
- 1234567890123456789012345.67890123456789
- 12345678901234567890123.4567890123456789
- 123456789012345678901.234567890123456789
- 1234567890123456789.01234567890123456789
- 12345678901234567.8901234567890123456789
- 123456789012345.678901234567890123456789
- 1234567890123.45678901234567890123456789
- 12345678901.2345678901234567890123456789
- 123456789.012345678901234567890123456789
- 1234567.89012345678901234567890123456789
- 12345.6789012345678901234567890123456789
- 123.456789012345678901234567890123456789
- 1.23456789012345678901234567890123456789
This is usually a resource-intensive task, as the package does not have time to do any optimizations with the number.
raw-view-zeros
Convert newly created numbers with lots of leading and trailing zeros into a readable format:
- 100000000000000000000000000000000000000
- 10000000000000000000000000000000000
- 1000000000000000000000000000000
- 100000000000000000000000000
- 10000000000000000000000
- 1000000000000000000
- 100000000000000
- 10000000000
- 1000000
- 100
- 0.01
- 0.000001
- 0.0000000001
- 0.00000000000001
- 0.000000000000000001
- 0.0000000000000000000001
- 0.00000000000000000000000001
- 0.000000000000000000000000000001
- 0.0000000000000000000000000000000001
- 0.00000000000000000000000000000000000001
Converting such numbers is technically quite different from converting numbers without zeros in raw-view. Each of the tests (raw-view and raw-vew-zeros) separately can give a wrong idea of performance, so they should be considered only together.
prepared-view
Conversion of prepared numbers (if the package supports it) into a readable format.
Packages may use optimization mechanisms in their work (for example, saving previously calculated values). The raw-view test does not allow you to evaluate the fruits of this optimization. This test gives such an opportunity by performing the same operation as raw-view, but adding optimization. Compare the results of both tests.
Packages decimal_type, fixed, big_decimal do without optimization.
prepared-view-zeros
Conversion of prepared numbers (if the package supports it) with a large number of initial or final zeros into a readable format:
See description of previous tests.
decimal vs yet_another_decimal #
The last thing I want to do is compete with the author of decimal, especially when I see how long this package has been around and how well supported it is. I don't think I have anything overtly new to offer in the usual approach to decimal. Even using different approaches under the hood, the end result will be on the outside, not the inside. And it's pretty much the same feature set with pretty much the same performance.
But actually the decision to write my own
yet_another_decimal was not
only influenced by the poor (at the time) performance of
decimal. There was another reason. For my
task I needed a lightweight decimal, which needed a regular int
instead of
BigInt
to store values under the hood. My values fit even in int32. These are
the results of training: geoposition, distance, altitude gain, pace, heart
rate, cadence, power. As an old generation programmer, it's morally hard for me
to waste resources in places where it's not necessary. Especially I expect
a large amount of data and calculations with them. And I was surprised to find
no ready-made solution on pub.dev.
So, Decimal
was not originally the main purpose of the package. The main goal
was ShortDecimal
. Decimal
was just a natural evolution of the package.
Decimal
vs ShortDecimal
#
ShortDecimal
limitations #
ShortDecimal
has the same functions as Decimal
, but the values are stored
in int
with all the consequences. On the one hand, it is high performance,
but on the other hand it is a possibility of uncontrolled overflow of a value,
which will not happen in case of using BigInt
. You can write code that will
control overflow, but it will make the algorithms much more complicated and
slow. The additing is too simple to be burdened with additional checks. Each
such check will increase the operation's execution time by times.
Therefore, ShortDecimal
should only be used with the possibility of overflow
in mind:
print(ShortDecimal(9223372036854775807) + ShortDecimal.one); // -9223372036854775808
The ShortDecimal
capability bounds are int
bounds. In native platforms and
wasm it is int64, in js environment accuracy is promised only up to int53.
For int64, significant digits (base in package terms), i.e. the value without leading and trailing zeros, must not be out of the range [-9223372036854775808..9223372036854775807].
final a = ShortDecimal(9223372036854775807) >> 40; // ok
final b = ShortDecimal(9223372036854775807) << 23; // ok
print(a); // 0.0000000000000000000009223372036854775807
print(b); // 922337203685477580700000000000000000000000
The number itself 922337203685477580700000000000000000000000 goes well beyond
int
. But its base (without trailing zeros) fits into int64.
But you also have to work with that number in the same scale:
// 922337203685477580700000000000000000000000
// - 100000000000000000000000
// = 922337203685477580600000000000000000000000
print(b - (ShortDecimal(1) << 23)); // 922337203685477580600000000000000000000000 <- ok
// 922337203685477580700000000000000000000000
// - 1
// = 922337203685477580699999999999999999999999
print(b - ShortDecimal(1)); // -200376420520689665 <- overflow
Such constraints impose on ShortDecimal
the need to constantly optimize the
value resulting from operations on it, in order to keep the ability to stay
within the int
boundaries longer. Decimal
does not need such optimization.
For example, multiplying two numbers: 1.2 * 5. Under the hood, everything is
stored in an integer variable (base
) and a parameter indicating where the
decimal point is located (usually called scale
). 1.2 would be stored as
(base: 12, scale: 1) and 5 as (base: 5, scale: 0).
final a = Decimal.parse('1.2');
final b = Decimal.parse('5');
print(a.debugToString()); // Decimal(base: 12, scale: 1)
print(b.debugToString()); // Decimal(base: 5, scale: 0)
final c = ShortDecimal.parse('1.2');
final d = ShortDecimal.parse('5');
print(c.debugToString()); // ShortDecimal(base: 12, scale: 1)
print(d.debugToString()); // ShortDecimal(base: 5, scale: 0)
Multiplication of such numbers is quite a simple operation: the bases are
multiplied and the scales are added. The result will be: (base: 60, scale: 1).
This is 6. And Decimal
doesn't need to reduce it to (base: 6, scale: 0). But
for ShortDecimal
it is vital.
final r1 = a * b;
print(r1); // 6
print(r1.debugToString()); // Decimal(base: 60, scale: 1)
final r2 = c * d;
print(r2); // 6
print(r2.debugToString()); // ShortDecimal(base: 6, scale: 0)
Decimal
, of course, could after each operation bring the value to normal,
i.e. to (base: 6, scale: 0), but this is additional time, which in most cases
is unnecessary. And where BigInt
is used, there is no practical need for
this: there is not too much difference between (base: 6, scale: 0) and
(base: 60000000000, scale: 10). But in the case of int
we can reach overflow
very quickly. For example, it is enough to multiply 1.0 by 1.0, i.e.
(base: 10, scale: 1) by (base: 10, scale: 1), only 18 times to go beyond the
int
boundary. Even though it's only 1!
var a = Decimal.parse('1.0');
for (var i = 0; i < 18; i++) {
a *= Decimal.parse('1.0');
}
print(a.debugToString()); // Decimal(base: 10000000000000000000, scale: 19)
print(a); // 1
final i = 10000000000000000000; // The integer literal 10000000000000000000 can't be represented in 64 bits.
That's why you should pack the value after each operation to stay within int
boundaries longer. But you should not worry about performance. In the case of
int
it will be much faster than BigInt
without packing.
var a = ShortDecimal.parse('1.0');
for (var i = 0; i < 18; i++) {
a *= ShortDecimal.parse('1.0');
}
print(a.debugToString()); // ShortDecimal(base: 1, scale: 0)
print(a); // 1
Performance #
decimal | yet_another_decimal Decimal | yet_another_decimal ShortDecimal | |
---|---|---|---|
add | (▼4x) 0.713 µs | (▼4x) 0.646 µs | ★ 0.155 µs |
multiply-large | (▼4x) 0.121 µs | (▼3x) 0.115 µs | ★ 0.030 µs |
multiply-small | (▼4x) 0.120 µs | (▼3x) 0.114 µs | ★ 0.030 µs |
divide-large | (▼103x) 6.455 µs | (▼26x) 1.683 µs | ★ 0.063 µs |
divide-small | (▼1196x) 114.617 µs | (▼71x) 6.834 µs | ★ 0.096 µs |
divide-large-and-view | (▼103x) 6.664 µs | (▼26x) 1.686 µs | ★ 0.065 µs |
divide-small-and-view | (▼339x) 116.035 µs | (▼21x) 7.376 µs | ★ 0.342 µs |
raw-view | (▼4x) 10.474 µs | (▼3x) 6.882 µs | ★ 2.137 µs |
raw-view-zeros | (▼30x) 37.261 µs | (▼5x) 6.966 µs | ★ 1.232 µs |
prepared-view | (▼3x) 6.354 µs | (▼3x) 6.640 µs | ★ 2.082 µs |
prepared-view-zeros | ★ 1.274 µs | 1.339 µs | ★ 1.186 µs |
For a description of the tests, see Package performance.
The Decimal
and ShortDecimal
use the same algorithms. The difference in
performance is the difference between BigInt
and int
.
Chances are, you will rarely use the division operation in your application.
You probably won't have a large number of decimal calculations either. In this
case, the difference 3-5x can be neglected. The use of Decimal
in both
packages will most likely not lead to significant performance losses in the
whole application. That's why you can choose Decimal
in
decimal as well as Decimal
in
yet_another_decimal.
But if you need both performance and maximum memory saving, choose
ShortDecimal
, but do not forget about its limitations.
Decimal
optimization #
When it is necessary to return to the user the properties of a number, understandable to human perception, for example, the number of significant digits after the decimal point, it is impossible to do without packing the number. And having a packed number, you can do some operations, for example, converting a number into a string, much faster. This packing of a number is what optimization consists in, due to which some tests are executed much faster and some, on the contrary, much slower, when optimization costs do not pay off in time.
In Decimal
I tried to find a balance: pack a number only where it is needed,
and use it only if it is there, and do without it if it is not. But I added
an optimize
method that will allow you to manually pack a number to optimize
performance when the algorithm doesn't do it itself. This method can be called
safely many times. In reality, it will only pack the value once.
The optimize
method is clearly refers to internal implementation, not
business logic. And I don't really like its presence, but I haven't found
a better solution, since I couldn't decide for the user in which case it's
better to use number packing and in which case it's better to avoid it. All I
could do to keep the implementation from sticking out so obviously was to call
the method optimize
rather than pack
or rescale
. The user doesn't need to
know about packing and scaling a value, nor does the user need to know about
base
and scale
at all.
In the following example, the optimization significantly speeds up the conversion of a number to a string:
final v = Decimal(1000000000000000000) >> 18; // = 1
final sw = Stopwatch()..start();
for (var i = 0; i < 10000000; i++) {
v.toString(); // "1"
}
sw.stop();
print(sw.elapsed); // 0:00:02.445964
v.optimize();
sw
..reset()
..start();
for (var i = 0; i < 10000000; i++) {
v.toString(); // "1"
}
sw.stop();
print(sw.elapsed); // 0:00:00.048106
But if we get new numbers each time, and we do optimization along with each
conversion to a string, we will lose performance. Whereas the absence of
unjustified optimization would save resources. That's why I didn't make it
mandatory inside toString
.
var v = Decimal(1000000000000000000) >> 18; // = 1
final sw = Stopwatch()..start();
for (var i = 0; i < 10000000; i++) {
// Simulate the situation when new numbers arrive.
v = -v;
v.toString(); // "1" or "-1"
}
sw.stop();
print(sw.elapsed); // 0:00:02.568405
sw
..reset()
..start();
for (var i = 0; i < 10000000; i++) {
v = -v; // "1" or "-1"
// If we only need to output numbers once and will not use them anywhere
// else, this optimization is unnecessary. Optimization will still be
// optimization, but we will pay too much for it.
v.optimize();
v.toString(); // 0:00:15.578125
}
sw.stop();
print(sw.elapsed);
ShortDecimal
does not need to optimize since it optimizes the value in each
operation.