Đây là những gì đang diễn ra trong nhị phân. Như chúng ta biết, một số giá trị dấu phẩy động không thể được biểu diễn chính xác dưới dạng nhị phân, ngay cả khi chúng có thể được biểu diễn chính xác bằng số thập phân. 3 con số này chỉ là ví dụ về thực tế đó.
Với chương trình này, tôi đưa ra các biểu diễn thập lục phân của mỗi số và kết quả của mỗi phép cộng.
public class Main{
public static void main(String args[]) {
double x = 23.53; // Inexact representation
double y = 5.88; // Inexact representation
double z = 17.64; // Inexact representation
double s = 47.05; // What math tells us the sum should be; still inexact
printValueAndInHex(x);
printValueAndInHex(y);
printValueAndInHex(z);
printValueAndInHex(s);
System.out.println("--------");
double t1 = x + y;
printValueAndInHex(t1);
t1 = t1 + z;
printValueAndInHex(t1);
System.out.println("--------");
double t2 = x + z;
printValueAndInHex(t2);
t2 = t2 + y;
printValueAndInHex(t2);
}
private static void printValueAndInHex(double d)
{
System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
}
}
Các printValueAndInHex
phương pháp chỉ là một helper hex-in.
Đầu ra như sau:
403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004
4 số đầu tiên là x
, y
, z
, và s
's đại diện thập lục phân. Trong biểu diễn dấu phẩy động của IEEE, các bit 2-12 biểu thị số mũ nhị phân , nghĩa là tỷ lệ của số. (Các bit đầu tiên là bit dấu, và các bit còn lại cho mantissa .) Số mũ đại diện thực sự là số nhị phân trừ đi 1023.
Số mũ của 4 số đầu tiên được trích xuất:
sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Bộ bổ sung đầu tiên
Số thứ hai ( y
) có độ lớn nhỏ hơn. Khi thêm hai số này để có được x + y
, 2 bit cuối của số thứ hai ( 01
) được dịch chuyển ra khỏi phạm vi và không tính vào phép tính.
Bổ sung thứ hai thêm x + y
và z
thêm hai số có cùng tỷ lệ.
Bộ bổ sung thứ hai
Ở đây, x + z
xảy ra đầu tiên. Chúng có cùng tỷ lệ, nhưng chúng mang lại một con số cao hơn về tỷ lệ:
404 => 0|100 0000 0100| => 1028 - 1023 = 5
Bổ sung thứ hai thêm x + z
và y
, và bây giờ 3 bit được loại bỏ y
để thêm các số ( 101
). Ở đây, phải có một vòng lên trên, vì kết quả là số dấu phẩy động tiếp theo tăng lên: 4047866666666666
cho tập hợp bổ sung đầu tiên so với4047866666666667
tập hợp bổ sung thứ hai. Lỗi đó là đủ đáng kể để hiển thị trong bản in của tổng số.
Tóm lại, hãy cẩn thận khi thực hiện các phép toán trên các số của IEEE. Một số đại diện là không chính xác, và chúng thậm chí còn trở nên không chính xác hơn khi quy mô khác nhau. Thêm và trừ các số có tỷ lệ tương tự nếu bạn có thể.
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
). Do đó, có: hãy cảnh giác khi chọn thứ tự tổng và các hoạt động khác. Một số ngôn ngữ cung cấp tích hợp để thực hiện các khoản tiền "có độ chính xác cao" (ví dụ: pythonmath.fsum
), vì vậy bạn có thể cân nhắc sử dụng các hàm này thay vì thuật toán tổng ngây thơ.