kun*_*rav 4 floating-point awk
当我在某些记录集上执行求和时,我得到了奇怪的答案.在一种情况下,我没有使用%d,在下一种情况下我使用的是%d
使用%d之和的第一个表达式
awk -F"|" '(NR > 0 && NR < 36) {sum +=$150} END {printf ("%d\n",sum)}' muar.txt
-|33
Run Code Online (Sandbox Code Playgroud)
没有%d
awk -F"|" '(NR > 0 && NR < 36) {sum +=$150} END {printf ("\n"sum)}' muar.txt
-|34
Run Code Online (Sandbox Code Playgroud)
为什么它从34变为33
只是添加更多信息,直到34行我得到的总和为33.03而第35行的值为0.97所以实际上它应该是34而不是33
根据测试评论的附加细节 - 您可以创建一个文件,让我们的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"
}'
Run Code Online (Sandbox Code Playgroud)
这导致以下输出(第一列sum1
第二列sum2
full precision : 34.0000000000000000 33.9999999999999787
integer conversion : 34 33
string conversion : 34 34
Run Code Online (Sandbox Code Playgroud)
为什么这两个总和有不同的结果:
实质上,3个数字1.95
,0.97
并0.98
不能以二进制格式表示.发生近似表示它们
1.95 ~ 1.94999999999999995559107901499...
0.97 ~ 0.96999999999999997335464740899...
0.98 ~ 0.97999999999999998223643160599...
Run Code Online (Sandbox Code Playgroud)
当按照这样做的方式对它们进行求和时sum2
,33次加法的误差会增加并导致最终结果:
sum2 = 33.99999999999997868371792719699...
Run Code Online (Sandbox Code Playgroud)
误差sum1
远小于sum2
我们只进行2次乘法和2次加法.事实上,错误蒸发到正确的结果(即错误更小10^-17
):
1.95 ~ 1.94999999999999995559107901499...
29*0.97 ~ 28.12999999999999900524016993586...
4*0.98 ~ 3.91999999999999992894572642399...
sum1 ~ 34.00000000000000000000000000000...
Run Code Online (Sandbox Code Playgroud)
为了详细了解上述内容,我参考了每个计算机科学家应该知道的关于浮点运算的强制性文章
打印声明发生了什么?
awk
本质上是做内部转换:
printf "%d"
请求一个整数,但它是一个浮点数.awk
正在接收sum2
并通过删除数字的小数部分将其转换为整数,或者您可以想象它将其转换为Trough int()
因此33.99999...
转换为33
.
printf ""sum2
,这是从float到string的转换.基本上通过将字符串连接到数字,数字必须以字符串形式转换.如果数字是纯整数,它只会将其转换为纯整数.但是,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"
}'
Run Code Online (Sandbox Code Playgroud)
这导致以下输出(第一列sum2第二列sum3)
full precision : 33.9999999999999787 34.0000000000000000
integer conversion : 33 34
string conversion : 34 34
Run Code Online (Sandbox Code Playgroud)
如果您想查看中间步骤和之间的区别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
}
}'
Run Code Online (Sandbox Code Playgroud)