Kết quả đáng ngạc nhiên khi sử dụng số học dấu phẩy động awk


7

Tôi đã cố gắng để awk thực hiện một số số học tầm thường, bao gồm việc mang một số giá trị từ dòng này sang dòng tiếp theo.

Dưới đây là một cặp ví dụ tối thiểu, để so sánh. Ví dụ đầu tiên là hành vi dự kiến, vì 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
}'

Trả về

OK
OK

Ví dụ thứ hai không phải là hành vi dự kiến, vì 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
}'

Trả về

OK
Arithmetic fail...20.85,78.30

Bất cứ ai có thể giải thích những gì đang xảy ra ở đây?

Câu trả lời:


7

Các số dấu phẩy động 99,15 và 28,85 và 78,30 không có biểu diễn nhị phân chính xác của IEEE 754. Bạn có thể thấy điều này với một chương trình C thực hiện cùng một phép tính:

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

Tôi nhận được những câu trả lời trên bởi một máy x86 và một máy x86_64 có lẽ bởi vì cả hai đều thực hiện phép toán dấu phẩy động IEEE 754 :

a = 99.1500015 b = 20.8500004 c = 78.3000031

Đây là những gì xảy ra: số dấu phẩy động được biểu diễn bằng một bit dấu (dương hoặc âm), một số bit và số mũ. Không phải mọi số hữu tỷ (là số "dấu phẩy động" trong ngữ cảnh này) có thể được biểu diễn chính xác theo định dạng IEEE 754. Vì vậy, phần cứng càng gần càng tốt. Thật không may, trong trường hợp thử nghiệm của bạn, phần cứng không nhận được đại diện chính xác của bất kỳ 3 giá trị nào. Nó sẽ không ngay cả khi bạn sử dụng doublethay vì float, điều đó awkcó thể làm.

Dưới đây là một lời giải thích thêm về khoảng cách của các số dấu phẩy động có biểu diễn nhị phân chính xác.

Bạn có thể có thể tìm thấy một số giá trị vượt qua bài kiểm tra của bạn và những giá trị khác không. Còn nhiều điều nữa không.

Thông thường mọi người giải quyết một vấn đề dấu phẩy động bằng cách làm một cái gì đó như thế này:

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

Điều đó khó hơn rất nhiều để làm awk. Nếu bạn đang kiếm tiền với các đơn vị tiền tệ và hai chữ số đáng kể của đơn vị phụ (đô la và xu, giả sử), bạn chỉ nên thực hiện tất cả các tính toán trong các đơn vị phụ (xu ở Hoa Kỳ). Không sử dụng dấu phẩy động để tính toán tiền tệ. Bạn sẽ chỉ thấy mình hối hận vì quyết định đó.


1
awk cũng có thể hiển thị các số: awk 'BEGIN{a=99.15;b=20.85;c=78.30;printf("%22.20f %22.20f %22.20f",a,b,c)}' 99.15000000000000568434 20.85000000000000142109 78.29999999999999715783Cũng lưu ý rằng đại diện awk gấp đôi, mã c của bạn được sử dụng đơn (do đó lỗi lớn hơn). Đối với gấp đôi hơn 17 chữ số chính xác là không hợp lý.
Isaac

1
Và có một thực tế là IEEE784 làm tròn số chẵn. Một số chỉ đơn giản là không khớp, bất kể chúng được in bao lâu.
Isaac

7

Bạn đang bị cắn bởi một vấn đề số học dấu phẩy động.

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

http://floating-point-gui.de/ có thể giúp bạn giải tỏa mọi thứ cho bạn - nó cố gắng giải thích điểm nổi là gì và tại sao các lỗi số học như thế này xảy ra và cách tránh các loại vấn đề này trong bạn các chương trình.


5

Bạn có thể tránh những loại sai lầm như vậy bằng cách hình thành các con số:

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

Có vẻ như nó không thửecho -e "0,99.16\n20.85,78.31\n78.31,0\n-99.15,99.15\n20.85,78.30" | awk -F, '{ if (NR != 1 && sprintf(CONVFMT,prior_tot-$1) != $2) {printf("Arithmetic fail...%s %30.30f",$0,prior_tot-$1)} else {print "OK"} prior_tot = $2}'
Isaac
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.