使用 awk 浮点运算的令人惊讶的结果

使用 awk 浮点运算的令人惊讶的结果

我一直在尝试让 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}'

相关内容