Why are my BigDecimal objects initialized with unexpected rounding errors?
Asked Answered
F

1

23

In Ruby 2.2.0, why does:

BigDecimal.new(34.13985572755337, 9)

equal 34.0 but

BigDecimal.new(34.13985572755338, 9)

equal 34.1398557?

Note that I am running this on a 64 bit machine.

Freudian answered 3/2, 2015 at 9:35 Comment(14)
BigDecimal.new(34.13985572755337, 9).to_f returns 34.1398557 on my system ...Fraze
I get the same result for both .. what ruby are you using?Astronaut
I am experiencing the same issue. I will guess it depends on the architecture, my system is 32 bit so I think there is where I get the rounding error. Interestingly, BigDecimal.new('34.13985572755337', 9) when i do this it works great.Plausive
@hakcho well, strings don't have rounding issues ;-)Fraze
Ruby 2.2.0 on a 64-bit architectureFreudian
@Fraze I get it. I just thought the implementation with the strings just cast the type to some internal type that is the same for Float etc. Obviously, not the case.Plausive
What's the Stack Overflow etiquette for updating a question? It's essentially the same question, but I think BigDecimal.new('34.13985572755337', 9) is a better 2nd example.Freudian
I have the same issue on Ruby 2.1.3, 64-bit archPhraseograph
Interesting, I get 34.0 on Ruby 2.1.x and 2.2.x and 34.1398557 on Ruby 2.0.x and 1.9.xFraze
This issue is relative to BigDecimal initialize method when using FLOAT or RATIONAL. This behavior starts with number 32 ( 31 and before are corrects) BigDecimal.new('32.13985572755337', 9) if you pass a string this issue doesn't appear BigDecimal.new("34.13985572755337"). I think you can open an issue about this on bugs.ruby-lang.orgHug
Plus 10 for both the question and for SO. Andrew, I hope you will obey @YannVERY's marching orders.Donalt
@YannVERY is right, the BigDecimal constructor C code is a twisted mess of buggy sick that converts Floats to Rationals and then restarts the construction with the Rational.Dieselelectric
@muistooshort That is likely true, but irrelevant. If it's a bug, it should be reported as such to the appropriate upstream bug tracker. Questions about why something is implemented in a certain way in a language or on a platform are generally opinion-generating questions, and should be closed as such; this would not help the OP. Discussing whether or not it's a bug would also be off-topic; again, this would not help the OP. Your comment is just a rant about the code base, not a practical or canonical answer to an answerable question. Please try to be more constructive in the future.Shoring
Did you check that the constructor is receiving the floats (in full) that you are trying to give it. Float a = 34.13985572755337 BigDecimal.new( a , 9) Float b = 34.13985572755338 BigDecimal.new( b , 9)Bacchanalia
S
14

Initialize with Strings Instead of Floats

In general, you can't get reliable behavior with Floats. You're making the mistake of initializing your BigDecimals with Float values instead of String values, which introduces some imprecision right at the beginning. For example, on my 64-bit system:

float1 = 34.13985572755337
float2 = 34.13985572755338

# You can use string literals here, too, if your Float can't be properly
# represented. For example:
#
#    BigDecimal.new("34.13985572755337", 9)
#
# would be safer, but Float#to_s works fine with the provided corpus.
bd1 = BigDecimal.new(float1.to_s, 9)
bd2 = BigDecimal.new(float2.to_s, 9)

bd1.to_s
#=> "0.3413985572755337E2"
bd2.to_s
#=> "0.3413985572755338E2"

bd1.to_f == float1
#=> true
bd2.to_f == float2
#=> true

This is one of those cases where the internal representation of the arguments matter. Therefore, your mileage will vary depending on how you initialize your object.

Shoring answered 12/2, 2015 at 15:55 Comment(4)
Sorry but this doesn't even answer the question. Ruby can store a floating point value of 34.13985572755337 just fine, the problem is that somewhere in the guts of BigDecimal, some floating point values are getting rounded in bizarre ways. I don't think ~0.13 counts as a small imprecision with values ~34. This answer is a complete cop-out.Dieselelectric
@muistooshort Feel free to provide your own analysis of the underlying C module, or wherever you think the problem may lie on affected interpreters. I've provided a pragmatic solution to the OP's problem, and the solution demonstrably works. Questions about why language implementers implement things the way they do are off-topic and should be closed as opinion-based. However, you are certainly entitled to your bike-shedding, but since SO is not the MRI bug tracker, I think a concrete answer showing how to initialize a BigDecimal reliably is a valid answer. YMMV.Shoring
@CodeGnome, there is a difference between language design decisions and the source of a bug with the implementation (assuming this is one). A practical solution is always welcome and it's fine if you don't want to track the issue yourself, but don't dismiss it as off-topic for SO. A very narrow, specific bug in the low level implementation of something is the very opposite of "primary opinion based". The MRI bug tracker comment is a non-sequitur. Over 50% of the questions on SO about open source tools could probably be answered by looking the documentation or opening an issue on github.Coniah
@CodeGnome, The mission of SO, as I understand it, is exactly to be the first search result on google for any concrete enough programming question. This is one of them. I didn't find the answer I was looking for on this page.Coniah

© 2022 - 2024 — McMap. All rights reserved.