Unexpected Behaviour of the Rounding Function

I am trying to round some Float64 values and don’t understand the behavior of the round() function for specific values.

If I take:

round(0.585, digits=2, RoundNearestTiesUp)

I get 0.59, as expected, since I want values to be rounded up when ending in a 5.

However, for:

round(0.575, digits=2, RoundNearestTiesUp)

I get 0.57, which I don’t understand. This goes for 0.565 as well for some reason.

My question is why does this happen, particularly when I get the expected behavior for 0.505,0.515…0.595, except for 0.565 and 0.575?

I assume this has to do with the RoundingMode, but does anyone have any suggestions as to how I can consistently get the appropriate rounding of Float64s that I’m looking for; a work around perhaps?

PS:

I’m using Julia version 1.7.1

Floating point numbers aren’t decimal.

julia> big(0.575)
0.5749999999999999555910790149937383830547332763671875
3 Likes

I think that’s because the decimal value 0.575 is not exactly represented in binary floating-point arithmetic. It’s actually the value:

julia> big(0.575)
0.5749999999999999555910790149937383830547332763671875

which is < 0.575:

julia> 0.575 < 575//1000
true

so it correctly rounds down to 0.57. See PSA: floating-point arithmetic

The surprising thing to me is that 0.585 rounds up, since we also have:

julia> big(0.585)
0.58499999999999996447286321199499070644378662109375

julia> 0.585 < 585//1000
true
2 Likes

I think what’s happening here is that .58 and .59 are both slightly less than their decimal values as well (but I’m not 100% sure here)

Note that if you want to work with decimal values exactly you can use decimal floating point:

julia> using DecFP

julia> round(d"0.575", digits=2, RoundNearestTiesUp)
0.58

julia> round(d"0.585", digits=2, RoundNearestTiesUp)
0.59

It’s rare to actually need this in a real application, but it does make human interpretation of decimal inputs and outputs easier.

2 Likes

Float64(0.585) is exactly halfway between Float64(0.58) and Float64(0.59). However, the same is true for Float64(0.575), which is exactly halfway between Float64(0.57) and Float64(0.58):

julia> big(0.59) - big(0.585)
0.00500000000000000444089209850062616169452667236328125

julia> big(0.585) - big(0.58)
0.00500000000000000444089209850062616169452667236328125

julia> big(0.58) - big(0.575)
0.00500000000000000444089209850062616169452667236328125

julia> big(0.575) - big(0.57)
0.00500000000000000444089209850062616169452667236328125

So, by this logic, shouldn’t 0.575 also round up to 0.58?

Probably there is an additional roundoff error in the round code…

Thank you for your recommendation, I’ll check out the DecFP.jl package.

Well, strangely, it might not be as rare in real application.

In my case, I was trying to verify a min-max normalization on a fairly large data-set of around 42000 entries. I used DataFrames’ transform function to apply the calculation and rounding of the result across each value in the DataFrame.

And since, I already have a normalized version of the data (I was just checking them), I found some inaccuracies in comparing my results and to those of the pre-normalized data.

With that said, unexpected rounding was on average extremely infrequent. Nevertheless, I could see decimal floating point having a place in data manipulations that require a great deal of accuracy.