You can use floating-point numbers for money

栏目: IT技术 · 发布时间: 4年前

内容简介:One piece of popular programming wisdom is "never using floating-point numbers for money." This has never made sense to me. A 64-bit floating-point numberLet's start by discussing how to get the "right" answer for financial calculations (I have not worked

[ 2020-January-24 08:08 ]

One piece of popular programming wisdom is "never using floating-point numbers for money." This has never made sense to me. A 64-bit floating-point number can represent 15 decimal digits , which is all balances less than 10 trillion (9 999 999 999 999.99), with two digits after the decimal place. Excel does all computation with 64-bit floats , and it is used for tons of financial calculations. After investigating, my conclusion is that the common wisdom is good advice but overly simplified. If you round the result of every computation, then you can get exactly correct financial calculations using floating-point numbers, for realistic ranges of values. However, it can be tricky to make sure you round in the right places, so using a money-specific data type is an easy way to avoid these errors, and is still good advice. The challenge is that you need to carefully manage rounding when computing with money, no matter how you represent it. Floating-point numbers seem like they should "just work," and in most cases, they will. This means rounding bugs will be hidden until the right (wrong?) input is processed.

Let's start by discussing how to get the "right" answer for financial calculations (I have not worked in banking, so please correct me if I get this wrong). Financial calculations are typically written by humans in contracts in decimal (base 10). That is, we use numbers such as CAD $1.35 (2 decimal places), and interest rates like 1.85% (0.0185, 4 decimal places). Payments can only be made with some fixed precision, such as two decimal places in Canadian dollars. When we compute fractional values that may have more decimal places than the payment system, which happens for tax or interest, we have to decide how to round. The exact rule to be used will vary. In many cases, such as US banking interest payments, there is not a required standard, which I find surprising since banking has a reputation for punishingly exact regulation. I use round half to even in this article, since it is less biased and is the default rounding mode for IEEE floating-point numbers. No matter what rule we choose, the correct results are what we would compute "by hand," using the decimal math we learned in school.

Floating-point numbers in our computers are binary (base 2). This means there are some base-10 numbers that can't be represented exactly when converting between the two. Let's look at a few concrete examples. These are using IEEE 754 64-bit floating point values , and should be the results you will get with C/C++/Java double, Go float64, JavaScript, and Python (and probably nearly every programming language?).

  • 0.1 + 0.2 : Produces 0.30000000000000004 but should be exactly 0.3. This means (0.1 + 0.2) == 0.3 is false when it should be true. See Bruce Dawson's explanation for details .
  • 1.40 * 165 : produces 230.99999999999997 but should be exactly 231. This can cause errors when computing the total price for a purchase order, particularly if we truncate the output to 2 digits, instead of rounding.
  • Round half to nearest even to 2 decimal places (e.g. Python 3): round(2.675, 2) produces 2.67 but should produce 2.68, since 2.675 is halfway between 2.67 and 2.68, and it should round to the even choice. This is because 2.675 is actually 2.6749999999999998, which is slightly below half. The opposite error happens with round(2.665, 2) which produces 2.67 but should produce 2.66.
  • 1.25% of $0.40. This is equivalent to 0.0125*0.40 which should produce 0.005000, which if using round to even rounds to $0.00. However, in floating-point numbers, it is above the halfway point (0.00500000000000000097), so it rounds up.

Solution: Round after every operation

We can solve these problems by rounding after every operation. The "intuitive" reasoning is if we were computing numbers by hand, we need a fixed number of decimal places: e.g. 2 for basic totals. The floating-point numbers approximate the decimal numbers with a tiny bit of error. Since we "know" the exact answers have a finite number of decimal digits, we can just round off the lower part of the numbers, which will produce the nearest float with that number of digits. For this to work, the results must have at most 15 decimal digits, which is less than 10 trillion for 2 digits after the decimal, or less than 1 billion for 6 digits (e.g. interest calculations). As shown by Excel, you probably don't need to round after every operation: the error may accumulate, but the number of operations you would need to cause a 1 cent error is pretty huge. However, in this case you will need to "double round" before using the final result. You need to first round to the number of significant figures from your calculation, then you need to apply your rounding rule down to cents. Otherwise, you will get some rounding edge cases wrong (e.g. 3.6% of $3.75 = 0.135, which should round to $0.14, but in floating-point it is 0.1349... which rounds to $0.13).

I am not a theoretician and have not proven that this is actually correct. Let me know if you find a counter-example. I did, however, try every interest calculation in the range of [0.00%-4.00%] with all two-digit values [0.00-4.00], and they were equivalent to a precise decimal math library, so it does work at least for that limited domain (Go test program).

Manual fixed-point calculations

Another common suggested solution is to use an integer value that represents the largest precision you need. For example, CAD $1.23 could be stored as the integer 123. This is great for integer multiplication, addition and subtraction, which is sufficient for basic order accounting. However, as soon as you need to deal with interest or taxes, you need to track the number of decimal places and manage rounding, which is just as much of a pain as using floating point. For example, 1.25% of $0.40 can be expressed as 125 * 40 = 5000. However, we need to remember this has 6 digits after the decimal point, so is equivalent to 0.005 (4 decimal places times 2 decimal places produces a result with 6 decimal places). To convert to cents, we need to divide by 10000 and decide how to round. The primary advantage of using an integer is that it forces you to think about the conversions, rather than blindly applying the calculations and forgetting about rounding.

Conclusions

My summary: if you are doing some financial math that does not need to be accurate to the penny, just use floating point numbers. If it is good enough for Excel, it will be good enough for most applications. This is the approach I took when building acloud cost model that breaks down a cloud bill by software component. I was able to get it to exactly equal the bill by applying rounding rules in the right places, but simplified the code by not doing it. However, if you are writing software that needs to get it exactly right, use a specialized package. Not only will the APIs force you to get the math right, but they also provide tools to make it easier, such as rounding rules or currency support.

Further Reading


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法之美

算法之美

[美]布莱恩·克里斯汀、[美]汤姆·格里菲思 / 万慧、胡小锐 / 中信出版集团 / 2018-5-20 / 59.00

我们所有人的生活都受到有限空间和有限时间的限制,因此常常面临一系列难以抉择的问题。在一天或者一生的时光里,哪些事是我们应该做的,哪些是应该放弃的?我们对杂乱无序的容忍底线是什么?新的活动与熟悉并喜爱的活动之间如何平衡,才能取得令人愉快的结果?这些看似是人类特有的难题,其实不然,因为计算机也面临同样的问题,计算机科学家几十年来也一直在努力解决这些问题,而他们找到的解决方案可以给我们很多启发。 ......一起来看看 《算法之美》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具