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 형식으로 정확하게 표현될 수 있는 것은 아닙니다. 따라서 하드웨어는 최대한 가깝습니다. 불행하게도 귀하의 테스트 사례에서는 하드웨어가 이 세 가지 값을 정확하게 표현할 수 없습니다. double대신 사용하더라도 그렇지 않을 것입니다. 아마도 그럴 floatawk입니다.

이것은추가 설명정확한 이진 표현을 사용한 부동 소수점 숫자의 간격입니다.

일부 값은 테스트를 통과하지만 다른 값은 실패할 수 있습니다. 그렇지 않은 경우가 많습니다.

일반적으로 사람들은 다음을 수행하여 부동 소수점 문제를 해결합니다.

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}'

관련 정보