unj*_*nj2 22 c++ math floating-point excel rounding
例如,这篇博客说0.005并不完全是0.005,但是舍入这个数字会产生正确的结果.
我在C++中尝试了各种舍入,但在将数字舍入到某些小数位时失败了.例如,Round(x,y)将x舍入为y的倍数.所以回合(37.785,0.01)应该给你37.79而不是37.78.
我正在重新打开这个问题,向社区寻求帮助.问题在于浮点数的不精确性(37,785表示为37.78499999999).
问题是Excel如何解决这个问题?
Dav*_*men 20
"回合(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)
Run Code Online (Sandbox Code Playgroud)
两个单元格将显示相同的值,37.79.关于37785/1000是否应该以37位置位或37.79计算两位数的准确性存在合理的争论.如何处理这些角落案件有点武断,并没有达成共识的答案.微软内部甚至没有达成共识:" 由于历史原因,Round()函数并没有以不变的方式在不同的Microsoft产品中实现. "(http://support.microsoft.com/kb/196652)无限精密机器,微软的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将这个精确值向上ROUND()舍入而不是向下舍入是确定Excel 函数如何工作的关键:它是对称算术舍入的变体.它基于与角点情况的浮点表示的比较而舍入.
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;
}
Run Code Online (Sandbox Code Playgroud)
我不知道Excel是如何做到的,但是很好地打印浮点数是一个难题:http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems -你甚至不知道你有/