【问题标题】:float rounding error float to double conversionfloat 舍入错误 float 到 double 转换
【发布时间】:2014-08-22 10:53:32
【问题描述】:

我有一个我现在无法更改的系统,是使用浮点数来存储信息。

我遇到了舍入错误的问题。示例:

std::string floatToStr(float d)
{
    std::stringstream ss;
    ss << std::fixed << std::setprecision(15) << d;
    return ss.str();
}

    float val723 = 0.575f;
    std::cout << floatToStr(val723) << std::endl;

结果是

0.574999988079071

我可以用字符串处理来纠正这个问题。

std::string doubleToStr(double d, int precision)
{
    std::stringstream ss;
    ss << std::fixed << std::setprecision(precision) << d;
    return ss.str();
}

double val945 = (double)0.575f;
std::cout << doubleToStr(atof(doubleToStr(val945, 4).c_str()), 15) << std::endl;

结果是:

0.575000000000000

但这是一个昂贵的解决方案。有没有更好的解决方案可以在实时过程中使用? (实际上我不需要在我的实时代码中使用它,但如果我必须在实时代码中使用它,我会做好准备。)

Edit1:我知道浮点类型可以使用小数点后的 6 或 7 位数字。

【问题讨论】:

  • 如果您需要使用float,您不能期望超过 7 个(左右)十进制数字的精度。如果您需要更多数字,您将不得不忍受使用double(或者可能是一些用户定义的十进制类型,以准确表示您的值)的成本。恐怕没有办法从float 中获得更高的精度。
  • 是否可以选择二进制保存?

标签: c++ floating-point


【解决方案1】:

您可以用您的代码打破它。 PC的工作是二进制的,0.575是小数,一定要近似。

正确的解决方案是将浮点数打印到不超过实际存储的数字 (7),并打印其真实值而不是您认为它应该包含的十进制值。

【讨论】:

    【解决方案2】:

    嗯。你要求小数点后 15 位,这就是它给你的。似乎有点与抱怨相反!

    该值是一个浮点数,这意味着 6 个十进制数字大约是可用精度的限制(7,如果您确信您几乎没有舍入错误)。要求 15 位小数, '.' 之后正在向您显示浮点数的 ls 位的状态,这些位受舍入和表示错误的影响。

    您似乎想要做的是将浮点数的值呈现为双精度,小数点后 15 位,然后您需要将浮点数的双精度版本四舍五入为 6(或者,如果您勇敢)7 十进制 位精度。这与二进制到十进制的转换非常相似,但是您需要准备值的双份副本,以备输出。

    从获取字符串版本的值到 6 位数字并将其转换为双精度值,这不是一百万英里。你可以自己建立哪个更快!

    很遗憾,问题仍然存在。您要求在“.”之后 输入 15 位小数。这将显示大于 10.0 的值的表示错误,并且可能显示介于 1.0 和 10.00 之间的值。例如:如果您的浮点值是(比如说)57.51234f(其实际值约为 57.512340545654297);下面的代码将提供 575124.0 / 10000.0 作为双精度,当输出给出: 57.512300000000003 - 因为您要求的比双精度高大约 1 位数。 (对于 5751.234f,相同的过程给出 5751.229999999999563。)

    我会仔细考虑为什么 '.' 后面有 15 位小数。是必需的,特别是因为数据只有 6-7 位的总精度 - 所以 最多 '.' 后有 6-7 个“好”数字,具体取决于数字的大小。 p>

    FWIW:您可以将数字转换为科学形式,然后直接按摩字符串——这是执行以下代码的另一种方式。


      double pt[] = { 1E0,  1E1,  1E2,  1E3,  1E4,  1E5,  1E6,  1E7,  1E8,  1E9,
                      1E10, 1E11, 1E12, 1E13, 1E14, 1E15, 1E16, 1E17, 1E18, 1E19 } ;
    
      double xx ;
      int de ;
    
      de = 6 - (int)ceilf(log10f(x)) ;   /* where 'x' is the float to be shown */
    
      if ((de > 15) || (de < -18))       /* de > 15 -- no need to round, value too small  */
                                         /* de < 18 -- cannot round, value too big        */
        xx = x ;         /* xx is value to output */
      else
        {
          while (1)
            {
               xx = x ;
    
               if      (de < 0)
                 xx /= pt[-de] ;
               else if (de > 0)
                 xx *= pt[+de] ;
    
               xx = round(xx) ;
    
               if      (xx < pt[5])
                 de += 1 ;
               else if (xx > pt[6])
                 de -= 1 ;
               else
                 break ;
            } ;
    
          if      (de < 0)
            xx *= pt[-de] ;
          else if (de > 0)
            xx /= pt[+de] ;
        } ;
    
      ss << std::fixed << std::setprecision(15) << xx ;
    

    【讨论】:

    • 感谢您的解释。我试过你的代码,它让我知道如何转换它。我想对字符串的代码速度进行一些测试。当小数位为零和正数时,它工作得很好。例如,当我输入 x = 0.293751740273658f;它给了我 0.293752000000000。但是如果我添加一些小数,x = 123.293751740273658f;它给了我 123.293999999999997。负数也有问题。当我输入 x = -0.293751740273658f;它给了我 -0.293751746416092
    • 是的,要处理 -ve 数字,您需要处理符号。是的,正如我所说,根据数字的大小,当您要求 小数点后 15 位时,您将看到双精度的限制。双精度为您​​提供 15 个(近 16 个)十进制数字total 精度(有效数字)——无论小数点在哪里。没有办法解决这个问题。 123.752000000000000 有 18 位十进制数字(有效数字)。问题并不是真正如何做你想做的事,而是为什么你想要它?!
    • 我想使用双精度而不是浮点数,但我的系统(我必须使用旧库)在一个地方使用浮点数。我想更正确地将浮点数转换为双精度。但是正如我写的,我现在不需要,也许我将不得不使用,这个问题正在为将来做准备。
    • 嗯,问题是我们所说的“将浮点数更正确地转换为双精度”的意思。普通转换(例如赋值)准确地保留了浮点数的 24 个 binary 位。我建议通过转换 6 个 significant decimal 数字来转换浮点数,但只保留大约 20 个 binary 数字。但问题不是为什么要加倍,而是为什么要在点后显示 15 位小数?
    • 我不想在点后显示 15 位小数。我只是用 15 编写代码。它可以是 10、13 或 17。这不是问题的重点。问题是如何转换比赋值转换更正确,并且比字符串转换更好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多