Resultado surpreendente usando aritmética de ponto flutuante awk

7

Eu tenho tentado fazer o awk fazer uma aritmética trivial, que envolve carregar alguns valores de uma linha para a próxima.

Aqui está um par de exemplo mínimo, para comparação. O primeiro exemplo é o comportamento esperado, desde 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
}'

Retorna

OK
OK

O segundo exemplo não é comportamento esperado, já que 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
}'

Retorna

OK
Arithmetic fail...20.85,78.30

Alguém pode explicar o que está acontecendo aqui?

    
por SauceCode 06.04.2015 / 19:16

3 respostas

7

Os números de ponto flutuante 99.15 e 28.85 e 78.30 não possuem representações binárias IEEE 754 exatas. Você pode ver isso com um programa em C que faz o mesmo cálculo:

#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;
}

Eu recebo essas respostas por um x86 e uma máquina x86_64 provavelmente porque ambos fazem IEEE 754 matemática de ponto flutuante:

a = 99.1500015 b = 20,8500004 c = 78,3000031

Veja o que acontece: números de ponto flutuante são representados com um bit de sinal (positivo ou negativo), um número de bits e um expoente. Nem todo número racional (que é o que um número "ponto flutuante" está neste contexto) pode ser representado exatamente no formato IEEE 754. Então, o hardware fica o mais próximo possível. Infelizmente, no seu caso de teste, o hardware não obtém uma representação exata de qualquer um dos 3 valores. Não será nem mesmo se você usar double em vez de float , o que awk provavelmente faz.

Aqui está uma explicação adicional do espaçamento dos números de ponto flutuante que tem representações binárias exatas.

Provavelmente, você pode encontrar alguns valores que passam no teste e outros que não passam. Há muito mais que não.

Normalmente, as pessoas resolvem um problema de ponto flutuante fazendo algo assim:

if (abs(c) <= epsilon) {
    // We'll call it equal
} else {
    // Not equal
}

Isso é muito mais difícil de fazer em awk . Se você está fazendo dinheiro com unidades monetárias e dois dígitos significativos de sub-unidade (dólares e centavos, digamos), você deve realizar todos os cálculos nas sub-unidades (centavos nos EUA). Não use ponto flutuante para fazer cálculos monetários. Você só vai se arrepender dessa decisão.

    
por 06.04.2015 / 19:40
7

Você está sendo mordido por um problema de aritmética de ponto flutuante.

$ awk 'BEGIN { printf "%.17f\n", 99.15-20.85 }'
78.30000000000001137
O

link pode ajudar a esclarecer as coisas para você - ele tenta explicar o que é o ponto flutuante e por que a aritmética erros como esse acontecem e como evitar esses tipos de problemas em seus programas.

    
por 06.04.2015 / 19:31
4

Você pode evitar esse tipo de erro pela formatação de números:

awk -F, '{
    if (NR != 1 && sprintf(CONVFMT,prior_tot-$1) != $2)
        {print "Arithmetic fail..." $0}
    else
        {print "OK"}
    prior_tot = $2}'
    
por 06.04.2015 / 19:49

Tags