例如,this blog说 0.005 不完全是 0.005,但将该数字四舍五入会产生正确的结果。
我在 C++ 中尝试了各种四舍五入,但在将数字四舍五入到某些小数位时都失败了。例如,Round(x,y) 将 x 舍入为 y 的倍数。所以 Round(37.785,0.01) 应该给你 37.79 而不是 37.78。
我重新提出这个问题是为了向社区寻求帮助。问题在于 float 的不精确(37,785 表示为 37.78499999999)。
问题是 Excel 如何解决这个问题?
round() for float in C++ 中的解决方案对于上述问题是不正确的。
最佳答案
“Round(37.785,0.01) 应该给你 37.79 而不是 37.78。”
首先,没有一致认为 37.79 而不是 37.78 是这里的“正确”答案?决胜局总是有点艰难。虽然在平局的情况下总是四舍五入是一种广泛使用的方法,但它肯定不是唯一的方法。
其次,这不是打破平局的情况。 IEEE binary64 浮点格式中的数值是 37.784999999999997(大约)。除了人类输入 37.785 的值并碰巧将其转换为浮点表示之外,还有很多方法可以获得 37.784999999999997 的值。在大多数情况下,正确答案是 37.78 而不是 37.79。
附录
考虑以下 Excel 公式:
=ROUND(37785/1000,2)
=ROUND(19810222/2^19+21474836/2^47,2)
两个单元格将显示相同的值 37.79。关于 37785/1000 是否应该以两位精度四舍五入到 37.78 或 37.79 存在合理的争论。如何处理这些极端情况有点武断,也没有一致的答案。微软内部甚至没有一个一致的答案:“由于历史原因,Round() 函数在不同的微软产品中没有以一致的方式实现。”(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++中的算法:
#include <cmath> // std::floor
// Compute 10 to some positive integral power.
// Dealing with overflow (exponent > 308) is an exercise left to the reader.
double pow10 (unsigned int exponent) {
double result = 1.0;
double base = 10.0;
while (exponent > 0) {
if ((exponent & 1) != 0) result *= base;
exponent >>= 1;
base *= base;
}
return result;
}
// Round the same way Excel does.
// Dealing with nonsense such as nplaces=400 is an exercise left to the reader.
double excel_round (double x, int nplaces) {
bool is_neg = false;
// Excel uses symmetric arithmetic round: Round away from zero.
// The algorithm will be easier if we only deal with positive numbers.
if (x < 0.0) {
is_neg = true;
x = -x;
}
// Construct the nearest rounded values and the nasty corner case.
// Note: We really do not want an optimizing compiler to put the corner
// case in an extended double precision register. Hence the volatile.
double round_down, round_up;
volatile double corner_case;
if (nplaces < 0) {
double scale = pow10 (-nplaces);
round_down = std::floor (x * scale);
corner_case = (round_down + 0.5) / scale;
round_up = (round_down + 1.0) / scale;
round_down /= scale;
}
else {
double scale = pow10 (nplaces);
round_down = std::floor (x / scale);
corner_case = (round_down + 0.5) * scale;
round_up = (round_down + 1.0) * scale;
round_down *= scale;
}
// Round by comparing to the corner case.
x = (x < corner_case) ? round_down : round_up;
// Correct the sign if needed.
if (is_neg) x = -x;
return x;
}
关于c++ - 即使 float 不精确,Excel 如何成功地将它们舍入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6930786/