当我对某些记录集执行求和时,我得到了奇怪的答案。
在一种情况下我没有使用 %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.95
、 0.97
和 0.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
如果想看
sum2
和sum3
的中间步骤和区别可以查看下面的代码。 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/