Excel如何成功地舍入浮点数,即使它们不精确?

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如何解决这个问题?

对于上述问题,在C++中浮点数的轮()中的解决方案是不正确的.

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)


Ned*_*der 3

我不知道Excel是如何做到的,但是很好地打印浮点数是一个难题:http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems -你甚至不知道你有/