【问题标题】:Multiplying two floats doesn't give exact result将两个浮点数相乘并不能给出准确的结果
【发布时间】:2012-07-08 09:00:47
【问题描述】:

我正在尝试将两个floats 相乘,如下所示:

float number1 = 321.12;
float number2 = 345.34;
float rexsult = number1 * number2;

我想看到的结果是 110895.582,但是当我运行代码时它只给我 110896。大多数时候我都遇到了这个问题。任何计算器都能为我提供所有小数的确切结果。我怎样才能达到这个结果?

编辑:这是 C 代码。我正在使用 XCode iOS 模拟器。

【问题讨论】:

  • 这是 C 还是 Java?它可以来自您所展示的内容。
  • 你应该使用double而不是float
  • 您确定打印时不只是截断它吗?您如何看待获得的价值?
  • 我也用过double,结果一样。
  • 您只是将结果打印为 6 位数字。这是 C++ 中的默认设置,但您可以更改它。例如,在 C++ 中,std::cout<<std::setprecision(16)<<x;

标签: c floating-point


【解决方案1】:

有很多四舍五入。

float a = 321.12; // this number will be rounded
float b = 345.34; // this number will also be rounded
float r = a * b;  // and this number will be rounded too
printf("%.15f\n", r);

经过三个单独的四舍五入后,我得到 110895.578125000000000。

  1. 如果您想要超过 6 个十进制数字的精度,您将必须使用 double 而不是 float。 (请注意,我说的是“十进制数字的价值”,因为你没有得到十进制数字,你得到的是二进制。)就目前而言,1/2 ULP 的错误(完美的最坏情况界限em> 取整结果)约为 0.004。

  2. 如果您想要精确四舍五入的十进制数,则必须使用专门的十进制库来完成此类任务。 double 对科学家来说具有足够的精度,但如果你用钱工作,一切都必须 100% 准确。金钱没有浮点数。

与整数不同,浮点数需要一些实际的工作才能习惯它们的陷阱。参见“What Every Computer Scientist Should Know About Floating-Point Arithmetic”,这是该主题的经典介绍。

编辑:实际上,我不确定代码是否会四舍五入。它可能会四舍五入 次,因为 ab 的常量在存储时可能会先四舍五入为双精度,然后再四舍五入为单精度。但是我对C这部分的规则不是很了解。

【讨论】:

  • “可能会四舍五入” 没错,但是对于大多数十进制数,先四舍五入到double,然后再四舍五入到float 与直接四舍五入到float 相同。当我为了 blog.frama-c.com/index.php?post/2011/11/08/Floating-point-quiz 的目的而在 1 和 2 之间寻找一个看似无辜的十进制数时,我不得不在该点之后使用 14 位数字。我找不到任何只有 13 位的数字。
【解决方案2】:

那样你永远不会得到确切的结果。

首先,number1 ≠ 321.12,因为该值无法在 base-2 系统中精确表示。你需要无限数量的比特。

同样适用于 number2 ≠ 345.34。

因此,您从不精确的值开始。

然后乘积将被四舍五入,因为乘法会使有效位数增加一倍,但如果乘以浮点数,则乘积必须再次存储在 float 中。

您可能希望对数字使用基于 10 的系统。或者,如果您的数字只有 2 位小数,您可以使用整数(在这种情况下 32 位整数就足够了,但您最终可能需要 64 位):

32112 * 34534 = 1108955808。

表示 321.12 * 345.34 = 110895.5808。

【讨论】:

    【解决方案3】:

    由于您使用的是 C,因此您可以使用 "%.xf" 轻松设置精度,其中 x 是所需的精度。

    例如:

    float n1 = 321.12;
    float n2 = 345.34;
    float result = n1 * n2;
    
    printf("%.20f", result);
    

    输出:

    110895.57812500000000000000
    

    但是,请注意float 仅提供六位精度。为了获得更好的精度,请使用double

    【讨论】:

      【解决方案4】:
      • 浮点变量只是近似表示,不是精确表示。并非每个数字都可以“适合”浮点变量。例如,无法将 1/10 (0.1) 放入二进制变量中,就像无法将 1/3 放入十进制一(只能用无穷大的 0.33333 近似)
      • 输出此类变量时,通常会应用许多舍入选项。除非您全部设置,否则您永远无法确定应用了哪些。对于 之前进行舍入

      Printf 也做了一些舍入。考虑http://codepad.org/LLweoeHp

      float t = 0.1f;
      printf("result: %f\n", t);
      --
      result: 0.100000
      

      嗯,看起来不错。为什么?因为 printf 默认为某些精度并四舍五入输出。让我们拨入小数点后50位:http://codepad.org/frUPOvcI

      float t = 0.1f;
      printf("result: %.50f\n", t);
      --
      result: 0.10000000149011611938476562500000000000000000000000
      

      那不一样了,不是吗?在 625 之后,浮点数耗尽了容纳更多数据的容量,这就是我们看到零的原因。 double 可以容纳更多数字,但二进制中的 0.1 不是有限的。 Double不得不放弃,最终:http://codepad.org/RAd7Yu2r

      double t = 0.1;
      printf("result: %.70f\n", t);
      --
      result: 0.1000000000000000055511151231257827021181583404541015625000000000000000
      

      在您的示例中,仅 321.12 就足以引起麻烦:http://codepad.org/cgw3vUKn

      float t = 321.12f;
      printf("and the result is: %.50f\n", t);
      result: 321.11999511718750000000000000000000000000000000000000
      

      这就是为什么在将浮点值呈现给人类之前必须对其进行四舍五入的原因。

      计算器程序根本不使用浮点数或双精度数。他们实现十进制数字格式。例如:

      struct decimal
      {
           int mantissa; //meaningfull digits
           int exponent; //number of decimal zeroes
      };
      

      需要重新发明所有运算的Ofc:加法、减法、乘法和除法。或者只是寻找一个十进制库。

      【讨论】:

      • 不,1.0 永远不会是 1.0。舍入是以一种非常可预测的方式完成的,任何低于 2^24 的整数每次都将是完全精确的。
      • @DietrichEpp:我写的是 1.0 是虚构的例子。
      • 选择 0.1、1/3 或 100000001 很容易。有很多非虚构的例子。
      • @DietrichEpp 您可以很容易地编辑我的答案。你现在喜欢吗? (1/3 也不能用十进制表示,所以它不是有用的例子)
      猜你喜欢
      • 2014-01-11
      • 2016-04-30
      • 2016-06-02
      • 1970-01-01
      • 1970-01-01
      • 2013-06-05
      • 1970-01-01
      • 2023-03-05
      • 1970-01-01
      相关资源
      最近更新 更多