【问题标题】:Rounding double values in C++ like MS Excel does it像 MS Excel 一样在 C++ 中舍入双精度值
【发布时间】:2012-01-06 12:59:21
【问题描述】:

我搜索了整个网络,但找不到解决问题的方法。我只是想要一个像 MS Excel 那样对 double 值进行舍入的函数。这是我的代码:

#include <iostream>
#include "math.h"

using namespace std;

double Round(double value, int precision) {
    return floor(((value * pow(10.0, precision)) + 0.5)) / pow(10.0, precision);
}

int main(int argc, char *argv[]) {
    /* The way MS Excel does it:
        1.27815 1.27840 ->  1.27828
        1.27813 1.27840 ->  1.27827
        1.27819 1.27843 ->  1.27831
        1.27999 1.28024 ->  1.28012
        1.27839 1.27866 ->  1.27853
    */
    cout << Round((1.27815 + 1.27840)/2, 5) << "\n"; // *
    cout << Round((1.27813 + 1.27840)/2, 5) << "\n";
    cout << Round((1.27819 + 1.27843)/2, 5) << "\n";
    cout << Round((1.27999 + 1.28024)/2, 5) << "\n"; // *
    cout << Round((1.27839 + 1.27866)/2, 5) << "\n"; // *

    if(Round((1.27815 + 1.27840)/2, 5) == 1.27828) {
                      cout << "Hurray...\n";
    }
    system("PAUSE");
    return EXIT_SUCCESS;
}

我在 stackoverflow 找到了这个函数,答案说它像内置的 excel 舍入例程一样工作,但事实并非如此。你能告诉我我错过了什么吗?

【问题讨论】:

  • 你的条件是虚假的,你一般不能像那样天真地比较浮点值。
  • 您真的需要这样的舍入值,还是只想打印格式化为给定小数位数的值?
  • 我确实需要它进行进一步处理。

标签: c++ excel double rounding


【解决方案1】:

从某种意义上说,你所要求的是不可能的:

大多数常见平台上的浮点值没有“小数位数”的概念。像 2.3 或 8.71 这样的数字根本无法精确表示。因此,要求任何将返回具有给定非零小数位数的浮点值的函数是没有意义的——这样的数字根本不存在。

您可以对浮点类型做的唯一事情是计算最接近的可表示近似值,然后以所需的精度打印结果,这将为您提供所需数字的文本形式。要计算表示,您可以这样做:

double round(double x, int n)
{
    int e;
    double d;

    std::frexp(x, &e);

    if (e >= 0) return x; // number is an integer, nothing to do

    double const f = std::pow(10.0, n);
    std::modf(x * f, &d);                 // d == integral part of 10^n * x

    return d / f;
}

(您也可以使用modf 而不是frexp 来确定@​​987654324@ 是否已经是一个整数。您还应该检查n 是否为非负数,或者以其他方式定义负“精度”的语义。 )


除了使用 floating 点类型之外,您还可以执行 fixed 点算术。也就是说,您将所有内容存储为 整数,但您将它们视为单位,例如 1/1000。然后你可以打印如下这样的数字:

 std::cout << n / 1000 << "." << n % 1000;

加法按预期工作,但您必须编写自己的乘法函数。

【讨论】:

    【解决方案2】:

    要比较双精度值,您必须指定一个比较范围,其中的结果可以被认为是“安全的”。您可以为此使用宏。

    这是您可以使用的一个示例:

    #define COMPARE( A, B, PRECISION ) ( ( A >= B - PRECISION ) && ( A <= B + PRECISION ) ) 
    
    int main()
    {
      double a = 12.34567;
      bool equal = COMPARE( a, 12.34567F, 0.0002 );
    
      equal = COMPARE( a, 15.34567F, 0.0002 );
      return 0;
    }
    

    【讨论】:

      【解决方案3】:

      谢谢大家的回答!在考虑了可能的解决方案后,我将代码中的原始 Round() 函数更改为将值添加 0.6 而不是 0.5。

      值“127827.5”(我明白这不是一个精确的表示!)变为“127828.1”,最后通过 floor() 并将其除为“1.27828”(或更像 1.2782800..001)。使用 Renan Greinert 建议的 COMPARE 并正确选择精度,我现在可以安全地比较这些值。

      这是最终版本:

      #include <iostream>
      #include "math.h"
      #define COMPARE(A, B, PRECISION) ((A >= B-PRECISION) && (A <= B+PRECISION))
      
      using namespace std;
      
      double Round(double value, int precision) {
          return floor(value * pow(10.0, precision) + 0.6) / pow(10.0, precision);
      }
      int main(int argc, char *argv[]) {
          /* The way MS Excel does it:
              1.27815 1.27840 // 1.27828
              1.27813 1.27840 ->  1.27827
              1.27819 1.27843 ->  1.27831
              1.27999 1.28024 ->  1.28012
              1.27839 1.27866 ->  1.27853
          */
          cout << Round((1.27815 + 1.27840)/2, 5) << "\n"; 
          cout << Round((1.27813 + 1.27840)/2, 5) << "\n";
          cout << Round((1.27819 + 1.27843)/2, 5) << "\n";
          cout << Round((1.27999 + 1.28024)/2, 5) << "\n";
          cout << Round((1.27839 + 1.27866)/2, 5) << "\n";
      
          //Comparing the rounded value against a fixed one
          if(COMPARE(Round((1.27815 + 1.27840)/2, 5), 1.27828, 0.000001)) {
             cout << "Hurray!\n";
          }
          //Comparing two rounded values
          if(COMPARE(Round((1.27815 + 1.27840)/2, 5), Round((1.27814 + 1.27841)/2, 5), 0.000001)) {
             cout << "Hurray!\n";
          }    
      
          system("PAUSE");
          return EXIT_SUCCESS;
      }
      

      我通过对一百个双精度值进行四舍五入来测试它,然后将结果与 Excel 给出的结果进行比较。他们都是一样的。

      【讨论】:

        【解决方案4】:

        恐怕答案是圆不能施魔法。
        由于 1.27828 不能完全表示为双精度数,因此您无法将某些双精度数与 1.27828 进行比较并希望它匹配。

        【讨论】:

        • 那我该怎么办?转换成字符串?
        • 如果您需要它进行进一步处理,并且您绝对必须具有这个精确的精度,我是否建议在进行处理之前乘以 100000(和舍入),然后在显示结束之前除以 100000结果。
        【解决方案5】:

        你需要在没有小数部分的情况下进行数学运算,才能得到那个数字......所以像这样。

        double dPow = pow(10.0, 5.0);
        
            double a = 1.27815;
            double b = 1.27840;
        
        
            double a2 = 1.27815 * dPow;
            double b2 = 1.27840 * dPow;
        
            double c = (a2 + b2) / 2 + 0.5;
        

        使用你的函数...

        double c = (Round(a) + Round(b)) / 2 + 0.5;
        

        【讨论】:

          猜你喜欢
          • 2011-01-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-05-31
          • 1970-01-01
          • 2011-01-22
          • 1970-01-01
          相关资源
          最近更新 更多