为什么 awk 不使总和为零而是一个非常小的数字?

Ook*_*ker 5 awk floating-point

我有这个文件,我想对第一列中的所有数字求和。简单:

awk '{s+=$1;print $1,s}' file
0.1048 -1.2705
0.4196 -0.8509
0.4196 -0.4313
0.2719 -0.1594
0.0797 -0.0797
0.0797 -5.55112e-17   #Notice this line
Run Code Online (Sandbox Code Playgroud)

你看,最后一个应该是 0。我知道那e-17是零,但有时输出正好是 0。如果它不是 0,则输出在e-15to 的范围内e-17,以负号或正号表示。为了解决这个问题,我必须使用绝对值:

awk '{s+=$1;if (sqrt(s^2)<0.01) s=0;print $1,s}' file
Run Code Online (Sandbox Code Playgroud)

你知道为什么会这样吗?

uml*_*ute 11

发生这种情况是因为计算机在处理数字时只有有限的精度。可用精度使用二进制格式来表示数字。

这使得在我们的十进制系统中看似微不足道的数字只能表示为近似值(请参阅有关此的 Wikipedia 条目):例如0.1(如1/10)实际上存储为类似于0.100000001490116119384765625计算机上的东西。

因此,您的所有数字实际上只能通过近似值处理(除非您很幸运并且0.5可以精确表示这样的数字)。

总结所有这些近似数字最终会导致一个错误,即!= 0

  • 我是说*不* IEEE754 32 位浮点数可以准确地表示某些数字(无论这是 unix 还是 w32 或其他)。你总是会得到一个错误(你的错误是“大约 10^-15”,这是第 15 位数字,而不仅仅是第 6 位数字)。你不经常*看到*这个的原因是因为许多程序在输出中做了一些四舍五入。 (5认同)

ter*_*don 5

作为解决此问题的方法,您可以使用专门设计用于处理算术运算的程序,例如bc

$ awk '{printf "%s + ",$1}' file | sed 's/\+ $/\n/' | bc
0
Run Code Online (Sandbox Code Playgroud)

如果(似乎是这种情况)您有固定的小数位数,您可以简单地删除它们以处理整数,然后在末尾再次添加它们:

$ awk '{sub("0.","",$1);s+=$1;}END{print s/10000}' file
0
Run Code Online (Sandbox Code Playgroud)

或者

$ perl -lne 's/0\.//; $s+=$_; END{print $s/10000}' file
0
Run Code Online (Sandbox Code Playgroud)


Sco*_*ott 1

您的问题是 \xe2\x80\x9c为什么会发生这种情况?\xe2\x80\x9d,\n但您隐含的问题(其他人已经解决)是 \xe2\x80\x9c我该如何解决这个问题?\xe2\x80\x9d\ xc2\xa0\n您想出了一种方法,您在评论中提出了该方法:

\n\n
\n

那么如果我乘以1000消掉这个点,我就能得到准确的结果,\xe2\x80\x99t我可以吗?

\n
\n\n

是的。\xc2\xa0 好吧,10000,因为你有四个小数位。\xc2\xa0 考虑一下:\n

\n\n\n\n
awk \'{ s+=$1*10000; print $1, s/10000 }\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

不幸的是,这不起作用\xe2\x80\x99,因为当我们将标记(字符串)解释为十进制数时\xe2\x80\x99\n就已经发生了损坏。\xc2\xa0\n例如,显示输入printf "%.20f\\n"数据0.4157\nis实际上解释为 0.41570000000000001394。\xc2\xa0\n在这种情况下,乘以 10000 得到您所期望的结果:4157。\xc2\xa0\n但是,例如 = 0.597300000000000005311,\n然后乘以0.597310000 得到 5973.0 0000000000090949470。

\n\n

所以我们尝试

\n\n
awk \'{ s+=int($1*10000); print $1, s/10000 }\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

将 \xe2\x80\x9c 应该是 \xe2\x80\x9d 整数的数字(例如 5973.00000000000090949470)\n 转换为相应的整数 (5973)。\xc2\xa0\n但这会失败,因为有时转换错误为负数;\ne.g.,0.7130是 0.71299999999999996714。\xc2\xa0\n并且awk\xe2\x80\x99s函数会截断(向零)\n而不是四舍五入,7129 也是如此。int(expr)int(7129.99999999)

\n\n

因此,当生活给你柠檬时,你就制作柠檬水。\xc2\xa0\n当工具给你截断函数时,你通过添加 0.5 进行舍入。\xc2\xa0\n7129.99999999+0.5\xe2\x89\x887130。 49999999,当然int(7130.49999999)是 7130。\xc2\xa0\n但请记住:向零int()截断,并且您的输入包含负数。\xc2\xa0\n如果要将 \xe2\x80\x937129.99999999 舍入到 \xe2 \x80\x937130,\n需要减去0.5 才能得到 \xe2\x80\x937130.49999999。\xc2\xa0\n所以,

\n\n
awk \'{ s+=int($1*10000+($1>0?0.5:-0.5)); print $1, s/10000 }\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

$1*10000如果is ≤ 0 ,则将 \xe2\x80\x930.5 添加到其中$1

\n