当我对某些记录集执行求和时,我得到了奇怪的答案。
在一种情况下我没有使用 %d 而在下一种情况下我使用的是 %d

sum of using %d 的第一个表达式

 awk -F"|" '(NR > 0 && NR < 36) {sum +=$150} END {printf ("%d\n",sum)}' muar.txt
-|33

没有 %d
 awk -F"|" '(NR > 0 && NR < 36) {sum +=$150} END {printf ("\n"sum)}' muar.txt
-|34

为什么它从 34 四舍五入到 33

只是为了添加更多信息,直到第 34 行我得到的总和为 33.03,第 35 行的值为 0.97,所以实际上它应该是 34 而不是 33

根据测试评论的其他详细信息 - 您可以创建一个文件 let's a.txt
只有一个字段。第一个值是空白第二个是 1.95 然后 18 次 097 连续,然后 0.98 然后 6 次 0.97 然后 0.98 然后 3 次 0.97 然后 0.98 2 次然后 2 次 0.97

或者您可以连续 1.95 - 1 次、0.97 - 29 次和 0.98 4 次低于其他

最佳答案

你的问题的答案有两个:

  • 有一个数字问题
  • awk 进行一些内部转换

  • 你的例子之一是:1.95 + 29*0.97 + 4*0.98。我们都同意这个值的总和正好是 34。下面的小`awk 程序以两种不同的方式进行计算,从而产生显着的结果:
    awk 'BEGIN{sum1=1.95 + 29*0.97 + 4*0.98
               sum2=1.95;
               for(i=1;i<=29;i++){sum2+=0.97};
               for(i=1;i<=4;i++) {sum2+=0.98};
    
               printf "full precision     : %25.16f%25.16f\n",sum1,sum2
               printf "integer conversion : %25d%25d\n"      ,sum1,sum2
               printf "string conversion  : "sum1" "sum2"\n"
    }'
    

    这导致以下输出(第一列 sum1 第二列 sum2
    full precision     :       34.0000000000000000      33.9999999999999787
    integer conversion :                        34                       33
    string conversion  : 34 34
    

    为什么两次求和结果不同:

    本质上, 1.950.970.98 3 个数字不能用二进制格式表示。出现一个近似值,将它们表示为
    1.95 ~ 1.94999999999999995559107901499...
    0.97 ~ 0.96999999999999997335464740899...
    0.98 ~ 0.97999999999999998223643160599...
    

    当按照 sum2 对它们求和时,33 个加法的错误会增加并导致最终结果:
    sum2 = 33.99999999999997868371792719699...
    
    sum1 的误差比 sum2 小得多,因为我们只进行了 2 次乘法和 2 次加法。事实上,错误蒸发为正确的结果(即错误更小 10^-17 ):
       1.95 ~  1.94999999999999995559107901499...
    29*0.97 ~ 28.12999999999999900524016993586...
     4*0.98 ~  3.91999999999999992894572642399...
       sum1 ~ 34.00000000000000000000000000000...
    

    上面的详细理解,我引用必读文章What Every Computer Scientist Should Know About Floating-Point Arithmetic

    打印语句发生了什么?
    awk 本质上是在做内部转换:
  • printf "%d" 请求一个整数,但它是一个浮点数。 awk 正在接收 sum2 并通过删除数字的小数部分将其转换为整数,或者您可以想象它通过 int() 馈送它,因此 33.99999... 被转换为 33
  • printf ""sum2 ,这是从浮点数到字符串的转换。本质上,通过将字符串连接到数字,必须将数字转换为字符串。如果数字是纯整数,它只会将其转换为纯整数。然而,sum2 是一个浮点数。
    sum2 到字符串的转换在内部使用 sprintf(CONVFMT,sum2) 完成,其中 CONVFMT 是一个 awk 内置变量,它设置为 %.6g 。因此 sum2 默认四舍五入以表示最多 6 位十进制数字。因此 ""sum2 -> "34"

  • 我们可以改进 sum2 :

    是的! sum2 只不过是我们要添加的数字序列的表示。首先搜索所有常用术语并像 sum1 中所做的那样使用乘法是不切实际的。使用 Kahan Summation 可以实现改进。它背后的想法是跟踪代表您丢失的数字的补偿条款。

    下面的程序演示了它:
    awk 'BEGIN{sum2=1.95;
               for(i=1;i<=29;i++){sum2+=0.97};
               for(i=1;i<=4;i++) {sum2+=0.98};
               sum3=1.95; c=0
               for(i=1;i<=29;i++) { y = 0.97 - c; t = sum3 + y; c = (t - sum3) - y; sum3 = t }
               for(i=1;i<=4;i++)  { y = 0.98 - c; t = sum3 + y; c = (t - sum3) - y; sum3 = t }
    
               printf "full precision     : %25.16f%25.16f\n",sum2,sum3
               printf "integer conversion : %25d%25d\n"      ,sum2,sum3
               printf "string conversion  : "sum2" "sum3"\n"
    }'
    

    这导致以下输出(第一列 sum2 第二列 sum3)
    full precision     :       33.9999999999999787      34.0000000000000000
    integer conversion :                        33                       34
    string conversion  : 34 34
    

    如果想看sum2sum3的中间步骤和区别可以查看下面的代码。
     awk 'BEGIN{ sum2=sum3=1.95;c=0;
                 for(i=1;i<=29;i++) {
                    sum2+=0.97;
                    y = 0.97 - c; t = sum3 + y; c = (t - sum3) - y; sum3 = t;
                    printf "%25.16f%25.16f%25.16e\n", sum2,sum3,c
                 }
                 for(i=1;i<=4;i++) {
                    sum2+=0.98;
                    y = 0.98 - c; t = sum3 + y; c = (t - sum3) - y; sum3 = t;
                    printf "%25.16f%25.16f%25.16e\n", sum2,sum3,c
                 }
          }'
    

    关于awk - 使用 %d 在 Awk 程序中给出奇怪的舍入值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48808474/

    10-09 18:32