我一直试图让 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
}'
Run Code Online (Sandbox Code Playgroud)
退货
OK
OK
Run Code Online (Sandbox Code Playgroud)
第二个例子不是预期的行为,因为 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
}'
Run Code Online (Sandbox Code Playgroud)
退货
OK
Arithmetic fail...20.85,78.30
Run Code Online (Sandbox Code Playgroud)
任何人都可以解释这里发生了什么吗?
您正被浮点算术问题所困扰。
$ awk 'BEGIN { printf "%.17f\n", 99.15-20.85 }'
78.30000000000001137
Run Code Online (Sandbox Code Playgroud)
http://floating-point-gui.de/可能会帮助你解决问题——它试图解释什么是浮点数,为什么会发生这样的算术错误,以及如何在你的应用程序中避免这类问题程式。
浮点数 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;
}
Run Code Online (Sandbox Code Playgroud)
我通过 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
}
Run Code Online (Sandbox Code Playgroud)
在awk
. 如果您使用货币单位和子单位的两位有效数字(例如美元和美分)进行货币交易,您应该只在子单位(美国为美分)中执行所有计算。不要使用浮点数进行货币计算。你只会发现自己后悔那个决定。
您可以通过数字格式来避免此类错误:
awk -F, '{
if (NR != 1 && sprintf(CONVFMT,prior_tot-$1) != $2)
{print "Arithmetic fail..." $0}
else
{print "OK"}
prior_tot = $2}'
Run Code Online (Sandbox Code Playgroud)