2010-12-28 29 views
20

Mỗi lần tôi bắt đầu một dự án mới và khi tôi cần phải so sánh một số float hoặc các biến đôi tôi viết code như thế này:Làm thế nào để so sánh chính xác và tiêu chuẩn phao nổi?

if (fabs(prev.min[i] - cur->min[i]) < 0.000001 && 
    fabs(prev.max[i] - cur->max[i]) < 0.000001) { 
     continue; 
} 

Sau đó, tôi muốn thoát khỏi những biến kỳ diệu 0.000001 (và ,00000000001 cho double) và fabs, vì vậy tôi viết một hàm nội tuyến và một số xác định:

#define FLOAT_TOL 0.000001 

Vì vậy, tôi tự hỏi nếu có cách nào tiêu chuẩn để làm điều này? Có thể là một số tiêu đề tập tin tiêu đề? Nó cũng sẽ tốt đẹp nếu có giới hạn float và double (min và max)

+2

Có thể muốn nhìn vào http://stackoverflow.com/questions/17333/most-effective-way-for-float-and-double-comparison này –

+1

Phụ thuộc vào các trường hợp sử dụng, nhưng những gì về rất số nhỏ? Mã của bạn sẽ so sánh '1e-10' và' 1e-15' và '-1e-10' như nhau. Không có cách nào "chính xác" để so sánh các số dấu chấm động cho "sự gần gũi". – aschepler

+4

Tại sao #define? Bạn chỉ có thể sử dụng một float const tĩnh cho mục đích này. – Puppy

Trả lời

2

Cảm ơn câu trả lời của bạn, họ đã giúp tôi rất nhiều.Tôi đã đọc các tài liệu này: firstsecond

Câu trả lời là để sử dụng chức năng của riêng tôi để so sánh tương đối:

bool areEqualRel(float a, float b, float epsilon) { 
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b))); 
} 

Đây là giải pháp phù hợp nhất với nhu cầu của tôi. Tuy nhiên tôi đã viết một số bài kiểm tra và các phương pháp so sánh khác. Tôi hy vọng điều này sẽ hữu ích cho ai đó. areEqualRel vượt qua các thử nghiệm này, những người khác thì không.

#include <iostream> 
#include <limits> 
#include <algorithm> 

using std::cout; 
using std::max; 

bool areEqualAbs(float a, float b, float epsilon) { 
    return (fabs(a - b) <= epsilon); 
} 

bool areEqual(float a, float b, float epsilon) { 
    return (fabs(a - b) <= epsilon * std::max(1.0f, std::max(a, b))); 
} 

bool areEqualRel(float a, float b, float epsilon) { 
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b))); 
} 

int main(int argc, char *argv[]) 
{ 
    cout << "minimum: " << FLT_MIN  << "\n"; 
    cout << "maximum: " << FLT_MAX  << "\n"; 
    cout << "epsilon: " << FLT_EPSILON << "\n"; 

    float a = 0.0000001f; 
    float b = 0.0000002f; 
    if (areEqualRel(a, b, FLT_EPSILON)) { 
     cout << "are equal a: " << a << " b: " << b << "\n"; 
    } 
    a = 1000001.f; 
    b = 1000002.f; 
    if (areEqualRel(a, b, FLT_EPSILON)) { 
     cout << "are equal a: " << a << " b: " << b << "\n"; 
    } 
} 
+1

Chắc chắn bạn có nghĩa là std :: max (fabs (a), fabs (b)), trừ khi tất cả phao của bạn là dương – TonyK

+0

Tôi không nghĩ rằng điều này là cần thiết ở đây. Nếu bạn có một ví dụ làEqualRel() sẽ thất bại, vui lòng cung cấp nó. Hoặc trước tiên hãy thử nghiệm trong mã ở trên. – Dmitriy

+1

Làm thế nào về a = b = -1000? – TonyK

7

Bạn nên lưu ý rằng nếu so sánh hai phao cho bình đẳng, bạn thực chất là làm sai. Thêm một yếu tố slop để so sánh là không đủ tốt.

+0

Đồng ý với @ddyer: OP cần thực hiện và thực hiện một khóa học về phân tích số. – Yttrill

+0

Xét nghiệm đơn vị như thế nào? Nếu tôi đang thử nghiệm một thuật toán và tôi muốn kiểm tra xem kết quả, với các giá trị đầu vào đã cho, có gần giá trị mong đợi (float) không? – Gauthier

+0

Câu hỏi hay, không có câu trả lời đơn giản. Nếu bạn chỉ kiểm tra các lỗi gộp trong thuật toán, thì tôi giả sử một yếu tố slop là một nơi tốt để bắt đầu. Các thử nghiệm khác sẽ liên quan đến dữ liệu cho ăn được thiết kế để kích hoạt các vấn đề, chẳng hạn như sử dụng 2^32-1 làm đầu vào số nguyên. Nói chung, bạn có thể vạch ra sự khác biệt giữa việc triển khai và tiêu chuẩn tham chiếu, tìm kiếm bằng chứng về sự khác biệt. – ddyer

11

Tiêu chuẩn cung cấp giá trị epsilon. Đó là trong <limits> và bạn có thể truy cập giá trị theo số std::numeric_limits<float>::epsilonstd::numeric_limits<double>::epsilon. Có những giá trị khác trong đó, nhưng tôi không kiểm tra chính xác là gì.

+5

Mặc dù hãy cẩn thận rằng 'epsilon' không phải là một thay thế thẳng cho sự khoan dung liên tục như được sử dụng bởi người hỏi. Nó đại diện cho out-by-1 trong bit ít quan trọng nhất của giá trị 1.0, vì vậy nếu giá trị của bạn là khoảng 2, thì nó quá nhỏ để cung cấp bất kỳ sự khoan dung nào. Khá khó để sử dụng hiệu quả. –

4

Bạn nên sử dụng tiêu chuẩn xác định trong float.h:

#define DBL_EPSILON  2.2204460492503131e-016 /* smallest float value such that 1.0+DBL_EPSILON != 1.0 */ 

hoặc lớp numeric_limits:

// excerpt 
template<> 
class numeric_limits<float> : public _Num_float_base 
{ 
public: 
    typedef float T; 

    // return minimum value 
    static T (min)() throw(); 

    // return smallest effective increment from 1.0 
    static T epsilon() throw(); 

    // return largest rounding error 
    static T round_error() throw(); 

    // return minimum denormalized value 
    static T denorm_min() throw(); 
}; 

[EDIT:. Made nó dễ đọc hơn một chút]

Nhưng ngoài ra, nó phụ thuộc vào những gì bạn đang sau.

+2

+1: tốt đẹp, nhưng tiêu đề sao chép-dán không phải là IMO hữu ích nhất. – rubenvb

+1

Tôi chỉ muốn cho thấy rằng có nhiều giá trị thú vị hơn trong việc thực hiện số_limits . – 0xbadf00d

+0

Bạn nhận được nhận xét ở đâu "giá trị nổi nhỏ nhất như vậy mà 1,0 + DBL_EPSILON! = 1.0" từ? Đây là cụm từ sai để xác định 'DBL_EPSILON'. http://blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON –

15

Từ The Floating-Point Guide:

Đây là một cách xấu để làm điều đó bởi vì một epsilon cố định chọn vì nó “có vẻ nhỏ” thực sự có thể là cách quá lớn khi những con số được so sánh là rất nhỏ cũng. So sánh sẽ trả về “true” cho các số hoàn toàn khác nhau. Và khi số lượng rất lớn, epsilon có thể kết thúc nhỏ hơn lỗi làm tròn nhỏ nhất, để việc so sánh luôn trả về “sai”.

Vấn đề với "số ma thuật" ở đây không phải là mã cứng nhưng đó là "ma thuật": bạn không thực sự có lý do chọn 0,000001 trên 0,000005 hoặc 0,0000000000001, phải không? Lưu ý rằng float có thể xấp xỉ đại diện cho giá trị thứ hai và vẫn nhỏ hơn - chỉ khoảng 7 số thập phân chính xác sau chữ số không đầu tiên đầu tiên!

Nếu bạn định sử dụng epsilon cố định, bạn thực sự nên chọn nó theo yêu cầu của đoạn mã cụ thể mà bạn sử dụng nó. Cách khác là sử dụng lề lỗi tương đối (xem liên kết ở trên cùng để biết chi tiết) hoặc thậm chí tốt hơn, hoặc compare the floats as integers.

+2

Đối với những gì nó có giá trị, Bruce Dawson đã đề cập rằng bài viết của mình về so sánh các số dấu phẩy động hiện nay đã lỗi thời, và độc giả nên tham khảo [phiên bản 2012] (http://randomascii.wordpress.com/2012/02/25/so sánh-floating-point-number-2012-edition /) thay thế. –

+0

@ Chris Frederick: cảm ơn, tôi sẽ thêm liên kết tới ấn bản đó vào trang web –

1

bài này có một lời giải thích toàn diện về cách so sánh số dấu chấm động: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/

Trích:

  • Nếu bạn đang so sánh với zero, sau đó epsilons tương đối và ULPs dựa so sánh thường vô nghĩa. Bạn sẽ cần sử dụng một eps2on tuyệt đối , có giá trị có thể là một số bội số nhỏ của FLT_EPSILON và các yếu tố đầu vào cho phép tính của bạn. Có lẽ.
  • Nếu bạn so sánh với số khác 0 thì so sánh tương đối epsilons hoặc ULPs có lẽ là những gì bạn muốn. Bạn sẽ có thể muốn một số lượng nhỏ FLT_EPSILON cho người thân của bạn epsilon hoặc một số lượng nhỏ ULP. Một epsilon tuyệt đối có thể là được sử dụng nếu bạn biết chính xác số lượng bạn đã so sánh.
6

Bạn có thể sử dụng để thử nghiệm std::nextafter hai double với epsilon nhỏ nhất trên một giá trị (hoặc một yếu tố của epsilon nhỏ nhất).

bool nearly_equal(double a, double b) 
{ 
    return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b 
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b; 
} 

bool nearly_equal(double a, double b, int factor /* a factor of epsilon */) 
{ 
    double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor; 
    double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor; 

    return min_a <= b && max_a >= b; 
} 
3

Đây là một giải pháp C++ 11 của giải pháp @geotavros. Nó sử dụng chức năng std::numeric_limits<T>::epsilon() mới và thực tế là std::fabs()std::fmax() hiện đang bị quá tải cho float, doublelong float.

template<typename T> 
static bool AreEqual(T f1, T f2) { 
    return (std::fabs(f1 - f2) <= std::numeric_limits<T>::epsilon() * std::fmax(fabs(f1), fabs(f2))); 
} 
Các vấn đề liên quan