例如,此博客 说 0.005 并不完全是 0.005,但对该数字进行四舍五入会产生正确的结果。
我已经尝试过 C++ 中的各种舍入,但在将数字舍入到某些小数位时失败。例如,Round(x,y) 将 x 四舍五入为 y 的倍数。所以 Round(37.785,0.01) 应该给你 37.79 而不是 37.78。
我重新提出这个问题是为了向社区寻求帮助。问题在于浮点数的不精确性(37,785 表示为 37.78499999999)。
问题是Excel如何解决这个问题呢?
对于上述问题,round() for float in C++ 中的解决方案是不正确的。
For example, this blog says 0.005 is not exactly 0.005, but rounding that number yields the right result.
I have tried all kinds of rounding in C++ and it fails when rounding numbers to certain decimal places. For example, Round(x,y) rounds x to a multiple of y. So Round(37.785,0.01) should give you 37.79 and not 37.78.
I am reopening this question to ask the community for help. The problem is with the impreciseness of floating point numbers (37,785 is represented as 37.78499999999).
The question is how does Excel get around this problem?
The solution in this round() for float in C++ is incorrect for the above problem.
发布评论
评论(12)
“Round(37.785,0.01) 应该给你 37.79 而不是 37.78。”
首先,对于 37.79 而不是 37.78 才是“正确”答案没有达成共识?决胜局总是有点艰难。虽然平局时总是向上舍入是一种广泛使用的方法,但它肯定不是唯一的方法。
其次,这不是打破平局的情况。 IEEE 二进制 64 浮点格式的数值为 37.784999999999997(大约)。除了人工输入 37.785 值并碰巧将其转换为浮点表示形式之外,还有很多方法可以获得 37.784999999999997 的值。在大多数情况下,正确答案是 37.78,而不是 37.79。
附录
考虑以下 Excel 公式:
两个单元格将显示相同的值 37.79。关于 37785/1000 是否应该以两位精度舍入为 37.78 还是 37.79,存在一个合理的争论。如何处理这些极端情况有点武断,并且没有一致的答案。 Microsoft 内部甚至没有达成共识的答案:“由于历史原因,Round() 函数在不同的 Microsoft 产品中并未以一致的方式实现。”( http://support.microsoft.com/kb/196652 ) 给定无限精度的机器,Microsoft 的 VBA 会将 37.785 舍入为37.78(银行家回合),而 Excel 将产生 37.79(对称算术回合)。
对于后一个公式的舍入没有争议。它严格小于 37.785,因此应四舍五入为 37.78,而不是 37.79。但 Excel 将其四舍五入。为什么?
原因与实数在计算机中的表示方式有关。与许多其他公司一样,Microsoft 使用 IEEE 64 位浮点格式。数字 37785/1000 在以这种格式表示时会出现精度损失。 19810222/2^19+21474836/2^47 不会出现这种精度损失;这是一个“确切的数字”。
我特意构造了该精确数字,使其具有与不精确的 37785/1000 相同的浮点表示形式。 Excel 向上而不是向下舍入这个精确值是确定 Excel 的
ROUND()
函数如何工作的关键:它是对称算术舍入的变体。它基于与极端情况的浮点表示的比较进行舍入。C++ 中的算法:
"Round(37.785,0.01) should give you 37.79 and not 37.78."
First off, there is no consensus that 37.79 rather than 37.78 is the "right" answer here? Tie-breakers are always a bit tough. While always rounding up in the case of a tie is a widely-used approach, it certainly is not the only approach.
Secondly, this isn't a tie-breaking situation. The numerical value in the IEEE binary64 floating point format is 37.784999999999997 (approximately). There are lots of ways to get a value of 37.784999999999997 besides a human typing in a value of 37.785 and happen to have that converted to that floating point representation. In most of these cases, the correct answer is 37.78 rather than 37.79.
Addendum
Consider the following Excel formulae:
Both cells will display the same value, 37.79. There is a legitimate argument over whether 37785/1000 should round to 37.78 or 37.79 with two place accuracy. How to deal with these corner cases is a bit arbitrary, and there is no consensus answer. There isn't even a consensus answer inside Microsoft: "the Round() function is not implemented in a consistent fashion among different Microsoft products for historical reasons." ( http://support.microsoft.com/kb/196652 ) Given an infinite precision machine, Microsoft's VBA would round 37.785 to 37.78 (banker's round) while Excel would yield 37.79 (symmetric arithmetic round).
There is no argument over the rounding of the latter formula. It is strictly less than 37.785, so it should round to 37.78, not 37.79. Yet Excel rounds it up. Why?
The reason has to do with how real numbers are represented in a computer. Microsoft, like many others, uses the IEEE 64 bit floating point format. The number 37785/1000 suffers from precision loss when expressed in this format. This precision loss does not occur with 19810222/2^19+21474836/2^47; it is an "exact number".
I intentionally constructed that exact number to have the same floating point representation as does the inexact 37785/1000. That Excel rounds this exact value up rather than down is the key to determining how Excel's
ROUND()
function works: It is a variant of symmetric arithmetic rounding. It rounds based on a comparison to the floating point representation of the corner case.The algorithm in C++:
为了获得非常准确的任意精度并将浮点数舍入到一组固定的小数位,您应该查看 数学库就像 GNU MPFR 一样。虽然它是一个 C 库,但如果您想避免使用 C,我发布的网页还链接到几个不同的 C++ 绑定。
您可能还想阅读一篇题为 "每个计算机科学家应该了解的浮点知识施乐帕洛阿尔托研究中心的大卫·戈德堡(David Goldberg)的算术”。这是一篇优秀的文章,演示了允许在表示二进制数据中的所有内容的计算机中近似浮点数的基本过程,以及舍入误差和其他问题如何在基于 FPU 的浮点数学中蔓延。
For very accurate arbitrary precision and rounding of floating point numbers to a fixed set of decimal places, you should take a look at a math library like GNU MPFR. While it's a C-library, the web-page I posted also links to a couple different C++ bindings if you want to avoid using C.
You may also want to read a paper entitled "What every computer scientist should know about floating point arithmetic" by David Goldberg at the Xerox Palo Alto Research Center. It's an excellent article demonstrating the underlying process that allows floating point numbers to be approximated in a computer that represents everything in binary data, and how rounding errors and other problems can creep up in FPU-based floating point math.
我不知道Excel是如何做到的,但是很好地打印浮点数是一个难题: http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/
I don't know how Excel does it, but printing floating point numbers nicely is a hard problem: http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/
所以你的实际问题似乎是,如何获得正确舍入的浮点数 ->字符串转换。通过谷歌搜索这些术语,您会得到一堆文章,但如果您对使用的东西感兴趣,大多数平台都提供了 sprintf()/snprintf() 的相当有效的实现。因此,只需使用这些,如果发现错误,请向供应商提交报告。
So your actual question seems to be, how to get correctly rounded floating point -> string conversions. By googling for those terms you'll get a bunch of articles, but if you're interested in something to use, most platforms provide reasonably competent implementations of sprintf()/snprintf(). So just use those, and if you find bugs, file a report to the vendor.
无法编写以浮点数作为参数并返回另一个浮点数(精确舍入到给定十进制位数)的函数,因为许多具有有限十进制表示形式的数字具有无限二进制表示形式;最简单的例子之一是 0.1 。
为了实现您想要的效果,您必须接受使用不同的类型作为舍入函数的结果。如果您迫切需要打印数字,您可以使用字符串和格式化函数:问题变成如何准确获得您期望的格式。否则,如果您需要存储此数字以便对其执行精确计算,例如如果您正在进行会计,则需要一个能够准确表示十进制数字的库。在这种情况下,最常见的方法是使用缩放表示:值的整数加上小数位数。将值除以十,得到原始数字。
如果这些方法中的任何一个合适,我将尝试通过实用的建议来扩展我的答案。
A function that takes a floating point number as argument and returns another floating point number, rounded exactly to a given number of decimal digits cannot be written, because there are many numbers with a finite decimal representation that have an infinite binary representation; one of the simplest examples is 0.1 .
To achieve what you want you must accept to use a different type as a result of your rounding function. If your immediate need is printing the number you can use a string and a formatting function: the problem becomes how to obtain exactly the formatting you expect. Otherwise if you need to store this number in order to perform exact calculations on it, for instance if you are doing accounting, you need a library that's capable of representing decimal numbers exactly. In this case the most common approach is to use a scaled representation: an integer for the value together with the number of decimal digits. Dividing the value by ten raised to the scale gives you the original number.
If any of these approaches is suitable, I'll try and expand my answer with practical suggestions.
我相信以下 C# 代码对数字进行四舍五入,就像在 Excel 中对数字进行四舍五入一样。要准确复制 C++ 中的行为,您可能需要使用特殊的十进制类型。
用简单的英语来说,双精度数被转换为小数,然后四舍五入到十五位有效数字(不要与十五位小数混淆)。结果再次四舍五入到指定的小数位数。
这可能看起来很奇怪,但您必须了解的是,Excel始终显示四舍五入到 15 位有效数字的数字。如果 ROUND() 函数不使用该显示值作为起点,而是使用内部双精度表示形式,则可能会出现 ROUND(A1,N) 似乎与 A1 中的实际值不对应的情况。这对于非技术用户来说会非常困惑。
最接近 37.785 的双精度数的精确十进制值为 37.784999999999996589394868351519107818603515625。 (任何双精度数都可以用有限的十进制小数精确表示,因为四分之一、八分之一、十六分之一等都具有有限的小数扩展。)如果该数字直接四舍五入到小数点后两位,则与打破,结果将是 37.78。如果先四舍五入到 15 位有效数字,您将得到 37.7850000000000。如果进一步四舍五入到小数点后两位,则得到 37.79,所以这根本就没有什么神秘之处。
I believe the following C# code rounds numbers as they are rounded in Excel. To exactly replicate the behavior in C++ you might need to use a special decimal type.
In plain English, the double-precision number is converted to a decimal and then rounded to fifteen significant digits (not to be confused with fifteen decimal places). The result is rounded a second time to the specified number of decimal places.
That might seem weird, but what you have to understand is that Excel always displays numbers that are rounded to 15 significant figures. If the ROUND() function weren't using that display value as a starting point, and used the internal double representation instead, then there would be cases where ROUND(A1,N) did not seem to correspond to the actual value in A1. That would be very confusing to a non-technical user.
The double which is closest to 37.785 has an exact decimal value of 37.784999999999996589394868351519107818603515625. (Any double can be represented precisely by a finite base ten decimal because one quarter, one eighth, one sixteenth, and so forth all have finite decimal expansions.) If that number were rounded directly to two decimal places, there would be no tie to break and the result would be 37.78. If you round to 15 significant figures first you get 37.7850000000000. If this is further rounded to two decimal places, then you get 37.79, so there is no real mystery after all.
Excel 通过做 WORK 来“正确”地舍入这样的数字。他们从 1985 年开始,使用了一组相当“正常”的浮点例程,并添加了一些缩放整数假浮点,从那时起他们就一直在调整这些东西并添加特殊情况。该应用程序过去确实存在大多数与其他人相同的“明显”错误,只是它大多在很久以前就存在了。 90 年代初,当我为他们提供技术支持时,我自己提交了一些文件。
Excel rounds numbers like this "correctly" by doing WORK. They started in 1985, with a fairly "normal" set of floating-point routines, and added some scaled-integer fake floating point, and they've been tuning those things and adding special cases ever since. The app DID used to have most of the same "obvious" bugs that everybody else did, it's just that it mostly had them a long time ago. I filed a couple myself, back when I was doing tech support for them in the early 90s.
您需要的是:
如何实现(只是四舍五入最后一位数字的概述)
:
What you NEED is this :
How it can be implemented (just a overview for rounding last digit)
:
正如以 10 为基数的数字在转换为以 2 为基数时必须进行舍入一样,在将数字从 2 基数转换为以 10 为基数时也可以对其进行舍入。一旦数字具有以 10 为基数的表示形式,就可以通过查看要舍入的数字右侧的数字以直接的方式再次舍入。
虽然上述断言没有任何问题,但还有一个更务实的解决方案。问题是二进制表示试图尽可能接近十进制数,即使该二进制小于十进制。误差量在真实值的 [-0.5,0.5] 最低有效位 (LSB) 范围内。出于舍入目的,您希望它在 [0,1] LSB 内,以便误差始终为正,但如果不更改浮点数学的所有规则,这是不可能的。
您可以做的一件事是向该值添加 1 LSB,因此误差在真实值的 [0.5,1.5] LSB 范围内。总体而言,该结果不太准确,但准确程度也非常小。当值被四舍五入以十进制数表示时,它更有可能四舍五入为正确的十进制数,因为误差始终为正。
要在舍入之前向值添加 1 LSB,请参阅 这个问题。例如,在 Visual Studio C++ 2010 中,过程为:
Just as base-10 numbers must be rounded as they are converted to base-2, it is possible to round a number as it is converted from base-2 to base-10. Once the number has a base-10 representation it can be rounded again in a straightforward manner by looking at the digit to the right of the one you wish to round.
While there's nothing wrong with the above assertion, there's a much more pragmatic solution. The problem is that the binary representation tries to get as close as possible to the decimal number, even if that binary is less than the decimal. The amount of error is within [-0.5,0.5] least significant bits (LSB) of the true value. For rounding purposes you'd rather it be within [0,1] LSB so that the error is always positive, but that's not possible without changing all the rules of floating point math.
The one thing you can do is add 1 LSB to the value, so the error is within [0.5,1.5] LSB of the true value. This is less accurate overall, but only by a very tiny amount; when the value is rounded for representation as a decimal number it is much more likely to be rounded to a proper decimal number because the error is always positive.
To add 1 LSB to the value before rounding it, see the answers to this question. For example in Visual Studio C++ 2010 the procedure would be:
有很多方法可以使用统计、数值算法来优化浮点值的结果。
最简单的方法可能是在精度范围内搜索重复的 9 或 0。如果有的话,也许那些 9 是多余的,只需将它们四舍五入即可。但这在很多情况下可能行不通。下面是一个具有 6 位精度的
float
示例:Excel 始终将输入范围限制为 15 位数字,并将输出四舍五入到最大 15 位数字,因此这可能是 Excel 使用的方式之一
或者您可以 <包括精度和数字。每一步之后,根据操作数的精度调整精度。例如,
由于两个数字都在 double 的 16-17 位精度范围内,因此它们的和将精确到其中较大的一个,即 5 位。类似地,3+5<3+5。 16,所以他们的乘积将精确到 8 位十进制数
但是
4.1341677841 * 2.251457145
只会采用 double 的精度,因为实际结果超过 double 的精度另一种有效的算法是 Grisu 但我没有没有机会去尝试。
事实上,我认为 Excel 必须结合许多不同的方法才能达到最佳结果
There are many ways to optimize the result of a floating-point value using statistical, numerical... algorithms
The easiest one is probably searching for repetitive 9s or 0s in the range of precision. If there are any, maybe those 9s are redundant, just round them up. But this may not work in many cases. Here's an example for a
float
with 6 digits of precision:Excel always limits the input range to 15 digits and rounds the output to maximum 15 digits so this might be one of the way Excel uses
Or you can include the precision along with the number. After each step, adjust the accuracy depend on the precision of operands. For example
Since both number are inside the double's 16-17 digits precision range, their sum will be accurate to the larger of them, which is 5 digits. Similarly, 3+5 < 16, so their product will be precise to 8 decimal numbers
But
4.1341677841 * 2.251457145
will only take double's accuracy because the real result exceed double's precisionAnother efficient algorithm is Grisu but I haven't had an opportunity to try.
In fact I think Excel must combine many different methods to achieve the best result of all
正如 mjfgates 所说,Excel 付出了很大的努力才能做到这一点“正确”。当您尝试重新实现这一点时,要做的第一件事就是定义“正确”的含义。明显的解决方案:
实现有理算术
缓慢但可靠。
实施一系列启发式方法
快速但很难做到正确(想想“多年的错误报告”)。
这实际上取决于您的应用程序。
As mjfgates says, Excel does hard work to get this "right". The first thing to do when you try to reimplement this, is define what you mean by "right". Obvious solutions:
implement rational arithmetic
Slow but reliable.
implement a bunch of heuristics
Fast but tricky to get right (think "years of bug reports").
It really depends on your application.
大多数十进制分数无法用二进制精确表示。
一种解决方案是使用 BCD。它很旧了。但是,这也是经过验证的。我们每天都有很多其他的旧想法(比如用 0 代表什么都没有……)。
另一种技术使用输入/输出缩放。这样做的优点是几乎所有数学都是整数数学。
Most decimal fractions can't be accurately represented in binary.
One solution is to use BCD. It's old. But, it's also tried and true. We have a lot of other old ideas that we use every day (like using a 0 to represent nothing...).
Another technique uses scaling upon input/output. This has the advantage of nearly all math being integer math.