Ruby Float#Round Method Behaves Incorrectly with Round(2)

Ruby Float#round method behaves incorrectly with round(2)

AFAICS the ruby round() works correctly. Presumably it's just a wrapper around the round() function in libm anyway.

So the reason is that your floating point literals cannot be represented exactly in binary. E.g. "1.015" printed with a few more decimals gives "1.0149999999999999"; thus when rounding to two decimal digits, 1.01 is closer to the true value than 1.02. And so on for your other examples as well.

Also keep in mind that the default IEEE 754 rounding mode is "Round to nearest, ties to even" which is not the same as "Round to nearest, ties away from zero" which is what you may be familiar with from school.

Stata: Round not rounding correctly?

Stata uses double or float representations of numbers (i.e. base 2 not base 10). Unfortunately for you, 1.015 is not representable in base 2, so it goes to the nearest value which in this case is slightly less than 1.015, so the round goes down. This is a common issue when dealing with floating point precision on computers - see for example this question in Ruby: Ruby Float#round method behaves incorrectly with round(2)

So how do you fix it?

If 3 decimal places of precision are important to you, you need to use fixed point precision. I don't believe Stata supports this natively, but it's easy enough to do it yourself:

display round(1015, 10)*0.001

i.e. your input numbers get multiplied by 10^3, and your displayed numbers get multiplied by 10^-3. All the processing is therefore carried out with integer precision at the number of decimal places you're interested in.

Note, however, that the seemingly equivalent display round(1.015*1000, 0.01*1000)*0.001 would not work, because the base-2 rounding will have already occurred when reading "1.015" (i.e. the computer would see 1.01499999...*1000 = 1014.999...). Of course, this also applies if you are getting the value 1.015 from elsewhere, such as user input or reading a file.


As an aside, Stata seems very quiet on its rounding mode (also known as tie-breaking). While it's likely that it uses ties-up, it could also be ties-to-even or other options - it's best not to rely on precise half-way behaviour unless you can find an explicit spec for it.

Python 3.x rounding behavior

Python 3's way (called "round half to even" or "banker's rounding") is considered the standard rounding method these days, though some language implementations aren't on the bus yet.

The simple "always round 0.5 up" technique results in a slight bias toward the higher number. With large numbers of calculations, this can be significant. The Python 3.0 approach eliminates this issue.

There is more than one method of rounding in common use. IEEE 754, the international standard for floating-point math, defines five different rounding methods (the one used by Python 3.0 is the default). And there are others.

This behavior is not as widely known as it ought to be. AppleScript was, if I remember correctly, an early adopter of this rounding method. The round command in AppleScript offers several options, but round-toward-even is the default as it is in IEEE 754. Apparently the engineer who implemented the round command got so fed up with all the requests to "make it work like I learned in school" that he implemented just that: round 2.5 rounding as taught in school is a valid AppleScript command. :-)

round() doesn't seem to be rounding properly

I can't help the way it's stored, but at least formatting works correctly:

'%.1f' % round(n, 1) # Gives you '5.6'

Why does Ruby incorrectly parse strings with commas as floats?

Ruby simply call strtod C function https://github.com/ruby/ruby/blob/38caab29bc759be2694013fc3930116e64fcc1d4/object.c#L3278

d = strtod(p, &end);

and strtod function has something like this:

/*
* Count the number of digits in the mantissa (including the decimal
* point), and also locate the decimal point.
*/

decPt = -1;
for (mantSize = 0; ; mantSize += 1)
{
c = *p;
if (!isdigit(c)) {
if ((c != '.') || (decPt >= 0)) {
break;
}
decPt = mantSize;
}
p += 1;
}

https://opensource.apple.com/source/tcl/tcl-10/tcl/compat/strtod.c

according to if ((c != '.') || (decPt >= 0)) {
break;

strtod stop if find any not dot symbol, for example:

irb(main):002:0> "2;58".to_f
=> 2.0
irb(main):003:0> "2@58".to_f
=> 2.0
irb(main):004:0>

UPD: this approach is valid for mri 2.6 ruby implementation. In other versions/implementations it's may be different.

What is the best method of handling currency/money?

You'll probably want to use a DECIMAL type in your database. In your migration, do something like this:

# precision is the total number of digits
# scale is the number of digits to the right of the decimal point
add_column :items, :price, :decimal, :precision => 8, :scale => 2

In Rails, the :decimal type is returned as BigDecimal, which is great for price calculation.

If you insist on using integers, you will have to manually convert to and from BigDecimals everywhere, which will probably just become a pain.

As pointed out by mcl, to print the price, use:

number_to_currency(price, :unit => "€")
#=> €1,234.01

Why not use Double or Float to represent currency?

Because floats and doubles cannot accurately represent the base 10 multiples that we use for money. This issue isn't just for Java, it's for any programming language that uses base 2 floating-point types.

In base 10, you can write 10.25 as 1025 * 10-2 (an integer times a power of 10). IEEE-754 floating-point numbers are different, but a very simple way to think about them is to multiply by a power of two instead. For instance, you could be looking at 164 * 2-4 (an integer times a power of two), which is also equal to 10.25. That's not how the numbers are represented in memory, but the math implications are the same.

Even in base 10, this notation cannot accurately represent most simple fractions. For instance, you can't represent 1/3: the decimal representation is repeating (0.3333...), so there is no finite integer that you can multiply by a power of 10 to get 1/3. You could settle on a long sequence of 3's and a small exponent, like 333333333 * 10-10, but it is not accurate: if you multiply that by 3, you won't get 1.

However, for the purpose of counting money, at least for countries whose money is valued within an order of magnitude of the US dollar, usually all you need is to be able to store multiples of 10-2, so it doesn't really matter that 1/3 can't be represented.

The problem with floats and doubles is that the vast majority of money-like numbers don't have an exact representation as an integer times a power of 2. In fact, the only multiples of 0.01 between 0 and 1 (which are significant when dealing with money because they're integer cents) that can be represented exactly as an IEEE-754 binary floating-point number are 0, 0.25, 0.5, 0.75 and 1. All the others are off by a small amount. As an analogy to the 0.333333 example, if you take the floating-point value for 0.01 and you multiply it by 10, you won't get 0.1. Instead you will get something like 0.099999999786...

Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, errors will compound and you'll end up with values that are visibly not accurate. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required.

A solution that works in just about any language is to use integers instead, and count cents. For instance, 1025 would be $10.25. Several languages also have built-in types to deal with money. Among others, Java has the BigDecimal class, and Rust has the rust_decimal crate, and C# has the decimal type.

Python 3.x rounding behavior

Python 3's way (called "round half to even" or "banker's rounding") is considered the standard rounding method these days, though some language implementations aren't on the bus yet.

The simple "always round 0.5 up" technique results in a slight bias toward the higher number. With large numbers of calculations, this can be significant. The Python 3.0 approach eliminates this issue.

There is more than one method of rounding in common use. IEEE 754, the international standard for floating-point math, defines five different rounding methods (the one used by Python 3.0 is the default). And there are others.

This behavior is not as widely known as it ought to be. AppleScript was, if I remember correctly, an early adopter of this rounding method. The round command in AppleScript offers several options, but round-toward-even is the default as it is in IEEE 754. Apparently the engineer who implemented the round command got so fed up with all the requests to "make it work like I learned in school" that he implemented just that: round 2.5 rounding as taught in school is a valid AppleScript command. :-)

How can I force Ruby numbers to behave like integers, not fixnums?

Yes, the easiest way would be to call to_f instead of to_i when assigning a value to interim_result.



Related Topics



Leave a reply



Submit