对不精确表示的浮点数进行舍入
对于某些财务计算,我们在对浮点数进行舍入时遇到问题。
基本上,我们希望将 1000000000.555 这样的货币金额四舍五入到小数点后两位。但是,该数字的浮点表示形式为 1000000000.5549999,因此我们将向下舍入为 1000000000.55,而不是正确的 1000000000.56。
我们有什么办法可以安全地解决这个问题吗?到目前为止,我们唯一想到的就是在舍入操作之前始终将最小的可表示浮点数添加到金额中,但我们不知道这在所有情况下是否都是安全的。
该代码是用 C 编写的,需要在 windows32/64/linux/solaris 上运行,因此遗憾的是我们无法访问 .net 中的 Decimal 数据类型等好东西。
任何输入都会有帮助。
谢谢, 里卡德
We have a problem with rounding of floating point numbers for some financial calculations.
Basically, we want to round monetary amounts like 1000000000.555 to 2 decimals. However, the float representation of this number is 1000000000.5549999 and as a result we will round down to 1000000000.55 rather than the correct 1000000000.56.
Is there any way we can come around this problem in a safe way? The only thing we have come up with so far is to always add the smallest representable float to the amount before the rounding operation, but we don't know if this is safe in all cases.
The code is written in C and need to run on windows32/64/linux/solaris so we unfortunately do not have access to nice stuff like the Decimal datatype in .net.
Any input would be helpful.
Thanks,
Rickard
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
最常见的表示货币的正确方法是使用整数。例如,在欧元区,一种常见的方法是以微欧元 (1E-6) 表示价值。显然您会为此使用 64 位数学。您将在整个应用程序中始终如一地使用它。只有在人类 I/O 上才会进行四舍五入,并且可以通过除以 10000 来获得以美分为单位的整数。
The proper way to represent currencies most often is using integers. For instance, in the Euro zone a common approach is to represent values in micro-euro's (1E-6). You'd obviously use 64 bits math for this. You'd consistently use this throughout the application. Only on human I/O would you round, and you would do so by dividing by 10000 to get an integer amount in cents.
这个故事的寓意是永远不要使用浮点数来表示金钱!
金钱是离散的,并且大多数财务和会计法规都承认这一点。您无法实际支付 3.145217 美元的账单。虽然在 17 世纪,人们可以将硬币交给铁匠,让他将银元切成碎片(八片有漂亮的披萨片标记来帮助这个过程!),但今天这是不可能的。
例如,瑞士可用的最小硬币是 5 rappe,因此会计和账单必须精确到 5 美分,即您无法收到 3.14 瑞士法郎的账单,它必须是 3.15 瑞士法郎,或者万一您的供应商是 3.10 瑞士法郎慷慨,因为你只能用现金支付 3.10 或 3.15。
您也有两个选择。获取 boost BigDecimal 库,它将允许您精确指定舍入。或者,正如另一张海报建议的那样,使用 long long 代表千分之一欧元的金额,并且仅显示四舍五入。
另一种可能性是使用半美分的单位,即 55 欧元 35 美分在内部表示为 11070 半美分,那么不需要担心任何标准会计交易的四舍五入。
我不禁担心您没有正确捕获您的业务需求。在我的上一个项目中,有一百多页的业务规则涉及利率计算,其中至少有 40 页涉及计算每个阶段的小数位数或要使用的舍入算法。
The moral of this tale is never, ever use floating point numbers to represent money!
Money comes in discrete amounts, and, most financial and accounting regulations acknowledge this. You cannot physically pay a bill for $3.145217 dollars. While in the 17th century it was acceptable to take a coin to a blacksmith and get him to cut a silver dollar into pieces (Pieces of Eight had nice pizza slice markings to aid this process!)today its just not possible.
For instance the smallest coin available in Switzerland is 5 rappe, so accounting and bills must be expressed to the nearest 5 cents i.e you cannot get a bill for CHF 3.14 it must be CHF 3.15, or CHF 3.10 in the unlikely event that you supplier is generous, because you could can only pay either 3.10 or 3.15 in cash.
There are two options open too you. Get the boost BigDecimal library, which would allow to specify you roundings precisely. Or as another poster suggested use long long representing your amounts in thousandths of Euros and only rounding on display.
A further possibility is to use units of half-cents i.e. 55 Euros 35 cents is represented internally as 11070 half cents then should not need to worry about rounding for any standard accounting transaction.
I cant help worrying that you have not captured your business requirements properly. On my last project there were a over a hundred pages of business rules dealing with interest rate calculations at least 40 pages of this concerned the number of decimal places at each stage of a calculation or the rounding algorithim to be used.
我建议使用十进制数字包,例如 decNumber。
I recommend to use a decimal number package, such as decNumber.
如果您在 Windows 中并且愿意编写托管 C/C++。
在大多数情况下,您只需使用 十进制
如果您无法或不愿意使用托管 C/C++,请查看十进制数字包,例如 decNumber。
在这两种情况下,您都应该进行大量单元测试和集成测试,以涵盖四舍五入的极端情况。他们会给你带来最大的痛苦。舍入也非常适合隐藏其他错误。
然而,正如其他人所说,在转弯时要非常小心。例如,在英国所得税软件规范中,大约一半的文本是关于在哪里以及如何(每个地方都不同)对数字进行舍入的内容,因为您必须获得与使用手动税表相同的结果。
If you are in windows and are willing to write managed C/C++.
In most cases, you can just use the decimal class. From MSDN
If you are not able or willing to use managed C/C++, look at decimal number package, such as decNumber.
In both cases you should have a lot of unit and integration tests that cover the corner cases with rounding. They will give you the most pain. Also rounding is very good at hiding other bugs.
However as other have said be very careful where you round. E.g In the UK income tax software spec about half the text is about where and how (different in each place) to round numbers, as you have to get the same results then using manual tax table.