$2.275 is $2.27 for DART Round, toStringAsFixed, etc.

2,385 views
Skip to first unread message

Moises Belchin

unread,
Jul 19, 2013, 5:04:32 AM7/19/13
to General Dart Discussion
Hi all,

Working with numbers I found this:

num numero = 2.275;
print('$numero :: :: ${numero.toStringAsFixed(2)}');

In other programming languages when you use round (I'm talking in money terms) to 2 digits: 2.275 is converted to 2.28 but when I use toStringAsFixed(2) in Dart it returns 2.27 instead of 2.28.

How can I round to 2 digits? Am I the only who noticed that? Am I missing something?

Regards and thanks for hearing.

Saludos.
Moisés Belchín.

jim.trainor.kanata

unread,
Jul 19, 2013, 7:18:59 AM7/19/13
to mi...@dartlang.org
In Javascript it is converted to a call to "toFixed(2)"

Javascript produces the same result - both the dart2js output, and manually coded.  So I expect that dart is simply matching javascript behavior in this case.

The documentation on the toStringAsFixed method doesn't actually say that it does any rounding.  Just that it returns "fractionDigits" after the decimal.

Florian Loitsch

unread,
Jul 19, 2013, 7:41:31 AM7/19/13
to General Dart Discussion
On Fri, Jul 19, 2013 at 1:18 PM, jim.trainor.kanata <jim.train...@gmail.com> wrote:
In Javascript it is converted to a call to "toFixed(2)"

Javascript produces the same result - both the dart2js output, and manually coded.  So I expect that dart is simply matching javascript behavior in this case.

The documentation on the toStringAsFixed method doesn't actually say that it does any rounding.  Just that it returns "fractionDigits" after the decimal.
It does round, but it rounds the actual value.
Doubles are rationals that are (non-evenly) distributed through the range -10^308 to 10^308. Given that they only have 64bits this means that there are gaps between adjacent doubles (bigger gaps for bigger numbers, smaller gaps for smaller numbers). This also means that many numbers, like 0.1(or 2.275), don't have a precise double equivalent. Instead, you get the closest double. It turns out that for 0.1 the closest double has the value ~0.10000000000000000555. For 2.275 the closest double is ~2.2749999999999999112. You usually don't see these long numbers because the toString() method finds the shortest decimal representation that is still the closest (which by construction is 0.1, resp. 2.275).
When you round (or truncate to 2 digits after the decimal point) the rounding algorithm uses the precise number and not the shortest decimal number. This means that it will round 2.2749999999999999112 and not 2.275. This is the reason that you get "2.27"
Note that this is not the only problem you encounter when dealing with doubles. Much more common are issues like 0.1 + 0.2 != 0.3. Try it: `print(0.1 + 0.2);`.

I recommend reading "What Every Computer Scientist Should Know About Floating-Point Arithmetic" if you want to know more.

If you are working with money I strongly recommend using integers instead (just premultiply any incoming value). For example, instead of dealing with Euros: 3.2Eur you could deal with cents: 320. If that's not enough: 3200 tenth-of-a-cent. Ideally you should wrap this away into a Currency (or similar class). There are also packages (like http://pub.dartlang.org/packages/decimal) but I can't comment on them (never used them).


Moises Belchin wrote:

num numero = 2.275;
print('$numero :: :: ${numero.toStringAsFixed(2)}');

--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
 
 



--
Give a man a fire and he's warm for the whole day,
but set fire to him and he's warm for the rest of his life. - Terry Pratchett

Moises Belchin

unread,
Jul 19, 2013, 8:05:55 AM7/19/13
to General Dart Discussion
Firts of all thanks all for the great explanation. I would like to make a suggestion:

Please make programmer life as easier as possible. It would be easier use .toRound(numberDigits)

Regards.


Saludos.
Moisés Belchín.


2013/7/19 Florian Loitsch <floi...@google.com>

Jim Trainor

unread,
Jul 19, 2013, 4:06:13 PM7/19/13
to mi...@dartlang.org
I'm puzzled by something about this.  If run "jsc" and experiment I see the following:

$ jsc
> numero=2.275
2.275
> print (numero);
2.275

The same happens in chrome's dev console:

numero=2.275
2.275
console
.log(numero)
2.275


Why does Javascript not print out the number using all significant digits? I expect to see the full precision of the floating point number and see the erro in its approximation of 2.275.

By way of contrast, the error in the floating point point representation is clear using the common 0.1 + 0.2 example:

0.1+0.2
0.30000000000000004

The results are identical if one plays with Python (a good thing....).


Florian Loitsch

unread,
Jul 19, 2013, 5:04:48 PM7/19/13
to General Dart Discussion
On Fri, Jul 19, 2013 at 10:06 PM, Jim Trainor <jim.train...@gmail.com> wrote:
I'm puzzled by something about this.  If run "jsc" and experiment I see the following:

$ jsc
> numero=2.275
2.275
> print (numero);
2.275

The same happens in chrome's dev console:

numero=2.275
2.275
console
.log(numero)
2.275


Why does Javascript not print out the number using all significant digits? I expect to see the full precision of the floating point number and see the erro in its approximation of 2.275.
See the dartdoc for `num.toString`:

Specifically: "Computes the shortest string of digits that correctly represent the input number."
It does not generate the most precise string representation, but the shortest.
Otherwise you would almost always end up with 17 digits (at least for small numbers).

JavaScript has a similar rule.


By way of contrast, the error in the floating point point representation is clear using the common 0.1 + 0.2 example:

0.1+0.2
0.30000000000000004

The results are identical if one plays with Python (a good thing....).
That's IEEE. 


--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
 
 

jim.trainor.kanata

unread,
Jul 19, 2013, 5:08:57 PM7/19/13
to mi...@dartlang.org
okay, I see, makes sense for output to a console.  Thanks.


Florian Loitsch wrote:

On Fri, Jul 19, 2013 at 10:06 PM, Jim Trainor

Lasse R.H. Nielsen

unread,
Jul 21, 2013, 5:14:57 PM7/21/13
to mi...@dartlang.org
On Fri, Jul 19, 2013 at 11:08 PM, jim.trainor.kanata <jim.train...@gmail.com> wrote:
okay, I see, makes sense for output to a console.  Thanks.

Not just for the console. It makes a fundamental kind of sense for doubles :)

The problem with doubles are that they are not exact numbers, and weren't intended to be. 

It's safer to consider a double as representing a range of real numbers, like a measurement with an uncertainty.
We don't know exactly which number it is, but it's somewhere in this range. For doubles, the range is those real numbers that are closer to that double than to any other double (and you need a tie-breaker rule for the point right between two doubles).
That's also why you have both +0.0 and -0.0 - they represent different ranges around zero, one positive and one negative (with zero itself being in both ranges).

So when we ask the system to get a finite string representation of a double, it picks the real number in the range represented by the double which has the shortest representation (just to save bits), and when parsing it back, it will find the double with a range that contains that real number.

/L



--
Lasse R.H. Nielsen - l...@google.com  
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K - Denmark - CVR nr. 28 86 69 84

jim.trainor.kanata

unread,
Jul 22, 2013, 5:53:00 AM7/22/13
to mi...@dartlang.org
Thanks.  I think understand the issues surrounding doubles. Although I have much better understand of what Dart (and JS) are doing with them at string conversion time with this explanation.  I'm a long time user, and writer, of rational number classes in media applications to avoid using doubles to represent non integer sample rates. I've seen all the chaos that ensues (impossible to resolve off-by-one errors) when people start using doubles and doing comparisons, etc, expecting exact results.  (Actually, in such applications dart's integer implementation will server quite well because it is common to cross multiply large rational numbers in those applications and that creates intermediate results that overflow 64 bit integers.)

I was thrown off when I entered "2.275" and didn't see all the bits of the floating point representation split back on the jsc console output.  It didn't dawn on my that jsc, or dart, would be doing what they are doing rather than just dumping all the bits of the representation to the output.

21 July, 2013 5:14 PM
19 July, 2013 5:08 PM
okay, I see, makes sense for output to a console.  Thanks.


Florian Loitsch wrote:
19 July, 2013 5:04 PM
19 July, 2013 4:06 PM
I'm puzzled by something about this.  If run "jsc" and experiment I see the following:

$ jsc
> numero=2.275
2.275
> print (numero);
2.275

The same happens in chrome's dev console:

numero=2.275
2.275
console
.log(numero)
2.275


Why does Javascript not print out the number using all significant digits? I expect to see the full precision of the floating point number and see the erro in its approximation of 2.275.

By way of contrast, the error in the floating point point representation is clear using the common 0.1 + 0.2 example:

0.1+0.2
0.30000000000000004

The results are identical if one plays with Python (a good thing....).


--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
 
 
19 July, 2013 7:41 AM
On Fri, Jul 19, 2013 at 1:18 PM, jim.trainor.kanata <jim.train...@gmail.com> wrote:
In Javascript it is converted to a call to "toFixed(2)"

Javascript produces the same result - both the dart2js output, and manually coded.  So I expect that dart is simply matching javascript behavior in this case.

The documentation on the toStringAsFixed method doesn't actually say that it does any rounding.  Just that it returns "fractionDigits" after the decimal.
It does round, but it rounds the actual value.
Doubles are rationals that are (non-evenly) distributed through the range -10^308 to 10^308. Given that they only have 64bits this means that there are gaps between adjacent doubles (bigger gaps for bigger numbers, smaller gaps for smaller numbers). This also means that many numbers, like 0.1(or 2.275), don't have a precise double equivalent. Instead, you get the closest double. It turns out that for 0.1 the closest double has the value ~0.10000000000000000555. For 2.275 the closest double is ~2.2749999999999999112. You usually don't see these long numbers because the toString() method finds the shortest decimal representation that is still the closest (which by construction is 0.1, resp. 2.275).
When you round (or truncate to 2 digits after the decimal point) the rounding algorithm uses the precise number and not the shortest decimal number. This means that it will round 2.2749999999999999112 and not 2.275. This is the reason that you get "2.27"
Note that this is not the only problem you encounter when dealing with doubles. Much more common are issues like 0.1 + 0.2 != 0.3. Try it: `print(0.1 + 0.2);`.

I recommend reading "What Every Computer Scientist Should Know About Floating-Point Arithmetic" if you want to know more.

If you are working with money I strongly recommend using integers instead (just premultiply any incoming value). For example, instead of dealing with Euros: 3.2Eur you could deal with cents: 320. If that's not enough: 3200 tenth-of-a-cent. Ideally you should wrap this away into a Currency (or similar class). There are also packages (like http://pub.dartlang.org/packages/decimal) but I can't comment on them (never used them).


Moises Belchin wrote:

num numero = 2.275;
print('$numero :: :: ${numero.toStringAsFixed(2)}');
--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
 
 



--
Give a man a fire and he's warm for the whole day,
but set fire to him and he's warm for the rest of his life. - Terry Pratchett
Reply all
Reply to author
Forward
0 new messages