Sitemap

Why precision matters — Decimals in Dart/Flutter

6 min readJul 6, 2025

--

Press enter or click to view image in full size

As a fellow app developer, you know that writing software can be hard — bugs keep popping up, or a third-party API just isn’t doing what you want. But at least simple mathematical operations work, right?

Not always. Did you know that adding two numbers together in Dart (and most other languages) might not give you the result you expected?

Give it a try yourself. Here is the most famous and simple example:

print(0.1 + 0.2);

This should print “0.3” — but actually, we get “0.30000000000000004”.

By the end of this article, you’ll understand why decimal calculations can be tricky in Dart and how to handle them correctly to avoid bugs in your Flutter apps.

Let’s get back to the example from above. Not a big deal, you might think — let’s just round to make the value look fine. Anyway, we should use a NumberFormat from the intl package to properly localize numbers. So, to display some monetary amount, we might use:

final numberFormat = NumberFormat("#0.00", "en_US");
print(numberFormat.format(0.1 + 0.2));

And it gives us the correct output of “0.30”.

But besides just “displaying” issues, we also might run into actual problems in your business logic. Here is a snippet emulating a shopping basket. There is a 50 Euro minimum order value. In the basket, we have a pair of jeans, a t-shirt, and socks. These should add up to exactly 50 Euro.

final jeans = 24.99;
final tShirt = 15.50;
final socks = 9.51;

final total = jeans + tShirt + socks;

if (total < 50) {
print('Add more items to reach 50 Euro minimum order value');
}

print(total); // should output 50, does 49.99999999999999

Again, the standard floating point numbers don’t work properly. Running this will print the message about adding more items to the basket.

Hopefully, this convinces everyone that standard Dart types aren’t suitable when precise calculations are needed — especially in cases like this one involving monetary values.

Floating Points Numbers

Above, I mentioned floating point numbers. But what are they?

Floating point numbers are how computers store decimal values like 3.14 or 0.001. Unlike integers, which only store whole numbers, floating point numbers use a special format that lets the “decimal point” move or “float” to different positions. Think of it like scientific notation — instead of writing 12,345,000 you can write 1.2345 × 1⁰⁷. Computers do something similar but in binary, allowing to store really large (or small) numbers without needing too much memory.

In Dart and Flutter, our standard double is exactly such a data type.

If this got you hooked, look at how numbers in Dart are represented and read the IEEE 754 standard that makes a double work.

Why Floating Point Arithmetic Fails for Decimals

Before we look into solving this issue, let’s discuss why this is happening.

Computers work in binary, so all numbers we input into our program must be converted from the decimal system we use in our day-to-day life to binary. And this reveals very specific implications.

Like in decimal, binary can represent some numbers easily while others are hard to represent. The fraction 1/3 in decimal is a perfect example. Expressing it as a fraction is easy, but in decimal representation, it always loses some precision. There is an infinite number of 3s behind the zero (0.33333333333…).

In binary, the same thing is happening, but with completely different numbers. You might have guessed it — decimal 0.1 is very hard to represent in binary and results in an infinite length number (0.0001100110011…).

And this is the root cause of some numbers not matching our expectations, as a computer is converting back and forth between binary and decimal.

You, of course, can mitigate the issue by storing numbers with a high amount of decimal places. But we still don’t have infinite memory and processing power. And that’s why “standard” data types like double will produce these “errors”.

How to use Decimal data types in Dart

So how do we overcome these issues? Meet BigDecimal, a library supporting arbitrary precision on numbers.

Let’s start by recreating the example from above:

final jeans = BigDecimal.parse('24.99');
final tShirt = BigDecimal.parse('15.50');
final socks = BigDecimal.parse('9.51');

final total = jeans + tShirt + socks;

if (total < BigDecimal.parse('50')) {
print('Add more items to reach 50 Euro minimum order value');
}

print(total); // outputs 50.00

First of all, this outputs 50.00 properly and won’t warn us about too few items in the basket. Next, you might notice that decimals are constructed with strings. This makes sure we don’t have precision issues when creating the objects. It also opens up an easy way to send and receive “correct” numbers over, e.g., a REST API.

Why BigDecimal, I see many packages for decimals in Dart. You are absolutely right. I like BigDecimal as a project because it is stable, highly performant and based on a battle tested Java implementation. But do your own research! A good start is looking at this comparison.

How does BigDecimal work

Internally, a BigDecimal object operates over 2 properties, an intVal and scale.

These two describe the value we store while avoiding any lossy storage. Our desired value will always get stored as an integer, while the scale defines where the decimal point goes. To give you some examples, 9.99 becomes 999 with a scale of 2, while 0.939 becomes 939 with a scale of 3.

Note that the integer is not a normal int, but rather BigInt, a datatype that, just like BigDecimal allows storing arbitrarily large numbers.

On top of these properties comes logic to perform basic operations like addition or multiplication. This allows us to use BigDecimals “just” like doubles in your business logic. As these are implemented to work with scale and intVal, no problems arise due to the “binary representation” of decimals.

Should I now always use BigDecimal instead of double? Surely not. For most logic and operations we perform in our apps, using doubles is plenty good. Decimal types have a huge performance penalty compared to standard floating point types. Use them only when dealing with monetary, financial, or other operations where correctness is an issue.

Lastly, we need to talk about rounding. Even though BigDecimal can work with arbitrary precision, you do not want to store infinitely long numbers — 1/3 still results in an infinitely long decimal number. This means when performing certain operations, like division, we must provide rounding and scaling information:

final one = BigDecimal.one;
final three = BigDecimal.parse('3');

final oneThird = one.divide(
three,
roundingMode: RoundingMode.HALF_UP,
scale: 10,
)

Rounding mode should be self-explanatory, while scale defines how precise we want to be. Setting scale to 2 gives us 0.33, while a scale at 10 gives us 0.3333333333.

Bonus: Formatting Decimals

Now that you know how to properly use decimal data types in your business logic, you might want to show them to the user. To properly localize the numbers, you are most likely already using the intl package. But, out of the box, there is no support for decimal types. For this, I wrote a little package that bridges the gap between intl and big_decimal. It is called big_decimal_intl a and allows using any number format of intl to format or parse decimals.

Here is a little example:

final format = NumberFormat.currency(
locale: 'de_DE',
name: 'EUR',
);
final formatter = BigDecimalFormatter(format);

// next line prints: 100.000,00 EUR
print(formatter.format(BigDecimal.parse('100000')));

// next line prints: 100000.00
print(formatter.parse('100.000'));

To format decimal numbers, just create any `NumberFormat` object depending on your needs. Afterwards, create a BigDecimalFormatter. It acts as a wrapper around the number format object, enabling formatting and parsing of `BigDecimal` objects.

And this wraps up our introduction to decimal numbers in Dart and Flutter. By understanding how number types work and where precision matters, you’ll be better equipped to handle calculations smoothly — whether it’s for finances, measurements, or any situation where accuracy counts.

Thanks for reading!

--

--