c++ - 即使 float 不精确,Excel 如何成功地将它们舍入?

标签 c++ excel math floating-point rounding

例如,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/

相关文章:

c++ - 从 istream_iterator 理解平等

VBA,用于循环效率

C 数学作业

jquery - 将 Excel 公式转换为 JavaScript 格式

python - 如何在 boost python 中公开采用可变参数的 C++ 函数

c++ - 哪些运算符是为作用域枚举自动定义的?

c++ - QT C++ 中的 QStringList

python - 数据帧分组的多个索引

excel - 使用 Application.OnTime 的调度和重复出现问题

php - 统一排列/分布数组项