我一直在尝试让 awk 做一些简单的算术,其中涉及将一些值从一行传送到下一行。
这是一个最小的示例对,用于比较。第一个示例是预期行为,因为 99.16 - 20.85 = 78.31
$ echo -e "0,99.16\n20.85,78.31" | awk -F, '{
if (NR != 1 && (prior_tot - $1) != $2) {
print "Arithmetic fail..." $0
} else {
print "OK"
};
prior_tot = $2
}'
退货
OK
OK
第二个例子不是预期的行为,因为 99.15 - 20.85 = 78.30
$ echo -e "0,99.15\n20.85,78.30" | awk -F, '{
if (NR != 1 && (prior_tot - $1) != $2) {
print "Arithmetic fail..." $0
} else {
print "OK"
};
prior_tot = $2
}'
退货
OK
Arithmetic fail...20.85,78.30
有人能解释一下这是怎么回事吗?
答案1
浮点数 99.15、28.85 和 78.30 没有精确的 IEEE 754 二进制表示形式。您可以使用执行相同计算的 C 程序来查看这一点:
#include <stdio.h>
int
main(int ac, char **av)
{
float a = 99.15;
float b = 20.85;
float c;
printf("a = %.7f\n", a);
printf("b = %.7f\n", b);
c = a - b;
printf("c = %.7f\n", c);
return 0;
}
我通过 x86 和 x86_64 机器得到这些答案可能是因为它们都这样做IEEE 754浮点数学:
a = 99.1500015 b = 20.8500004 c = 78.3000031
发生的情况是这样的:浮点数用符号位(正或负)、多个位和一个指数表示。并非每个有理数(即本文中的“浮点数”)都可以用 IEEE 754 格式精确表示。因此,硬件尽可能接近。不幸的是,在您的测试用例中,硬件无法获得这 3 个值中任何一个的精确表示。即使您使用double
而不是float
,它也不会,这awk
可能会。
这是一个进一步解释具有精确二进制表示的浮点数的间距。
您可能会发现一些值通过了您的测试,而其他值则未通过。还有很多没有的。
通常人们通过这样做来解决浮点问题:
if (abs(c) <= epsilon) {
// We'll call it equal
} else {
// Not equal
}
在 . 中做到这一点要困难得多awk
。如果您使用货币单位和两位有效数字的子单位(例如美元和美分)进行货币计算,则应该以子单位(美国为美分)进行所有计算。不要使用浮点进行货币计算。你只会发现自己后悔这个决定。
答案2
您遇到了浮点算术问题。
$ awk 'BEGIN { printf "%.17f\n", 99.15-20.85 }'
78.30000000000001137
http://floating-point-gui.de/也许能够帮助您解决问题 - 它试图解释什么是浮点,为什么会发生这样的算术错误,以及如何避免程序中的此类问题。
答案3
您可以通过数字格式来避免此类错误:
awk -F, '{
if (NR != 1 && sprintf(CONVFMT,prior_tot-$1) != $2)
{print "Arithmetic fail..." $0}
else
{print "OK"}
prior_tot = $2}'