2010-01-10 24 views
8

Tôi đã trải qua một hướng dẫn Haskell gần đây và nhận thấy hành vi này khi cố gắng một số biểu thức đơn giản Haskell trong tương tác ghci shell:Tại sao ghci nói rằng 1,1 + 1,1 + 1,1> 3,3 là đúng?

Prelude> 1.1 + 1.1 == 2.2 
True 
Prelude> 1.1 + 1.1 + 1.1 == 3.3 
False 
Prelude> 1.1 + 1.1 + 1.1 > 3.3 
True 
Prelude> 1.1 + 1.1 + 1.1 
3.3000000000000003 

Có ai biết tại sao lại như vậy?

+0

Đẹp javascript float - thập phân - nhị phân visualizer: http://babbage.cs.qc.edu/IEEE-754/Decimal.html – Seth

+4

Đây không phải là một vấn đề Haskell cụ thể: điều tương tự sẽ xảy ra trong bất kỳ ngôn ngữ nào sử dụng số dấu phẩy động. Các chi tiết chính xác phụ thuộc vào việc thực hiện điểm nổi, nhưng hầu hết các bộ vi xử lý sử dụng IEEE-754, được chỉ định chặt chẽ để đảm bảo rằng các chương trình hoạt động sai theo cách giống hệt nhau ở mọi nơi. –

Trả lời

36

1.13.3floating point numbers. Các phân số thập phân, chẳng hạn như .1 hoặc .3, không thể biểu diễn chính xác trong một số dấu phẩy động nhị phân. .1 nghĩa là 1/10. Để biểu diễn điều đó theo dạng nhị phân, trong đó mỗi số phân số đại diện cho 1/2 n (1/2, 1/4, 1/8, v.v.), bạn sẽ cần một số lượng vô hạn các chữ số, 0.000110011 ... lặp lại vô hạn.

Đây chính là vấn đề tương tự như đại diện, ví dụ, 1/3 trong cơ sở 10. Trong cơ sở 10, bạn sẽ cần một số lượng vô hạn các chữ số, .33333 ... mãi mãi, để đại diện cho 1/3 chính xác. Vì vậy, làm việc trong cơ sở 10, bạn thường tròn, để một cái gì đó như. 33. Nhưng nếu bạn thêm ba bản sao của số đó, bạn nhận được 0,99, không phải 1.

Để biết thêm thông tin về chủ đề, hãy đọc What Every Computer Scientist Should Know About Floating Point Arithmetic.

Để biểu diễn số hữu tỷ chính xác hơn trong Haskell, bạn luôn có thể sử dụng loại dữ liệu hợp lý, Ratio; kết hợp với bignums (số lượng lớn tùy ý, Integer trong Haskell, trái ngược với Int có kích thước cố định) là loại cho tử số và mẫu số, bạn có thể biểu diễn số hữu tỉ chính xác tùy ý, nhưng với tốc độ chậm hơn đáng kể so với số dấu phẩy động. được triển khai trong phần cứng và được tối ưu hóa cho tốc độ.

Số dấu phẩy động là một tối ưu hóa, để tính toán khoa học và số, có tính chính xác cho tốc độ cao, cho phép bạn thực hiện một số lượng lớn các tính toán trong một thời gian ngắn, miễn là bạn biết nó ảnh hưởng đến tính toán của bạn.

+0

Cảm ơn bạn đã chỉ ra loại dữ liệu 'Tỷ lệ'! –

+0

Chỉ là một quibble, nhưng 'Ratio' là một hàm tạo kiểu -' Rational' là một kiểu, kiểu (alias) của 'Ratio Integer'. – BMeph

+0

@BMeph: Ah vâng, đúng. Tôi đã trở nên quen thuộc hơn với Haskell trong khi đó (kể từ khi tôi hỏi câu hỏi này) và gõ constructors so với các loại không còn gây nhầm lẫn cho tôi khi họ có cùng tên. –

5

ít nổi có thể được thể hiện một cách chính xác sử dụng IEEE 754 đại diện, vì vậy họ sẽ luôn luôn là một chút đi.

13

Bởi vì số dấu chấm động không chính xác (wikipedia)

+8

-1 Gây hiểu lầm, xin lỗi. Số điểm nổi của IEEE * hoàn toàn chính xác *, bạn chỉ cần xem các lỗi làm tròn với số thập phân. Bạn có cùng một vấn đề cố gắng để đại diện cho phân số như 1/3 trong thập phân (0.3333 ...). – Seth

+5

Đồng ý. Vấn đề không phải là một sự chính xác, nó là một trong những biểu diễn. Các con số có thể được biểu diễn hoàn toàn là hoàn toàn chính xác. Những người nằm giữa những con số đó chỉ có thể xấp xỉ, và _that's_ vấn đề. – codekaizen

+16

Vì vậy, những gì bạn hai nitpickers đang nói là, số dấu phẩy động chỉ là không chính xác khi chúng không chính xác? Wow, không có gì vượt qua mọi người. Nếu '1.1 * 3! = 3.3' trong hệ thống toán học của bạn, tức là * không * chính xác. – Chuck

6

Nó phải thực hiện theo cách các số điểm nổi động IEEE hoạt động.

1,1 được biểu thị dưới dạng 1.1000000000000001 trong điểm động, 3,3 được biểu diễn dưới dạng 3.2999999999999998.

Vì vậy, 1.1 + 1.1 + 1.1 thực sự là

1.1000000000000001 + 1.1000000000000001 + 1.1000000000000001 = 3.3000000000000003

nào, như bạn có thể thấy là thực sự lớn hơn 3,2999999999999998.

Cách giải quyết thông thường là không đánh giá bình đẳng, hoặc để kiểm tra xem một số có nằm trong mục tiêu +/- một epsilon nhỏ (xác định độ chính xác bạn cần) không.

Ví dụ: nếu cả hai đều đúng, thì tổng là "bằng" đến 3,3 (trong lỗi được cho phép).

1.1 + 1.1 + 1.1 < 3.3 + 1e9 
1.1 + 1.1 + 1.1 > 3.3 - 1e9 
+9

Không, 1,1 không được biểu thị là 1.1000000000000001. Nó được biểu thị là 1.0001100110011001100110011001100110011001100110011010 (cơ số 2), khi được chuyển đổi sang thập phân, làm tròn thành 1.100000000000000001 –

+0

Ahh, đủ chính xác. – Seth

13

Bạn có thể tránh lỗi dấu chấm động trong Haskell sử dụng các loại hợp lý:

Prelude Data.Ratio> let a = (11 % 10) + (11 % 10) + (11 % 10) 
Prelude Data.Ratio> a > (33 % 10) 
False 
Prelude Data.Ratio> fromRational a 
3.3 

Tất nhiên, bạn phải trả tiền phạt thực hiện về tính chính xác tăng lên.

+8

Bạn thậm chí có thể viết 'let a = 1.1 + 1.1 + 1.1 :: Rational', không phải lo lắng về việc mất độ chính xác (chữ' 1.1' thực sự là từ viết tắt của 'fromRational (11% 10)'). –

+0

Trong thực tế, bạn có thể chỉ cần viết '1.1 + 1.1 + 1.1 <(3.3 :: Rational) 'để làm những gì OP muốn chính xác. Nó không phải là kinh điển-ish rằng ghci mặc định tính toán này để nổi anyway, khi bạn không rõ ràng nói '1.1 + 1.1 + 1.1 <(3.3 :: Float) '. – leftaroundabout

1

Nói chung, bạn không nên so sánh nổi cho bình đẳng (vì những lý do được nêu ở trên). Lý do duy nhất tôi có thể nghĩ là nếu bạn muốn nói "giá trị này có thay đổi không?" Ví dụ: "if (newscore/= oldscore)" sau đó thực hiện một số hành động. Đó là okay miễn là bạn không so sánh kết quả của hai tính toán riêng biệt để kiểm tra xem chúng xảy ra là bằng nhau (bởi vì sau đó ngay cả toán học nếu chúng được, họ có thể vòng khác).

Các vấn đề liên quan