2009-07-21 57 views
7

Tôi đang sử dụng Visual Studio 6 với một số mã thời gian cũ được viết bằng c. Tôi đã tìm thấy vấn đề mà mã trông giống như thế này ..So sánh quan hệ giữa int và float có thể trực tiếp trong C?

int x = 3; 
float y = 3.0; 

if(x == y){ 
    do some crazy stuff 
} 

đây có phải là so sánh hợp lệ không? là nó có thể tại thời gian chạy phân bổ cho phao là 3.0000001 và điều này sẽ thất bại?

+1

Chỉ là một ví dụ hoặc bạn thực sự yêu cầu trường hợp cụ thể liên quan đến 3.0? –

Trả lời

1

Vâng, tôi đoán bạn sẽ không quá ngạc nhiên khi biết rằng so sánh nổi cho bình đẳng là một sai lầm tân binh sau đó.

Vấn đề là nhiều gia số nhỏ hơn giá trị số nguyên không thể thực sự được biểu diễn chính xác trong điểm nổi IEEE. Vì vậy, nếu bạn đến phao bằng cách cố gắng "lập chỉ mục" nó lên đến giá trị 3.0 (nói theo số gia là 0,1), thì có thể so sánh bình đẳng của bạn có thể không bao giờ là là đúng.

Đó cũng là ý tưởng tồi từ quan điểm về độ bền kiểu. Bạn nên chuyển đổi float thành một int, kiểm tra int của bạn là "đủ gần" (ví dụ: < 3.1 và> 2.9 hoặc somesuch), hoặc tốt hơn nếu bạn đang cố gắng làm cho float làm nhiệm vụ kép cho một cái gì đó giống như một bộ đếm , tránh toàn bộ ý tưởng.

+6

Woah! Hầu hết các giá trị số nguyên? Tôi giả sử bạn có nghĩa là những người trên 9 quadrillion. Các số nguyên có kích thước hợp lý (như 3) tất cả phải trượt khá độc đáo và chính xác thành một phao IEEE. Vấn đề bắt đầu _to right_ của dấu thập phân. 0,1 không có biểu diễn hữu hạn trong nhị phân. – Nosredna

+0

Cảm ơn bạn đã phản hồi .. đây là> mã 15 tuổi và tôi đang cố gắng tìm nguyên nhân gốc để tại sao hàm được gọi trong một câu lệnh if như tôi đã đăng. Tôi nhận thấy kịch bản này, nơi mã so sánh một loại int và kiểu float và nghĩ rằng một cái gì đó không đúng ... Chúng tôi đã có một đối số nội bộ với một người bạn nếu điều này có thể thất bại. Anh ta nợ tôi ăn trưa. –

+0

Trên CPU 64 bit, hầu hết các giá trị cố định trên 9 nghìn tỷ. :-) – Ken

13

Điều này thường (nghĩa là luôn luôn) là một ý tưởng tồi. Như bạn nghi ngờ, so sánh từ 3 đến 3.0000001 sẽ thực sự thất bại.

gì hầu hết mọi người làm, nếu một so sánh int-float là thực sự cần thiết, là chọn một số ngưỡng chịu đựng và đi với điều đó, như vậy:

int x = 3; 
float y = 3.0; 

// some code here 

float difference = (float) x - y; 
float tolerableDifference = 0.001; 

if ((-tolerableDifference <= difference) && (difference <= tolerableDifference)) { 
    // more code 
} 
+0

Tôi muốn sử dụng fabs cho ngắn hơn nhưng có lẽ đang chậm: if (fabs (x-y) bh213

+1

hay: if (fabs (sự khác biệt) Nosredna

+0

@ bh213. Bạn đánh bại tôi vào nó. Tôi đồng ý - Tôi nghĩ rằng nó dễ đọc và dễ hiểu hơn. – Nosredna

1

Điểm mấu chốt của vấn đề là các số dấu phảy trong đó có một biểu diễn hữu hạn trong cơ sở 10, số thập phân không phải lúc nào cũng có một biểu diễn hữu hạn trong cơ sở 2, nhị phân.

+2

Nhưng 3.0, phải không? – Nosredna

-1

Sửa:

Cách đúng là sử dụng epsilon phương pháp:

#include <math.h> 
int x = 3; 
int y = 3.0; 
if (fabs((float) x - y) < 0.0001) { // Adjust the epsilon 
    // Do stuff 
} 
+1

Điều này sẽ vẫn thất bại nếu y == 3.0000001 vì sau đó bạn sẽ so sánh 3,0 đến 3,0000001. –

+1

sẽ không giúp ích gì. Lỗi dấu chấm động sẽ làm cho kiểm tra đó có vấn đề. == nói chung là một ý tưởng tồi cho phao nổi. – Herms

+0

+1 cho bản sửa đổi của bạn. –

0

Nếu mã trông theo nghĩa đen như những gì bạn gửi (không có tính can thiệp), sau đó nói đến một câu hỏi liệu 3.0(float)3 (vì số nguyên được tự động chuyển thành một phao) là giống nhau. Tôi nghĩ rằng họ được đảm bảo là như nhau trong trường hợp này, bởi vì 3 là chính xác thể hiện như một float.

Ngoài: Và thậm chí nếu các số nguyên là không chính xác biểu diễn bằng phao (tức là nếu nó thực sự lớn), tôi sẽ tưởng tượng rằng trong hầu hết các trường, x.0(float)x sẽ là như nhau bởi vì, như thế nào sẽ trình biên dịch tạo ra x.0 ở nơi đầu tiên, nếu không làm điều gì đó giống như (float)x? Tuy nhiên, tôi đoán điều này không được đảm bảo theo tiêu chuẩn.

+0

Kích thước của mantissa của phao có thể khác với số bit được sử dụng để biểu diễn số nguyên. Vì vậy, yeah, số lượng lớn có thể thất bại. Tôi đồng ý rằng anh ta có thể sẽ không gặp vấn đề với các số nguyên nhỏ. Nó chỉ là nguy hiểm để có được trong thói quen so sánh phao và ints. Đặc biệt khi một số số học đã xảy ra. Là 3,1f - 0,1f đại diện giống như 3,0f? – Nosredna

0

Điều đó thật đáng sợ. (Tôi tự hỏi bạn sẽ tìm thấy điều gì khác.)

x sẽ được thăng cấp, nhưng điều đó sẽ không giúp bạn. Bởi vì làm thế nào nổi được đại diện, bằng cách sử dụng == để so sánh chúng là không đáng tin cậy.

tôi có thể đề nghị một cái gì đó như thế này (kiểm tra sai số tuyệt đối/sự khác biệt) thay vì:

#define EPSILON 0.0001 
if (fabs((float)x - y) < EPSILON) { /* Do stuff. */ } 

mà là một phương pháp phổ biến và có thể là đủ cho mục đích của bạn, nếu giá trị của bạn của x và y là "nice ". Nếu bạn thực sự muốn đi sâu vào chủ đề so sánh phao, this article có thể có nhiều thông tin hơn bạn muốn. Nó nói về phương pháp epsilon:

Nếu phạm vi của expectedResult là biết đến sau đó kiểm tra các sai số tuyệt đối là đơn giản và hiệu quả. Chỉ cần thực hiện chắc chắn rằng giá trị lỗi tuyệt đối của bạn là lớn hơn mức chênh lệch tối thiểu tối thiểu cho phạm vi và loại mà bạn đang xử lý.

2

Không, không có vấn đề gì trong trường hợp sử dụng của bạn, vì các số nguyên được ánh xạ chính xác đến phao (không có vấn đề cắt ngắn thập phân, ví dụ 0,3; nhưng 3 là 1.1E10 trong ký hiệu khoa học nhị phân).

Trong trường hợp xấu nhất mà tôi có thể nghĩ đến, có thể là số nguyên không thể biểu diễn bằng float vì có "khoảng trống" lớn hơn 1 giữa hai số phao liên tiếp, nhưng ngay cả trong trường hợp đó, khi số nguyên là cast để float để làm so sánh, nó sẽ được cắt ngắn đến float gần nhất, theo cách tương tự như float float đã làm.

Vì vậy, các phao của bạn dài đến từ các chữ số thập phân, so sánh với số nguyên tương đương sẽ giống nhau, bởi vì số nguyên sẽ được đúc thành cùng một float trước khi so sánh có thể được thực hiện.

+1

Từ Chuẩn, 4.9, nếu một số nguyên không thể được biểu diễn chính xác, kết quả sẽ là giá trị đại diện thấp hơn hoặc cao hơn tiếp theo, và giá trị đó sẽ được xác định thực hiện. Quy tắc cho các chữ số dấu phẩy động là giống nhau, mặc dù có cách khác nhau (2.13.3). Tôi đã không tìm thấy bất cứ điều gì nói rằng họ phải được cùng một quy tắc, vì vậy trong khi tôi nghĩ rằng các chữ nổi sẽ đi ra giống như chuyển đổi float, nó không giống như tiêu chuẩn đòi hỏi nó. –

+0

điểm rất thú vị! – fortran

5

Tôi sắp sửa xu hướng ở đây một chút. Đối với câu hỏi đầu tiên về việc liệu so sánh có hợp lệ hay không, câu trả lời là có. Nó hoàn toàn hợp lệ. Nếu bạn muốn biết nếu một giá trị dấu phẩy động là chính xác bằng 3, thì so sánh với một số nguyên là tốt. Số nguyên được chuyển đổi hoàn toàn thành giá trị dấu phẩy động để so sánh. Trong thực tế, đoạn mã sau (ít nhất là với trình biên dịch tôi đã sử dụng) đã tạo ra các hướng dẫn lắp ráp giống hệt nhau.

if (3 == f) 
    printf("equal\n"); 

if (3.0 == f) 
    printf("equal\n"); 

Vì vậy, nó phụ thuộc vào logic và những gì các mục tiêu dự định là. Không có gì sai với cú pháp.

2

Ví dụ cụ thể của bạn, "làm một số nội dung điên rồ" sẽ thực thi. 3.0 sẽ không được 3.0000001 khi chạy.

Các câu trả lời khác là nhiều hơn cho các trường hợp chung, nhưng ngay cả một epsilon hardcoded không phải là ý tưởng lớn nhất trên thế giới. Một epsilon động dựa trên các con số thực tế có liên quan là tốt hơn nhiều vì số dương và âm càng nhiều thì càng ít thì epsilon được mã hóa cứng sẽ có liên quan.

4

Chưa có ai khác trích dẫn và tôi chưa liên kết với nó trong một thời gian, vì vậy đây là bài viết cổ điển trên các cạnh đáng sợ của biểu diễn dấu phẩy động và số học: What Every Computer Scientist Should Know About Floating Point.

Bài đọc này là một bài đọc đầy thách thức cho một nhà toán học phi, nhưng các điểm chính được nêu rõ ở giữa các phần lớn toán học sao lưu chúng.

Đối với thảo luận này, các điểm được thực hiện bởi các câu trả lời khác ở đây đều hợp lệ.Số học dấu chấm động là không chính xác, và do đó so sánh cho bình đẳng chính xác thường là một ý tưởng tồi. Do đó, epsilon là bạn của bạn.

Một ngoại lệ đối với quy tắc so sánh chính xác là kiểm tra chính xác bằng không. Nó là hoàn toàn hợp pháp và thường hợp lý để kiểm tra chính xác bằng không trước một phép chia hoặc lôgarit vì câu trả lời được xác định rõ cho bất kỳ giá trị khác không. Tất nhiên, trong sự hiện diện của các quy tắc IEEE và NaN, bạn có thể cho phép trượt và kiểm tra cho NaN hoặc Inf sau này.

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