Why not use Double or Float to represent currency?
Asked Answered
P

16

1235

I've always been told never to represent money with double or float types, and this time I pose the question to you: why?

I'm sure there is a very good reason, I simply do not know what it is.

Pinkie answered 16/9, 2010 at 19:23 Comment(5)
See this SO question: Rounding Errors?Doublereed
Just to be clear, they shouldn't be used for anything that requires accuracy -- not just currency.Gonzales
They shouldn't be used for anything that requires exactness. But double's 53 significant bits (~16 decimal digits) are usually good enough for things that merely require accuracy.Thales
@jeff Your comment completely misrepresents what binary floating-point is good for and what it isn't good for. Read the answer by zneak below, and please delete your misleading comment.Weltanschauung
And to be clear, by "exactness" (or "precision") you mean in decimal.Conch
E
1280

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.

Eckenrode answered 16/9, 2010 at 19:26 Comment(39)
@Fran You will get rounding errors and in some cases where large quantities of currency are being used, interest rate computations can be grossly offGallicize
...most base 10 fractions, that is. For example, 0.1 has no exact binary floating-point representation. So, 1.0 / 10 * 10 may not be the same as 1.0.Hovel
@Gallicize I think Fran was trying to be funny. Anyway, zneak's answer is the best I've seen, better even than the classic version by Bloch.Writhe
Of course if you know the precision, you can always round the result and thus avoid the whole issue. This is much faster and simpler than using BigDecimal. Another alternative is to use fixed precision int or long.Paunch
Unless you're building an FX system where exchange rates often go to 5 decimal places - you'd have to multiply everything up so you end up working in picoDollars or something to avoid rounding errors.Wafer
@Darren, I'm not sure what you mean. Are you trying to say that floats and doubles are appropriate when you need 5 decimal places? If so, please let me point out the BigDecimal class in Java and the decimal type in C#, which aim to solve that problem exactly.Eckenrode
@Eckenrode no, I'm a huge fan of BigDecimal (although I usually wrap it with an Amount class and then wrap that in a Money (Amount + Currency) object). I was pointing out that integers are not great as soon as you have FX calculations to perform.Wafer
should the first sentence be "rational" numbers, not "real"?Daysidayspring
@LevinMagruder, technically yes.Eckenrode
@LevinMagruder well it is true for both.Dominquedominquez
@Eckenrode While we're being pedantic, an IEEE-754 floating-point number does not necessarily have a base of two. The standard also defines base 10 floating-point numbers, though they are much less common.Electrolyse
@Eckenrode you do know that the rational numbers are a subset of the real numbers, right? IEEE-754 real numbers are real numbers. They just happen to also be rational.Dominquedominquez
@Tim, this is misleading because IEEE-754 numbers are all rational (except +/- inf and NaNs). Non-rational real numbers can't be represented by an IEEE-754 number. You can't store the infinite numbers of pi in a double, even though you can store a rational approximation.Eckenrode
Also, you should not use String for storing strings, because 'ab' will be represented as 'aaaaaaaaaaaab'. Use BigStringy instead. Seriously, how can we live with such flaw in the binary storage of floats?Caseous
This answer contains quite a few mathematical inaccuracies. In my opinion, it clouds the truth more than clarifying anything. I can't believe that so many people have upvoted it. Dogbane's answer surpasses this one in both clarity and correctness.Grozny
Well @DavidWallace, don't hold yourself back. Calling out the existence of problems alone won't make them disappear.Eckenrode
... make small errors into big errors, but it is not the reason to avoid using floats and doubles. In summary, I really don't feel you've answered the question correctly.Grozny
@DavidWallace, I just edited my post. I visibly screwed up with the denomination of number groups, which I believe makes for points 1 and 2. Now, for the issue of the tiny errors; a double has a precision of approximately 15 decimal digits, so for the sake of pragmatism, I consider that warning that it might look fine at first is important.Eckenrode
Wow, thank you for taking my criticisms seriously. What you have done is a huge improvement. I have removed my downvote and replaced it with an upvote. I'm still uneasy about the very last sentence, but I understand what you are trying to say about the accumulation of errors, so I see the wisdom of leaving it in. Thank you again for improving this answer.Grozny
Great answer to the original question. However, as a developer, I would love to see a recommendation of what we should use instead.Auramine
What about using Algebra to avoid the accumulation of errors? Do the algebraic data types in functional languages help?Levigate
@Eckenrode - when will the rounding errors become significant ?Monicamonie
An additional problem is seen with your own example of 1/3. Let's suppose that this value somehow sneaks into a "price" field - the customer is shown $0.33 as the price, so far so good. Now, what do you do if they by 3 items? The proper and expected result is $0.99, but simple addition will result in $1.00 since you had stored the full 1/3 instead of truncating it to 0.33.Delta
well, I may not be correct but if this is the case then how scientist do their complex mathematical calculation i.e. space shuttle, black hole etc.Aperient
Wow I didn't know this at all. It would explain some odd errors I've been getting.Divided
Is BigDecimal really advisable ? It does not have infinite precision, it merely offers more control over the rounding behaviour. Right ?Plain
@Dici, BigDecimal is actually a base 10 number, whereas float and double are base 2 numbers.Eckenrode
"if you take the floating-point value for 0.1 and you multiply it by 10, you won't get 1" Curiously, you do get 1, at least in Javascript, Python, Ruby and C (with both doubles and floats). Maybe, it rounds to 1. Still, your point can be shown with 0.1 + 0.2 != 0.3.Kenlay
@Kenlay You are right, the statement that float(0.1) * 10 ≠ 1 is wrong. In a double-precision float, 0.1 is represented as 0b0.00011001100110011001100110011001100110011001100110011010 and 10 as 0b1010. If you multiply these two binary numbers, you get 1.0000000000000000000000000000000000000000000000000000010, and after that has been rounded to the available 53 binary digits, you have exactly 1. The problem with floats is not that they always go wrong, but that they sometimes do - as with the example of 0.1 + 0.2 ≠ 0.3.Slowly
Long is preferable over BigDecimal since (i) transactions cannot have fractional cent (/pence/etc) (ii) BigDecimal is expensive w.r.t. CPU, RAM and storage. (iii) Many serialisation formats do not support BigDecimal, this can cause subtle errors (e.g. JSON) (iv) BigDecimal is not a primitive type. (v) Each language treats Decimal differently, e.g. in Java one has to be careful to use the correct BigDecimal constructor (vi) Some systems will "silently truncate" non-arbitrary precision decimals, e.g. MySQL.Stork
I understand that accuracy is important in finance. I just can't see how rounding error can have any significance if e.g. double is used. How many operations you need to make prior to that, before it's visible? Another thing if you have operation like $0.01 + $0.02, yes you will end up with rounding error, but what if you do the rounding each operation, so you don't propagate the rounding error, but consequently stay in your precision range? Would that work?Endoskeleton
@NeverEndingQueue I have similar thoughts as you. I am writing a Monte Carlo simulation program that deals with forecasting how money is spent, which does not seem to require exactness/accuracy since the numbers will all be randomized anyways, and the numbers are all going to be relatively large (millions to hundreds of millions), so perhaps being off by 0.00000001 isn't so bad - I'm not sure how to quantify how much rounding error gets accumulated anyway.Henricks
@PeterLawrey I have used long in our system involving money. Mostly because I wanted to do deductions on balances using Interlocked.Decrement but it was a good fit in other parts too. So I represent all money in the fractional money units. E.g. cents. I was revisiting the problem the other day and I can't find anything to back up my original design decision. What are the downsides to using an integer and expressing in fractional units?Carissacarita
BigDecimal also can't represent 1/3. SO?Homer
@Carissacarita The main downside is the cost of an error. In floating-point, you can get a horrible number possibly out by a fraction of a cent on a $1m trade. For fixed precision, you can be out by one decimal place or two; worst case a $1m trade turns into $100m.Paunch
@PeterLawrey So use BigInteger, which can theoretically store values up to 2²¹⁴⁷⁴⁸³⁶⁴⁷-1, except that storing a number that large would use up the entire addressable memory space of a 64-bit CPU.Infatuation
@A.R. Most currencies have decimal places. Perhaps you meant BigDecimal. The problem with BigDecimal is it can hide rounding errors because the results always look reasonable. (Also Java doesn't have language support like C# does)Paunch
@A.R. The theoretical limit of BigInteger is ~2 billion 64-bit values of 128 billion bits. However, the longest decimal String is ~2 billion digits.Paunch
So multiply by 10ˣ, where x is the number of digits of decimal precision required. When it's time to display, put the decimal point in the right place. Make x a constant stored in one place, and centralize the display logic in one place that depends on x. That way, if you need to change the precision, just update the constant.Infatuation
F
366

From Bloch, J., Effective Java, (2nd ed, Item 48. 3rd ed, Item 60):

The float and double types are particularly ill-suited for monetary calculations because it is impossible to represent 0.1 (or any other negative power of ten) as a float or double exactly.

For example, suppose you have $1.03 and you spend 42c. How much money do you have left?

System.out.println(1.03 - .42);

prints out 0.6100000000000001.

The right way to solve this problem is to use BigDecimal, int or long for monetary calculations.

Though BigDecimal has some caveats (please see currently accepted answer).

Frontlet answered 16/9, 2010 at 19:52 Comment(12)
I'm a little confused by the recommendation to use int or long for monetary calculations. How do you represent 1.03 as an int or long? I've tried "long a = 1.04;" and "long a = 104/100;" to no avail.Auramine
@Peter, you use long a = 104 and count in cents instead of dollars.Eckenrode
@Eckenrode What about when a percentage needs to be applied like compounding interest or similar?Incontrovertible
@trusktr, I'd go with your platform's decimal type. In Java, that's BigDecimal.Eckenrode
@maaartinus ...and you don't think using double for such things is error-prone? I've seen the float rounding issue hit real systems hard. Even in banking. Please don't recommend it, or if you do, provide that as a separate answer (so we can downvote it :P )Beanery
May I ask what's wrong with 0.6100000000000001? We may round it off to 0.61, right? And also if we use integers and cents, I wonder what happens when we arrive at values less than a cent in our calculations? Do we round them off down? For instance, when there are fractions, we may divide a dollar(100c) by 3 which will be 33.3333... . Now is it safe to save 33? What happens when these rounded off values are accumulated? Three transactions would be almost a cent lost! Where am I wrong?Commissariat
@Commissariat The problem with 0.6100000000000001 is that exists an error not in the made calculation but in the data-type itself because of its own base 2 basis. Mathematically, the result is wrong. Of course you could truncate or round the result to show it right, but it only hides the initial error. In your example, you can use the decimal amount you wish to store in integer data-type. You could use 33333 to save 33.333 (one more decimal position) if you don't want to lose a cent in each transaction. But it becomes impractical and programatically it spends more than a cent per transaction.Diarchy
@Commissariat 0.6100000000000001 is simply wrong in the context of monetary calculations. If you do such math in a test in school you'll get a F. Also it's impossible to divide 100 cts. by 3 (equally distribute 100 cts. over 3 parties physically). For such scenarios there (must) exist rules that have to be considered, e.g. "distribute smallest unit of remainder of division to parties Px in their given order", such that P1 gets 34 cts. and P2/P3 get 33 cts. I also disagree with @Andrés Morales: Abstracting monetary stuff with FPN is not an error of the datatype, it is simply wrong by design.Lenz
@Eckenrode This is what I did in the system I designed, used integer and store money in the currency's fractional units. Is there any downside to doing this?Carissacarita
@Carissacarita A potential downside, depending on the specific case, is that it does not allow for fractional cents, used for example in stock trading, gas prices, interest rates, etc. Most applications that deal with normal prices in dollars and cents (for example) will not have that problem. In many cases it can make sense to create a value object type for monetary values and hide the details of whether int, long, BigDecimal, or something else is used.Doall
@RaimundKrämer perhaps you misread. I am storing fractional cents. We are storing up to 5 decimal places but as integer. So 1 for example represents $0.00001. And 1c equals = 10000Carissacarita
@Carissacarita seems like I misunderstood what you meant by the currency's fractional units. I interpreted it as, e.g., cents being the fractional unit of the dollar, so storing those would mean an integral number of cents. But now I see you meant a smaller, custom fractional unit.Doall
S
96

This is not a matter of accuracy, nor is it a matter of precision. It is a matter of meeting the expectations of humans who use base 10 for calculations instead of base 2. For example, using doubles for financial calculations does not produce answers that are "wrong" in a mathematical sense, but it can produce answers that are not what is expected in a financial sense.

Even if you round off your results at the last minute before output, you can still occasionally get a result using doubles that does not match expectations.

Using a calculator, or calculating results by hand, 1.40 * 165 = 231 exactly. However, internally using doubles, on my compiler / operating system environment, it is stored as a binary number close to 230.99999... so if you truncate the number, you get 230 instead of 231. You may reason that rounding instead of truncating would have given the desired result of 231. That is true, but rounding always involves truncation. Whatever rounding technique you use, there are still boundary conditions like this one that will round down when you expect it to round up. They are rare enough that they often will not be found through casual testing or observation. You may have to write some code to search for examples that illustrate outcomes that do not behave as expected.

Assume you want to round something to the nearest penny. So you take your final result, multiply by 100, add 0.5, truncate, then divide the result by 100 to get back to pennies. If the internal number you stored was 3.46499999.... instead of 3.465, you are going to get 3.46 instead 3.47 when you round the number to the nearest penny. But your base 10 calculations may have indicated that the answer should be 3.465 exactly, which clearly should round up to 3.47, not down to 3.46. These kinds of things happen occasionally in real life when you use doubles for financial calculations. It is rare, so it often goes unnoticed as an issue, but it happens.

If you use base 10 for your internal calculations instead of doubles, the answers are always exactly what is expected by humans, assuming no other bugs in your code.

Stanhope answered 12/9, 2012 at 15:11 Comment(13)
Related, interesting: In my chrome js console: Math.round(.4999999999999999): 0 Math.round(.49999999999999999): 1Grapheme
This answer is misleading. 1.40 * 165 = 231. Any number other than exactly 231 is wrong in a mathematical sense (and all other senses).Screening
@Screening I think that's why Randy says floats are bad... My Chrome JS console shows 230.99999999999997 as the result. That is wrong, which is the point made in the answer.Incontrovertible
@Incontrovertible The misleading part is "For example, using doubles for financial calculations does not produce answers that are "wrong" in a mathematical sense". Yes, it does. And it's got nothing to do with the choice of base; base 10 has the same problem (e.g. 1/3).Screening
Ah, I see. Well, could be a mis-statement, but overall I think we get the idea from the answer.Incontrovertible
@Karu: Imho the answer is not mathematically wrong. It's just that there are 2 questions one being answered which is not the question being asked. The question your compiler answers is 1.39999999 * 164.99999999 and so on which mathematically correct equals 230.99999.... Obviously tha's not the question that was asked in the first place....Mcwhorter
@Mcwhorter That's not quite true in the general case. The problem is that the result of multiplying two numbers together can have more digits than either of the two numbers had to start with. For example, 0.99 * 0.99 == 0.9801. This happens in base 2 during the multiplication, not base 10, but the effect is the same. So the result can get truncated as well; even though it's a different question being answered, the answer for that question is still not necessarily mathematically correct.Screening
@Mcwhorter Now in this specific case, 165 is represented exactly by both float and double, so there's no 164.9999999 involved. My head hurts but I think that does mean the answer to the question being asked will come out exact. Anyway, you made a good point because it's interesting that there are three distinct places where error creeps in - converting the operands to binary, representing the result in binary, and converting the result to decimal. I think any combination of the three can happen.Screening
How could the number 3.46499999 you are talking about arrive if it has error of almost 0.005, while we start with precision of more then 10 digits?Swimmingly
@CurtisYallop because the closes double value to 0.49999999999999999 is 0.5 Why does Math.round(0.49999999999999994) return 1?Harriman
"This is not a matter of accuracy, nor is it a matter of precision. It is a matter of meeting the expectations of humans who use base 10 for calculations instead of base 2." So... it is a matter of accuracy and precision...Code
At least part of the problem is the base 10 expectation on base 2 calculations. An example where using base 10 leads to the wrong result might help elucidate. 1/3 in base 3 is 0.1. So in base 3: 0.1 + 0.1 + 0.1 == 1 which is the correct answer. If you do the same calculation in base 10: 0.33... + 0.33.. + 0.33.. = 0.99.. With infinite precision this is still the right answer, but any finite precision leads to an approximate answer.Neolithic
So, since monetary values are expressed in cents, then by using base 10 representation for money you will never arrive at a value with infinite decimal points. But you can still have the precision problem described by @ScreeningNeolithic
C
70

I'm troubled by some of these responses. I think doubles and floats have a place in financial calculations. Certainly, when adding and subtracting non-fractional monetary amounts there will be no loss of precision when using integer classes or BigDecimal classes. But when performing more complex operations, you often end up with results that go out several or many decimal places, no matter how you store the numbers. The issue is how you present the result.

If your result is on the borderline between being rounded up and rounded down, and that last penny really matters, you should be probably be telling the viewer that the answer is nearly in the middle - by displaying more decimal places.

The problem with doubles, and more so with floats, is when they are used to combine large numbers and small numbers. In java,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

results in

1.1875
Colp answered 3/4, 2013 at 14:54 Comment(13)
THIS!!!! I was searching all answers to find this RELEVANT FACT!!! In normal calculations nobody cares if you are of by some fraction of a cent, but here with high numbers easily some dollars get lost per transaction!Bouley
And now imagine someone getting daily revenue of 0.01% on his 1 Million dollars - he would get nothing each day - and after a year he has not gotten 1000 Dollars, THIS WILL MATTERBouley
The problem is not the accuracy but that float doesn't tell you that it becomes inaccurate. An integer can only hold up to 10 digits a float can hold up to 6 without becoming inaccurate (when you cut it accordingly). It does allow this while an integer gets an overflow and a language like java will warn you or won't allow it. When you use a double, you can go up to 16 digits which is enough for many use cases.Balkin
I never realized this! Fast forward 7,5 years and this is still an issue (using C#). 1000000.0m + 1.2m - 1000000.0m does result in 1.2 ;-) Thanks a lot for the example!Punchy
This is an issue with floats -- but can you provide an example of an incorrect result using doubles and any kind of money that might be used in computations today? I've tried calculations with doubles and the US national debt (23 trillion roughly in 2020) and still can't find rounding errors.Distiller
@Josiah Yoder Just a few minutes ago I visited an online shop where net prices are stored with excessive decimal places. By choosing the right product and amount, I receive an invoice which is 1.15% too high (@20.23 instead of €20.00). Arguably, this issue is not caused by any float format, but due to lack of rounding (or incorrect rounding). But the real point is: when using fixed-point formats, you need to think about decimal places and correct rounding, while floating point formats will leave you as a happy coder who doesn't care. "Just my 23 cent."Harte
@Harte Could you share more details of your response? It's hard for me to imagine any online shop reaching an error of 1.15% when using floats or doubles. Knowing the numbers you used and the prices involved, can you reconstruct calculations that reproduce the 20.23 result so that exactly where the rounding error occurs would be revealed?Distiller
@Josiah Yoder End user unit price: €0.02 (shown in catalog), net price (excl. VAT) internally stored with three decimal places: €0.017, end price (incl. VAT) calculated from net price + 19% VAT would be €0.020 (rounded to three places) - but programmer uses float and does not to three places: €0.02023. These prices are rounded to two places only for display. So 1000 units at €0.02 = €20.23. My point is that, if you have to use fixed point numbers, you need to think harder instead of "doubles are precise enough, so why think?". -- Their invoicing system works differently, though (IIRC).Harte
@Harte Thank you for the specifics. I feel like I'm starting to understand. But I'm unfamiliar with European tax law, and thus confused. Is it correct that prices are often shown as "end user prices" (including tax) and that the seller ought to take the end user price of €0.02, which includes €0.017 for the seller and €0.003 of tax, multiply that by 1000 to get €17.00 for the seller and €3.00 of tax? This feels odd (from an American context, where taxes are always calculated at the end and never included in the advert price), where it feels the taxes on €17.00 @19% ought to be €3.23. Thanks!Distiller
@Josiah Yoder VAT laws in the EU are...complicated. Since the introduction of the Euro, three decimal places are mandatory, meaning that applications typically use 4 decimal places to ensure correct rounding. Prices shown are usually end user prices, but are typically stored as net prices (excl. VAT). VAT is calculated at the end per delivery in Germany, not for individual items. I think the Netherlands however allow to calculate the tax for each item and add sum this up at the end. For VAT advance payments in Germany, different rules apply (even rounding down to zero places at one point).Harte
"The problem with doubles ... is when they are used to combine large numbers and small numbers". This is not the only case. Try: (int) (1209.10 * 100.00). You get 120909. Any floating point calculation is deeply suspect in any programming language. Pay extra attention.Decarbonate
The example here is simply a presentation bug. The price in the catalog should be 0.017. If it worked the other way 0.024 presented at 0.02 and charged at 0.0.24 it is false advertising.Radioactive
sorry should have read: If it worked the other way and the catalog showed 0.02 but then the charged price was 0.024 then that's false advertising and illegal in most countries.Radioactive
P
56

I'll risk being downvoted, but I think the unsuitability of floating point numbers for currency calculations is overrated. As long as you make sure you do the cent-rounding correctly and have enough significant digits to work with in order to counter the binary-decimal representation mismatch explained by zneak, there will be no problem.

People calculating with currency in Excel have always used double precision floats (there is no currency type in Excel) and I have yet to see anyone complaining about rounding errors.

Of course, you have to stay within reason; e.g. a simple webshop would probably never experience any problem with double precision floats, but if you do e.g. accounting or anything else that requires adding a large (unrestricted) amount of numbers, you wouldn't want to touch floating point numbers with a ten foot pole.

Paperboy answered 20/1, 2017 at 11:56 Comment(14)
This is actually a pretty decent answer. In most cases it's perfectly fine to use them.Sinkhole
It should be noted that most investment banks use double as do most C++ programs. Some use long but thus has it's own problem of tracking scale.Paunch
I find this answer intriguing. I assume you and @PeterLawrey speak from experience. Is it possible to find citations / web links to back your claims? I know for a fact that companies use financial information in Excel all the time from my own experience. But what about investment banks using double?Distiller
@JosiahYoder Trading systems were traditionally written in C++ where using double or fixed precision is common. i.e. no BigDecimal. The problem I have with fixed precision is the cost of any potential error. For double is probably less than 1 cents even on a billion-dollar trade, but for fixed precision, you could be out by a factor of 10x or more.Paunch
I was first exposed to this problem many years ago when an accountant told they can not accept a difference of a cent in the books.Menzies
As to excel, I tried in Google sheets and MS Excel, the results are correct. I mean, if we do this in console "1.03 - .42", as above, the result is "0.6100000000000001". Do this in sheets, and you will never get the "1" at the end. So, does this mean, sheets & excel are representing these floating numbers as int from behind? Probably, that's why no one is complaining about inaccuracy, aside from it's really not obvious and since people do often round numbers off.Phototypography
I was having serious issues in MS Excel with rounding errors. With phone numbers. The problem occurred when exporting and re-importing data. Apparently, the conversion between decimal and floating point formats caused...issues; issues which would not have appeared if the number had been handled as decimals all the time. -- With regards to the web shop example, see my comment to Rob Scala's answer. Which confirm the importance of correct rounding as mentioned by you. Just note that it's not always "cent rounding": 0, 3, 4, 5 and 6 decimal places are sometimes required.Harte
@Menzies An accountant who bitched about whole cents appears very generous to me - and probably doesn't do tax calculations in Euros. Since the introduction of the Euro, three decimal places are mandatory for tax calculations, actually requiring four decimal places to allow for correct rounding of the third decimal place.Harte
This was about 30+ years ago, in an (horrible) RM-COBOL 74 application for MS-DOS and certainly not in any first world country. The customer was not bitching, it was a clear bug in very direct calculations. If the invoice total was $100 and tax was 21% it calculated net value $79, tax $21, and when adding the total we would get something like $100.01. I have since seen similar issues in C and Java applications.Menzies
@Menzies Since this was in the early 90's I suspect that float was used instead of double. Rounding errors are MUCH more likely with float.Distiller
Floating point representation errors are an inherent property of floating point mathematics. Using double instead of float is not really an improvement for this kind of error. There are plenty of references to explain this issue. For example dzone.com/articles/…. Actually just go over the answers in this page for explanations and solutions.Menzies
@Phototypography Excel handles the most common forms of rounding errors nicely. See "example of when a value reaches zero" on this pageDistiller
People calculating with currency in Excel have always used double precision floats... and I have yet to see anyone complaining about rounding errors. That's because Excel does all sorts of — evidently ad-hoc — extra munging on the numbers it displays, in order to eliminate most of the anomalies which people would otherwise complain about. So Excel really can't be used as an argument either way on this question.Eastlake
@SteveSummit Quite a bit about Excel scares me. Do you know that it stores times as fractions of a day, so that 00:00:01 = 0.00001157407?Infatuation
B
41

Floats and doubles are approximate. If you create a BigDecimal and pass a float into the constructor you see what the float actually equals:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

this probably isn't how you want to represent $1.01.

The problem is that the IEEE spec doesn't have a way to exactly represent all fractions, some of them end up as repeating fractions so you end up with approximation errors. Since accountants like things to come out exactly to the penny, and customers will be annoyed if they pay their bill and after the payment is processed they owe .01 and they get charged a fee or can't close their account, it's better to use exact types like decimal (in C#) or java.math.BigDecimal in Java.

It's not that the error isn't controllable if you round: see this article by Peter Lawrey. It's just easier not to have to round in the first place. Most applications that handle money don't call for a lot of math, the operations consist of adding things or allocating amounts to different buckets. Introducing floating point and rounding just complicates things.

Bondsman answered 16/9, 2010 at 19:29 Comment(6)
float, double and BigDecimal are represent exact values. Code to object conversion are inexact as well as other operations. The types themselves are not inexact.Aporia
@chux: rereading this, I think you have a point that my wording could be improved. I'll edit this and reword.Bondsman
instead of new BigDecimal(1.01F), try instead new BigDecimal("1.01") because, in your example, the problem doesn't stem from BigDecimal itself, but rather the 1.01F part. This 1.01F is giving you 1.0099999904632568359375 and BigDecimal works fine.Pekan
@Shark: I can't help thinking you missed my point? Of course BigDecimal works fine. I never meant to encourage people to pass in floats to BigDecimal, this was meant as an exercise. If this is unclear and you have a suggestion to improve this then it is welcome.Bondsman
@NathanHughes no, i did not miss the point, and sorry for necroing it after 10 years, i just ran into it today from... some other unrelated place, saw it, and noticed the somewhat-misleading-but-correct groovy output. To improve on it, do try to add one more thing to it: groovy:000> new BigDecimal("1.01") and that output. It will help clarify to novice people that actually nothing but strings (and BigDecimal to a greater extent) can hold precise and accurate information on any N-ary long decimal number :DPekan
@Shark: I think 6hats good advice. If you can't wait for me to get to it then feel free to edit.Bondsman
B
24

While it's true that floating point type can represent only approximatively decimal data, it's also true that if one rounds numbers to the necessary precision before presenting them, one obtains the correct result. Usually.

Usually because the double type has a precision less than 16 figures. If you require better precision it's not a suitable type. Also approximations can accumulate.

It must be said that even if you use fixed point arithmetic you still have to round numbers, were it not for the fact that BigInteger and BigDecimal give errors if you obtain periodic decimal numbers. So there is an approximation also here.

For example COBOL, historically used for financial calculations, has a maximum precision of 18 figures. So there is often an implicit rounding.

Concluding, in my opinion the double is unsuitable mostly for its 16 digit precision, which can be insufficient, not because it is approximate.

Consider the following output of the subsequent program. It shows that after rounding double give the same result as BigDecimal up to precision 16.

Precision 14
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611

Precision 15
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110

Precision 16
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101

Precision 17
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611011
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611013

Precision 18
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110125

Precision 19
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101252

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;

public class Exercise {
    public static void main(String[] args) throws IllegalArgumentException,
            SecurityException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        String amount = "56789.012345";
        String quantity = "1111111111";
        int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
        for (int i = 0; i < precisions.length; i++) {
            int precision = precisions[i];
            System.out.println(String.format("Precision %d", precision));
            System.out.println("------------------------------------------------------");
            execute("BigDecimalNoRound", amount, quantity, precision);
            execute("DoubleNoRound", amount, quantity, precision);
            execute("BigDecimal", amount, quantity, precision);
            execute("Double", amount, quantity, precision);
            System.out.println();
        }
    }

    private static void execute(String test, String amount, String quantity,
            int precision) throws IllegalArgumentException, SecurityException,
            IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
                String.class, int.class);
        String price;
        try {
            price = (String) impl.invoke(null, amount, quantity, precision);
        } catch (InvocationTargetException e) {
            price = e.getTargetException().getMessage();
        }
        System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
                quantity, price));
    }

    public static String divideUsingDoubleNoRound(String amount,
            String quantity, int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        String price = Double.toString(price0);
        return price;
    }

    public static String divideUsingDouble(String amount, String quantity,
            int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        MathContext precision0 = new MathContext(precision);
        String price = new BigDecimal(price0, precision0)
                .toString();
        return price;
    }

    public static String divideUsingBigDecimal(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);
        MathContext precision0 = new MathContext(precision);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0, precision0);

        // presentation
        String price = price0.toString();
        return price;
    }

    public static String divideUsingBigDecimalNoRound(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0);

        // presentation
        String price = price0.toString();
        return price;
    }
}
Biffin answered 26/10, 2012 at 19:3 Comment(1)
COBOL has a native decimal type that is fixed-point. This can accurately reference all decimal types up to 18 digits. That's not the same thing as a floating-point number, regardless of the number of digits, because it is a native decimal type. 0.1 will always be 0.1, not sometimes 0.99999999999999Jarita
B
18

The result of floating point number is not exact, which makes them unsuitable for any financial calculation which requires exact result and not approximation. float and double are designed for engineering and scientific calculation and many times doesn’t produce exact result also result of floating point calculation may vary from JVM to JVM. Look at below example of BigDecimal and double primitive which is used to represent money value, its quite clear that floating point calculation may not be exact and one should use BigDecimal for financial calculations.

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

Output:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9
Brownie answered 11/8, 2014 at 20:18 Comment(7)
Let us try something other than trivial addition/subtraction and integer mutplicaiton, If code calculated the monthly rate of a 7% loan, both types would need fail to provide an exact value and need rounding to the nearest 0.01. Rounding to the lowest monetary unit is a part of money calculations, Using decimal types avoid that need with addition/subtraction - but not much else.Aporia
@chux-ReinstateMonica: If interest is supposed to compound monthly, compute the interest each month by adding together the daily balance, multiply that by 7 (the interest rate), and divide, rounding to the nearest penny, by the number of days in the year. No rounding anywhere except once per month at the very last step.Frustration
@Frustration My comment emphasizes using a binary FP of the smallest monetary unit or a decimal FP both incur similar rounding issues - like in your comment with "and divide, rounding to the nearest penny". Using a base 2 or base 10 FP does not provide an advantage either way in your scenario.Aporia
@chux-ReinstateMonica: In the above scenario, if the math works out that the interest should be precisely equal to some number of half-cents, a correct financial program must round in precisely specified fashion. If floating-point calculations yield an interest value of e.g. $1.23499941, but the mathematically-precise value before rounding should have been $1.235 and rounding is specified as "nearest even",, use of such floating-point calculations won't cause the result to be off by $0.000059, but rather by a whole $0.01, which for accounting purposes is Just Plain Wrong.Frustration
@Frustration Using binary double FP to the cent would have no trouble calculating to the 0.5 cent as neither would decimal FP. If floating-point calculations yield an interest value of e.g. 123.499941¢, either through binary FP or decimal FP, the double rounding problem is the same - no advantage either way. Your premise seems to assume the mathematically-precise value and the decimal FP are the same - something even decimal FP does not guarantee. 0.5/7.0*7.0 is a problem for for binary and deicmal FP. IAC, most of will be moot as I expect the next version of C to provide decimal FP.Aporia
What is required to do financial/accounting calculations properly is to use only mathematically-exact operations except at places where rounding is precisely specified. When properly dividing numbers, either rounding must be specified, one must compute both quotient and remainder, or the product of the quotient and divisor must precisely equal the dividend. Dividing by 7 without specifying rounding or remainder would generally be wrong.Frustration
Thx for the numbers => dotnetfiddle.net/IrrM1BPyrogen
S
18

As said earlier "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, you’ll lose more and more precision as the errors add up. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required."

Finally Java has a standard way to work with Currency And Money!

JSR 354: Money and Currency API

JSR 354 provides an API for representing, transporting, and performing comprehensive calculations with Money and Currency. You can download it from this link:

JSR 354: Money and Currency API Download

The specification consists of the following things:

  1. An API for handling e. g. monetary amounts and currencies
  2. APIs to support interchangeable implementations
  3. Factories for creating instances of the implementation classes
  4. Functionality for calculations, conversion and formatting of monetary amounts
  5. Java API for working with Money and Currencies, which is planned to be included in Java 9.
  6. All specification classes and interfaces are located in the javax.money.* package.

Sample Examples of JSR 354: Money and Currency API:

An example of creating a MonetaryAmount and printing it to the console looks like this:

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

When using the reference implementation API, the necessary code is much simpler:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

The API also supports calculations with MonetaryAmounts:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

CurrencyUnit and MonetaryAmount

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

MonetaryAmount has various methods that allow accessing the assigned currency, the numeric amount, its precision and more:

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;

MonetaryAmounts can be rounded using a rounding operator:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

When working with collections of MonetaryAmounts, some nice utility methods for filtering, sorting and grouping are available.

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

Custom MonetaryAmount operations

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
    BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
    BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
    return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

Resources:

Handling money and currencies in Java with JSR 354

Looking into the Java 9 Money and Currency API (JSR 354)

See Also: JSR 354 - Currency and Money

Salientian answered 7/8, 2016 at 7:8 Comment(1)
kudos for mentioning the MonetaryAmount in Java 9Contact
P
7

Most answers have highlighted the reasons why one should not use doubles for money and currency calculations. And I totally agree with them.

It doesn't mean though that doubles can never be used for that purpose.

I have worked on a number of projects with very low gc requirements, and having BigDecimal objects was a big contributor to that overhead.

It's the lack of understanding about double representation and lack of experience in handling the accuracy and precision that brings about this wise suggestion.

You can make it work if you are able to handle the precision and accuracy requirements of your project, which has to be done based on what range of double values is one dealing with.

You can refer to guava's FuzzyCompare method to get more idea. The parameter tolerance is the key. We dealt with this problem for a securities trading application and we did an exhaustive research on what tolerances to use for different numerical values in different ranges.

Also, there might be situations when you're tempted to use Double wrappers as a map key with hash map being the implementation. It is very risky because Double.equals and hash code for example values "0.5" & "0.6 - 0.1" will cause a big mess.

Pliam answered 12/12, 2017 at 2:52 Comment(0)
R
5

If your computation involves various steps, arbitrary precision arithmetic won't cover you 100%.

The only reliable way to use a perfect representation of results(Use a custom Fraction data type that will batch division operations to the last step) and only convert to decimal notation in the last step.

Arbitrary precision won't help because there always can be numbers that has so many decimal places, or some results such as 0.6666666... No arbitrary representation will cover the last example. So you will have small errors in each step.

These errors will add-up, may eventually become not easy to ignore anymore. This is called Error Propagation.

Rampart answered 5/1, 2015 at 14:56 Comment(0)
M
3

Many of the answers posted to this question discuss IEEE and the standards surrounding floating-point arithmetic.

Coming from a non-computer science background (physics and engineering), I tend to look at problems from a different perspective. For me, the reason why I wouldn't use a double or float in a mathematical calculation is that I would lose too much information.

What are the alternatives? There are many (and many more of which I am not aware!).

BigDecimal in Java is native to the Java language. Apfloat is another arbitrary-precision library for Java.

The decimal data type in C# is Microsoft's .NET alternative for 28 significant figures.

SciPy (Scientific Python) can probably also handle financial calculations (I haven't tried, but I suspect so).

The GNU Multiple Precision Library (GMP) and the GNU MFPR Library are two free and open-source resources for C and C++.

There are also numerical precision libraries for JavaScript(!) and I think PHP which can handle financial calculations.

There are also proprietary (particularly, I think, for Fortran) and open-source solutions as well for many computer languages.

I'm not a computer scientist by training. However, I tend to lean towards either BigDecimal in Java or decimal in C#. I haven't tried the other solutions I've listed, but they are probably very good as well.

For me, I like BigDecimal because of the methods it supports. C#'s decimal is very nice, but I haven't had the chance to work with it as much as I'd like. I do scientific calculations of interest to me in my spare time, and BigDecimal seems to work very well because I can set the precision of my floating point numbers. The disadvantage to BigDecimal? It can be slow at times, especially if you're using the divide method.

You might, for speed, look into the free and proprietary libraries in C, C++, and Fortran.

Miracidium answered 18/7, 2015 at 1:51 Comment(1)
Regarding SciPy/Numpy, fixed-precision (ie Python's decimal.Decimal) is not supported (docs.scipy.org/doc/numpy-dev/user/basics.types.html). Some function won't properly work with Decimal (isnan for instance). Pandas is based on Numpy and was initiated at AQR, one major quantitative hedge-fund. So you have your answer regarding financial calculations (not grocery accounting).Lunneta
P
2

Take a look at this simple example: it looks like logically correct, but in real world this can return unexpected results if not threated correctly:

0.1 x 10 = 1 👍 , so:

double total = 0.0;

// adds 10 cents, 10 times
for (int i = 0; i < 10; i++) {
    total += 0.1;  // adds 10 cents
}

Log.d("result: ", "current total: " + total);

// looks like total equals to 1.0, don't?

// now, do reverse
for (int i = 0; i < 10; i++) {
    total -= 0.1;  // removes 10 cents
}

// total should be equals to 0.0, right?
Log.d("result: ", "current total: " + total);
if (total == 0.0) {
    Log.d("result: ", "is total equal to ZERO? YES, of course!!");
} else {
    Log.d("result: ", "is total equal to ZERO? No...");
    // so be careful comparing equality in this cases!!!
}

OUTPUT:

 result: current total: 0.9999999999999999
 result: current total: 2.7755575615628914E-17   🤔
 result: is total equal to ZERO? No... 😌
Pretended answered 24/11, 2015 at 19:50 Comment(1)
The problem is not that round-off error happens, but that you doesn't deal with it. Round the result to two decimal places (if you want cents) and you're done.Stephaniestephannie
C
2

To add on previous answers, there is also the option of implementing Joda-Money in Java, besides BigDecimal, when dealing with the problem addressed in the question. Java module name is org.joda.money.

It requires Java SE 8 or later and has no dependencies.

To be more precise, there is a compile-time dependency but it is not required.

<dependency>
  <groupId>org.joda</groupId>
  <artifactId>joda-money</artifactId>
  <version>1.0.1</version>
</dependency>

Examples of using Joda Money:

  // create a monetary value
  Money money = Money.parse("USD 23.87");
  
  // add another amount with safe double conversion
  CurrencyUnit usd = CurrencyUnit.of("USD");
  money = money.plus(Money.of(usd, 12.43d));
  
  // subtracts an amount in dollars
  money = money.minusMajor(2);
  
  // multiplies by 3.5 with rounding
  money = money.multipliedBy(3.5d, RoundingMode.DOWN);
  
  // compare two amounts
  boolean bigAmount = money.isGreaterThan(dailyWage);
  
  // convert to GBP using a supplied rate
  BigDecimal conversionRate = ...;  // obtained from code outside Joda-Money
  Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
  
  // use a BigMoney for more complex calculations where scale matters
  BigMoney moneyCalc = money.toBigMoney();

Documentation: http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html

Implementation examples: https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money

Chev answered 25/4, 2019 at 14:49 Comment(0)
M
0

Float is binary form of Decimal with different design; they are two different things. There are little errors between two types when converted to each other. Also, float is designed to represent infinite large number of values for scientific. That means it is designed to lost precision to extreme small and extreme large number with that fixed number of bytes. Decimal can't represent infinite number of values, it bounds to just that number of decimal digits. So Float and Decimal are for different purpose.

There are some ways to manage the error for currency value:

  1. Use long integer and count in cents instead.

  2. Use double precision, keep your significant digits to 15 only so decimal can be exactly simulated. Round before presenting values; Round often when doing calculations.

  3. Use a decimal library like Java BigDecimal so you don't need to use double to simulate decimal.

p.s. it is interesting to know that most brands of handheld scientific calculators works on decimal instead of float. So no one complaint float conversion errors.

Mccammon answered 29/5, 2020 at 12:1 Comment(0)
R
-1

American currency can easily be represented with dollar and cent amounts. Integers are 100% precise, while floating point binary numbers do not exactly match floating point decimals.

Runck answered 20/8, 2020 at 15:50 Comment(2)
Wrong. Integers are not 100% precise. Precision requires decimal or fraction.Apologete
They are precise for integral values like currency.Runck

© 2022 - 2024 — McMap. All rights reserved.