【问题标题】:discrepancies in rounding off in CC四舍五入的差异
【发布时间】:2022-01-13 01:20:43
【问题描述】:

我遇到了一个问题,当我打印一个双精度值时,它会不一致地向上或向下舍入。我使用的格式:

printf("%.2lf", double);

这可能是什么问题?谢谢!

【问题讨论】:

  • 示例就像当值是 1203.505000 时它变成 1203.50 但在其他时候它可能变成 1203.51
  • 该分数可能无法以二进制浮点数精确表示。所以它实际上类似于1203.505000000001(向上取整)或1203.50499999999(向下取整)。浮点运算会累积类似的舍入误差。
  • 如果这是个问题,您可能一开始就不应该使用浮点数。如果这是钱,请使用整数乘以 100。
  • 分数精确到小数点后第三位,我需要使用双精度
  • 请发一个minimal reproducible example,证明你用相同的值得到不同的结果。

标签: c floating-point rounding


【解决方案1】:

源代码或输入中的任何十进制数字都将转换为 C 实现用于double 的二进制格式。这会将值更改为远离 xxx.xx5 值。然后使用%.2lf 打印时,舍入的是更改后的值,而不是原始值。仅使用 double 中的值无法避免这种情况。

借助其他信息,例如原始数字具有多少个十进制数字的已知限制或它们可能采用的值,有可能重建原始值并对其进行四舍五入。但是,问题中不存在此类信息。

【讨论】:

    【解决方案2】:

    fraction is exact at the 3rd decimal point

    由于double 使用二进制内部表示,大多数小数如 0.1、0.01、0.001 无法准确表示。而不是x.xx5 的确切代码值,而是使用double near,可能略低或略高。

    要恢复 精确到小数点后第三位,请将 double 缩放 1000.0 并四舍五入到最接近的整数。

    double d1000 = round(d * 1000.0);
    

    要打印到小数点后 2 位,请将 d1000 舍入到最接近的 10 位,舍入然后除以 100.0

    double d100 = round(d1000 / 10.0) / 100.0;
    printf("%.2lf", double);
    

    实际上,代码通过使用double rounding 来恢复。


    例子:

    #include <stdio.h>
    #include <math.h>
    
    void prt(double d) {
      double d1000 = round(d * 1000.0);
      double d100 = round(d1000 / 10.0) / 100.0;
      printf("%.16e %.2lf\n", d, d100);
    }
    
    void prt3(double d) {
      prt(nextafter(d, 0));
      prt(d);
      prt(nextafter(d, d*2));
    }
    
    int main() {
      prt3(1203.505000);
      prt3(1203.705000);
    }
    

    输出

    1.2035049999999999e+03 1203.51
    1.2035050000000001e+03 1203.51
    1.2035050000000003e+03 1203.51
    1.2037049999999997e+03 1203.71
    1.2037049999999999e+03 1203.71
    1.2037050000000002e+03 1203.71
    

    【讨论】:

      【解决方案3】:

      在你写的评论中

      分数精确到小数点后第三位

      您的原始数字可能是,但是一旦将其转换为内部使用二进制小数的double,就没有“第三个小数点”之类的东西,实际上像 0.001 和 0.005 这样的十进制数字根本无法表示没错。

      以下是一些示例数字,它们如何表示为 double,以及它们如何打印为 "%.2lf,即转换回十进制并四舍五入到两位。

      (请注意,当我说“它们如何表示为 double”时,我真正向您展示的是内部二进制表示再次转换回十进制。查看 也很有趣实际的内部表示,二进制或十六进制,但更难有意义地显示,并且与今天的问题无关,所以我不会打扰。)

      input value internal double value printed "%.2lf"
      1203.480 1203.4800000000000181899 1203.48
      1203.485 1203.4849999999998999556 1203.48
      1203.490 1203.4900000000000090949 1203.49
      1203.495 1203.4949999999998908606 1203.49
      1203.500 1203.5000000000000000000 1203.50
      1203.505 1203.5050000000001091394 1203.51
      1203.510 1203.5099999999999909051 1203.51
      1203.515 1203.5150000000001000444 1203.52
      1203.520 1203.5199999999999818101 1203.52
      1203.525 1203.5250000000000909495 1203.53
      1203.530 1203.5299999999999727152 1203.53

      您可以看到,有时 .xx5 表示为 .xx50000000001 并按预期向上取整,但有时它表示为 .xx49999999999 并向下取整。

      您问“这里可能存在什么问题”,这就是我所解释的。其他答案包含有关如何调整代码以解决该问题的建议。

      【讨论】:

        猜你喜欢
        • 2019-07-02
        • 2017-11-26
        • 1970-01-01
        • 1970-01-01
        • 2023-01-12
        • 2012-08-04
        • 2023-03-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多