मुझे फ्लोटिंग पॉइंट तुलना कैसे करनी चाहिए?


85

मैं वर्तमान में कुछ कोड लिख रहा हूँ जहाँ मेरे पास कुछ पंक्तियाँ हैं:

और फिर अन्य स्थानों पर मुझे समानता करने की आवश्यकता हो सकती है:

संक्षेप में, मेरे पास बहुत सारे फ्लोटिंग पॉइंट गणित चल रहे हैं और मुझे शर्तों के लिए विभिन्न तुलनाएं करने की आवश्यकता है। मैं इसे पूर्णांक गणित में नहीं बदल सकता क्योंकि इस संदर्भ में ऐसी बात निरर्थक है।

मैंने पहले पढ़ा है कि फ्लोटिंग पॉइंट की तुलना अविश्वसनीय हो सकती है, क्योंकि आपके पास इस तरह की चीजें हो सकती हैं:

संक्षेप में, मैं यह जानना चाहूंगा: मैं अस्थायी रूप से फ्लोटिंग पॉइंट संख्याओं की तुलना कैसे कर सकता हूं (कम से अधिक, समानता)?

मैं जिस रेंज का उपयोग कर रहा हूं वह लगभग 10E-14 से 10E6 है, इसलिए मुझे छोटी संख्या के साथ-साथ बड़े काम करने की आवश्यकता है।

मैंने इसे भाषा अज्ञेय के रूप में टैग किया है क्योंकि मुझे इस बात में दिलचस्पी है कि मैं इस बात को कैसे पूरा कर सकता हूं कि मैं किस भाषा का उपयोग कर रहा हूं।


फ़्लोटिंग पॉइंट नंबरों का उपयोग करते समय मज़बूती से ऐसा करने का कोई तरीका नहीं है। हमेशा ऐसे नंबर होंगे जो कंप्यूटर के लिए समान होते हैं हालांकि वास्तविकता में नहीं होते हैं (जैसे कि 1E + 100, 1E + 100 + 1), और आपके पास आमतौर पर गणना परिणाम भी होंगे कि कंप्यूटर वास्तविकता में नहीं के बराबर हैं (देखें नेल्हाज के उत्तर के लिए टिप्पणियों में से एक)। आपको यह चुनना होगा कि आप दोनों में से किसकी इच्छा कम है।
24

दूसरी ओर, यदि आप कहते हैं, केवल परिमेय संख्याओं के साथ व्यवहार करते हैं, तो आप पूर्णांक संख्याओं के आधार पर कुछ परिमेय संख्या अंकगणित को लागू कर सकते हैं और फिर दो संख्याओं को समान माना जाता है यदि दो संख्याओं में से एक को दूसरे के लिए रद्द किया जा सकता है।
24

ठीक है, वर्तमान में मैं एक सिमुलेशन काम कर रहा हूँ। जिस स्थान पर मैं आमतौर पर इन तुलना कर रहा हूं वह चर समय चरणों (कुछ ओड को हल करने के लिए) से संबंधित है। कुछ उदाहरण हैं जहां मुझे यह जांचने की आवश्यकता है कि किसी वस्तु के लिए दिए गए समय के कदम, किसी अन्य वस्तु के समय के कदम से कम या अधिक के बराबर है।
माइक बेली


सरणियों का उपयोग क्यों नहीं करते? stackoverflow.com/questions/28318610/…
एड्रियन पी।

जवाबों:


69

जब तक आप फ्लोट / डबल सटीक सीमा के किनारे पर सही काम नहीं कर रहे हैं, तब तक बड़े / छोटे की तुलना करना वास्तव में एक समस्या नहीं है।

एक "फजी बराबरी" तुलना के लिए, यह (जावा कोड, अनुकूलित करने में आसान होना चाहिए) वह है जो मैं बहुत काम के बाद फ्लोटिंग-पॉइंट गाइड के लिए आया था और बहुत सारी आलोचनाओं को ध्यान में रखते हुए:

public static boolean nearlyEqual(float a, float b, float epsilon) {
    final float absA = Math.abs(a);
    final float absB = Math.abs(b);
    final float diff = Math.abs(a - b);

    if (a == b) { // shortcut, handles infinities
        return true;
    } else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
        // a or b is zero or both are extremely close to it
        // relative error is less meaningful here
        return diff < (epsilon * Float.MIN_NORMAL);
    } else { // use relative error
        return diff / (absA + absB) < epsilon;
    }
}

यह एक परीक्षण सूट के साथ आता है। आपको तुरंत किसी भी समाधान को खारिज नहीं करना चाहिए जो नहीं करता है, क्योंकि यह लगभग कुछ किनारे के मामलों में विफल होने की गारंटी है जैसे कि एक मान 0, शून्य के विपरीत दो बहुत छोटे मूल्य, या शिशु।

एक विकल्प (अधिक विवरण के लिए ऊपर लिंक देखें) फ़्लोट्स के बिट पैटर्न को पूर्णांक में बदलने और एक निश्चित पूर्णांक दूरी के भीतर सब कुछ स्वीकार करने के लिए है।

किसी भी मामले में, संभवतः कोई भी समाधान नहीं है जो सभी अनुप्रयोगों के लिए एकदम सही है। आदर्श रूप में, आप अपने वास्तविक उपयोग के मामलों को कवर करने वाले टेस्ट सूट के साथ अपना खुद का विकास / अनुकूलन करेंगे।


1
@toochin: इस बात पर निर्भर करता है कि आप कितनी बड़ी त्रुटि के लिए अनुमति देना चाहते हैं, लेकिन यह सबसे स्पष्ट रूप से एक समस्या बन जाती है, जब आप शून्य, सकारात्मक और नकारात्मक के निकटतम संख्या पर विचार करते हैं - शून्य के अलावा, ये किसी भी अन्य दो की तुलना में एक साथ करीब हैं। मान, फिर भी रिश्तेदार त्रुटि के आधार पर कई भोले-भाले कार्यान्वयन उन्हें बहुत दूर होने पर विचार करेंगे।
माइकल बोर्गवर्ड 20

2
हम्म। आपके पास एक परीक्षण है else if (a * b == 0), लेकिन फिर उसी लाइन पर आपकी टिप्पणी है a or b or both are zero। लेकिन ये दो अलग चीजें नहीं हैं? जैसे, अगर a == 1e-162और b == 2e-162फिर शर्त a * b == 0सही होगी।
मार्क डिकिंसन

1
@toochin: मुख्य रूप से क्योंकि कोड आसानी से अन्य भाषाओं के लिए पोर्टेबल माना जाता है, जिसमें वह कार्यक्षमता नहीं हो सकती है (यह केवल 1.5 जावा में जावा में जोड़ा गया था)।
माइकल बोर्गवर्ड

1
यदि वह फ़ंक्शन बहुत अधिक उपयोग किया जाता है (उदाहरण के लिए वीडियो गेम के प्रत्येक फ्रेम) तो मैं इसे महाकाव्य अनुकूलन के साथ विधानसभा में फिर से लिखूंगा।

1
महान मार्गदर्शक और महान उत्तर, विशेष रूप से abs(a-b)<epsयहां के उत्तरों पर विचार कर रहे हैं। दो प्रश्न: (1) क्या यह बेहतर नहीं होगा कि सभी <एस को बदल दिया जाए <=, इस प्रकार "शून्य-ईपीएस" की तुलना, सटीक तुलना के बराबर है? (2) के diff < epsilon * (absA + absB);बजाय diff / (absA + absB) < epsilon;(अंतिम पंक्ति) का उपयोग करना बेहतर नहीं होगा -?
फ्रांज डी।

41

टी एल; डॉ

  • संभावित रूप से अधिक कुशल होते हुए, कुछ सीमा मामलों में कुछ अवांछनीय परिणामों से बचने के लिए वर्तमान में स्वीकृत समाधान के बजाय निम्नलिखित फ़ंक्शन का उपयोग करें।
  • अपने संख्याओं पर अपेक्षित अपव्यय को जानें और तुलनात्मक कार्य के अनुसार उन्हें खिलाएं।

ग्राफिक्स, कृपया?

फ्लोटिंग पॉइंट नंबरों की तुलना करते समय, दो "मोड" होते हैं।

पहला एक सापेक्ष मोड है, जहां उनके आयाम के बीच का अंतर xऔर yअपेक्षाकृत माना जाता है |x| + |y|। जब 2 डी में प्लॉट किया जाता है, तो यह निम्नलिखित प्रोफ़ाइल देता है, जहां हरे रंग का अर्थ है समानता xऔर y। (मैंने epsilonचित्रण प्रयोजनों के लिए 0.5 लिया )।

यहाँ छवि विवरण दर्ज करें

सापेक्ष मोड वह है जो "सामान्य" या "बड़े पर्याप्त" फ्लोटिंग पॉइंट मान के लिए उपयोग किया जाता है। (उस पर और बाद में)।

दूसरा एक पूर्ण विधा है, जब हम उनके अंतर की निश्चित संख्या से तुलना करते हैं। यह निम्नलिखित प्रोफाइल देता है ( चित्रण के लिए फिर epsilonसे 0.5 और relth1 के साथ)।

यहाँ छवि विवरण दर्ज करें

तुलना का यह पूर्ण विधा है, जो "छोटे" फ्लोटिंग पॉइंट मानों के लिए उपयोग किया जाता है।

अब सवाल यह है कि हम उन दो प्रतिक्रिया पैटर्न को एक साथ कैसे जोड़ते हैं।

माइकल बोर्गवर्ड के उत्तर में, स्विच मूल्य पर आधारित है diff, जो नीचे relth( Float.MIN_NORMALउसके उत्तर में) होना चाहिए । इस स्विच ज़ोन को नीचे दिए गए ग्राफ़ में रचा गया दिखाया गया है।

यहाँ छवि विवरण दर्ज करें

क्योंकि relth * epsilonछोटा होता है कि relth, हरी पैच एक साथ छड़ी नहीं है, बारी में समाधान एक बुरा संपत्ति देता है: हम संख्या ऐसी है कि के साथ तीन बच्चों पा सकते हैं x < y_1 < y_2और अभी तक x == y2लेकिन x != y1

यहाँ छवि विवरण दर्ज करें

इसका उदाहरण लें:

हमारे पास है x < y1 < y2, और वास्तव y2 - xमें 2000 गुना से अधिक बड़ा है y1 - x। और फिर भी वर्तमान समाधान के साथ,

इसके विपरीत, ऊपर प्रस्तावित समाधान में, स्विच ज़ोन के मूल्य पर आधारित है |x| + |y|, जिसे नीचे दिए गए हैट स्क्वायर द्वारा दर्शाया गया है। यह सुनिश्चित करता है कि दोनों क्षेत्र इनायत से जुड़ते हैं।

यहाँ छवि विवरण दर्ज करें

इसके अलावा, ऊपर दिए गए कोड में ब्रांचिंग नहीं है, जो अधिक कुशल हो सकता है। उस ऑपरेशन पर विचार करें जैसे कि , maxऔर abs, जिसे प्राथमिकताओं में शाखाओं की आवश्यकता होती है, अक्सर विधानसभा निर्देश समर्पित होते हैं। इस कारण से, मुझे लगता है कि इस दृष्टिकोण एक और समाधान है कि माइकल के ठीक करने के लिए किया जाएगा से बेहतर है nearlyEqualसे स्विच को बदलने के द्वारा diff < relthकरने के लिए diff < eps * relthहै, जो तब मूलतः एक ही प्रतिक्रिया पैटर्न का उत्पादन होगा।

रिश्तेदार और पूर्ण तुलना के बीच स्विच कहां करें?

उन मोड के बीच का स्विच चारों ओर बना होता है relth, जिसे FLT_MINस्वीकृत उत्तर के रूप में लिया जाता है। इस विकल्प का मतलब है कि वह float32है जो हमारे फ्लोटिंग पॉइंट नंबरों की शुद्धता को सीमित करता है।

यह हमेशा समझ में नहीं आता है। उदाहरण के लिए, यदि आप जिन संख्याओं की तुलना करते हैं, वे घटाव के परिणाम हैं, शायद कुछ की सीमा में FLT_EPSILONअधिक समझ में आता है। यदि वे घटाए गए संख्याओं की जड़ें हैं, तो संख्यात्मक संसेचन और भी अधिक हो सकता है।

जब आप एक फ्लोटिंग पॉइंट की तुलना करने पर विचार करते हैं तो यह स्पष्ट है 0। यहां, किसी भी सापेक्ष तुलना विफल हो जाएगी, क्योंकि |x - 0| / (|x| + 0) = 1। इसलिए तुलना को पूर्ण रूप से बदलने की जरूरत है जब xआपकी गणना की असंभवता के क्रम पर है - और शायद ही कभी यह जितना कम हो FLT_MIN

यह relthऊपर दिए गए पैरामीटर की शुरूआत का कारण है।

इसके अलावा, इसके relthसाथ गुणा नहीं करने से epsilon, इस पैरामीटर की व्याख्या सरल है और संख्यात्मक सटीकता के स्तर के अनुरूप है जो हम उन नंबरों पर उम्मीद करते हैं।

गणितीय रूंबिंग

(यहां ज्यादातर अपनी मर्जी से रखा गया)

आमतौर पर मैं मानता हूं कि एक अच्छी तरह से व्यवहार किया गया फ्लोटिंग पॉइंट तुलना ऑपरेटर =~में कुछ बुनियादी गुण होने चाहिए।

निम्नलिखित स्पष्ट नहीं हैं:

  • आत्म-समानता: a =~ a
  • समरूपता: a =~ bतात्पर्य हैb =~ a
  • विपक्ष द्वारा आक्रमण: a =~ bतात्पर्य-a =~ -b

(हमारे पास नहीं है a =~ bऔर b =~ cइसका मतलब है a =~ c, =~एक समानता संबंध नहीं है)।

मैं निम्नलिखित गुण जोड़ूंगा जो फ्लोटिंग पॉइंट तुलनाओं के लिए अधिक विशिष्ट हैं

  • यदि a < b < c, तो a =~ cतात्पर्य a =~ b(निकट मान भी समान होना चाहिए)
  • यदि a, b, m >= 0तब a =~ bतात्पर्य है a + m =~ b + m(समान अंतर के साथ बड़े मान भी बराबर होने चाहिए)
  • अगर 0 <= λ < 1तब a =~ bतात्पर्य है λa =~ λb(शायद तर्क के लिए कम स्पष्ट)।

वे गुण पहले से ही सम-विषम कार्यों के लिए मजबूत अवरोध प्रदान करते हैं। ऊपर प्रस्तावित कार्य उन्हें सत्यापित करता है। शायद एक या कई अन्यथा स्पष्ट गुण गायब हैं।

जब एक के बारे में सोच =~के रूप में समानता के रिश्ते की एक परिवार =~[Ɛ,t]द्वारा parameterized Ɛऔर relth, एक भी जोड़ सकता है

  • यदि Ɛ1 < Ɛ2फिर a =~[Ɛ1,t] bइसका मतलब है a =~[Ɛ2,t] b(किसी दिए गए सहिष्णुता के लिए समानता का अर्थ है उच्च सहिष्णुता में समानता)
  • अगर t1 < t2तब a =~[Ɛ,t1] bतात्पर्य है a =~[Ɛ,t2] b(किसी दिए गए अभेद्यता के लिए समानता का अर्थ है एक उच्च वृद्धि पर समानता)

प्रस्तावित समाधान भी इनकी पुष्टि करता है।


1
यह एक महान जवाब है!
दाविदघ

1
c ++ कार्यान्वयन प्रश्न: क्या (std::abs(a) + std::abs(b))कभी इससे अधिक हो सकता है std::numeric_limits<float>::max()?
anneb

1
@anneb हाँ, यह + INF हो सकता है।
पॉल ग्रोके

16

मुझे फ्लोटिंग पॉइंट नंबरों की तुलना करने की समस्या थी A < Bऔर A > B यहाँ वह है जो काम करता है:

फैब्स - निरपेक्ष मूल्य - का ख्याल रखता है यदि वे अनिवार्य रूप से समान हैं।


1
fabsयदि आप पहला परीक्षण करते हैं, तो बिल्कुल भी उपयोग करने की आवश्यकता नहीं हैif (A - B < -Epsilon)
फिशिनियर

11

फ्लोट नंबरों की तुलना करने के लिए हमें एक सहिष्णुता स्तर चुनना होगा। उदाहरण के लिए,

final float TOLERANCE = 0.00001;
if (Math.abs(f1 - f2) < TOLERANCE)
    Console.WriteLine("Oh yes!");

एक नोट। आपका उदाहरण बल्कि मज़ेदार है।

double a = 1.0 / 3.0;
double b = a + a + a;
if (a != b)
    Console.WriteLine("Oh no!");

कुछ गणित यहाँ

a = 1/3
b = 1/3 + 1/3 + 1/3 = 1.

1/3 != 1

अरे हाँ..

क्या मतलब है आपका

if (b != 1)
    Console.WriteLine("Oh no!")

3

आइडिया मैं स्विफ्ट में फ्लोटिंग पॉइंट तुलना के लिए था

infix operator ~= {}

func ~= (a: Float, b: Float) -> Bool {
    return fabsf(a - b) < Float(FLT_EPSILON)
}

func ~= (a: CGFloat, b: CGFloat) -> Bool {
    return fabs(a - b) < CGFloat(FLT_EPSILON)
}

func ~= (a: Double, b: Double) -> Bool {
    return fabs(a - b) < Double(FLT_EPSILON)
}

1

माइकल Borgwardt और bosonix के जवाब से PHP के लिए अनुकूलन:

class Comparison
{
    const MIN_NORMAL = 1.17549435E-38;  //from Java Specs

    // from http://floating-point-gui.de/errors/comparison/
    public function nearlyEqual($a, $b, $epsilon = 0.000001)
    {
        $absA = abs($a);
        $absB = abs($b);
        $diff = abs($a - $b);

        if ($a == $b) {
            return true;
        } else {
            if ($a == 0 || $b == 0 || $diff < self::MIN_NORMAL) {
                return $diff < ($epsilon * self::MIN_NORMAL);
            } else {
                return $diff / ($absA + $absB) < $epsilon;
            }
        }
    }
}

1

आपको अपने आप से पूछना चाहिए कि आप संख्याओं की तुलना क्यों कर रहे हैं। यदि आप तुलना का उद्देश्य जानते हैं तो आपको अपने नंबरों की आवश्यक सटीकता भी पता होनी चाहिए। यह प्रत्येक स्थिति और प्रत्येक अनुप्रयोग संदर्भ में अलग है। लेकिन बहुत सारे व्यावहारिक मामलों में एक आवश्यक पूर्ण सटीकता है। यह केवल बहुत ही कम है कि एक रिश्तेदार सटीकता लागू है।

एक उदाहरण देने के लिए: यदि आपका लक्ष्य स्क्रीन पर एक ग्राफ खींचना है, तो आप संभवत: फ्लोटिंग पॉइंट मानों की तुलना करना चाहते हैं यदि वे स्क्रीन पर एक ही पिक्सेल में मैप करते हैं। यदि आपकी स्क्रीन का आकार 1000 पिक्सेल है, और आपके नंबर 1e6 रेंज में हैं, तो आप 100 को 200 के बराबर तुलना करना चाहेंगे।

आवश्यक पूर्ण सटीकता को देखते हुए, फिर एल्गोरिथ्म बन जाता है:

public static ComparisonResult compare(float a, float b, float accuracy) 
{
    if (isnan(a) || isnan(b))   // if NaN needs to be supported
        return UNORDERED;    
    if (a == b)                 // short-cut and takes care of infinities
        return EQUAL;           
    if (abs(a-b) < accuracy)    // comparison wrt. the accuracy
        return EQUAL;
    if (a < b)                  // larger / smaller
        return SMALLER;
    else
        return LARGER;
}

0

मानक सलाह कुछ छोटे "एप्सिलॉन" मूल्य (आपके आवेदन के आधार पर चुनी गई, शायद) का उपयोग करने के लिए है, और उन झांकियों पर विचार करें जो एक दूसरे के एप्सिलॉन के बराबर हैं। जैसे कुछ

एक अधिक पूर्ण उत्तर जटिल है, क्योंकि फ्लोटिंग पॉइंट त्रुटि अत्यंत सूक्ष्म है और इसके बारे में तर्क करने के लिए भ्रमित है। यदि आप वास्तव में किसी भी सटीक अर्थ में समानता के बारे में परवाह करते हैं, तो आप शायद एक ऐसा समाधान तलाश रहे हैं जिसमें फ्लोटिंग पॉइंट शामिल न हो।


क्या होगा अगर वह 2.3E-15 की तरह वास्तव में छोटे फ्लोटिंग पॉइंट नंबरों के साथ काम कर रहा है?
19

1
मैं मोटे तौर पर [10E-14, 10E6] के साथ काम कर रहा हूं, काफी मशीन एप्सिलॉन नहीं बल्कि इसके बहुत करीब।
माइक बेली

2
छोटी संख्याओं के साथ काम करना कोई समस्या नहीं है यदि आप ध्यान रखें कि आपको सापेक्ष त्रुटियों के साथ काम करना है । यदि आप अपेक्षाकृत बड़ी त्रुटि सहिष्णुता के बारे में परवाह नहीं करते हैं, तो ऊपर ठीक होगा यदि आप इसे कुछ के साथ इस तरह की स्थिति में बदल if ((a - b) < EPSILON/a && (b - a) < EPSILON/a)
देंगे

2
ऊपर दिया गया कोड भी समस्याग्रस्त है जब आप बहुत बड़ी संख्या से निपटते हैं c, क्योंकि एक बार आपकी संख्या काफी बड़ी हो जाने के बाद, EPSILON मशीन की सटीकता से छोटा होगा c। जैसे मान लीजिए c = 1E+22; d=c/3; e=d+d+d;। फिर e-cअच्छी तरह से 1 से अधिक हो सकता है
भीचिन

1
उदाहरण के लिए, double a = pow(8,20); double b = a/7; double c = b+b+b+b+b+b+b; std::cout<<std::scientific<<a-c;(a और c, pnt और nelhage के अनुसार बराबर नहीं है), या double a = pow(10,-14); double b = a/2; std::cout<<std::scientific<<a-b;(a और b, pnt और nelhage के अनुसार बराबर) को
आज़माएं

0

मैंने उपरोक्त टिप्पणियों को ध्यान में रखते हुए एक समानता समारोह लिखने की कोशिश की। यहाँ मैं क्या लेकर आया हूँ:

संपादित करें: Math.Max ​​(A, b) से Math.Max ​​(Math.Abs ​​(a), Math.Abs ​​(b) में बदलें

विचार? मुझे अभी भी एक से अधिक और साथ ही साथ कम से कम काम करने की आवश्यकता है।


epsilonहोनी चाहिए Math.abs(Math.Max(a, b)) * Double.Epsilon;, या यह हमेशा से छोटा होगा diffनकारात्मक के लिए aऔर b। और मुझे लगता है कि आपका epsilonकार्य बहुत छोटा है, हो सकता है कि फ़ंक्शन ==ऑपरेटर से अलग कुछ भी न लौटाए । से बड़ा है a < b && !fpEqual(a,b)
30

1
जब दोनों मान बिल्कुल शून्य होते हैं, तो Double.Epsilon और -Double.Epsilon के लिए विफल रहता है, शिशुओं के लिए विफल रहता है।
माइकल बॉर्गवर्ड

1
शिशुओं के मामले में मेरे विशेष आवेदन में कोई चिंता नहीं है, लेकिन यह बहुत ही विख्यात है।
माइक बेली

-1

आपको यह ध्यान रखना होगा कि ट्रंकेशन त्रुटि एक सापेक्ष है। दो संख्याएँ लगभग समान हैं यदि उनका अंतर उनके ulp जितना बड़ा है (अंतिम स्थान पर इकाई)।

हालाँकि, यदि आप फ़्लोटिंग पॉइंट गणनाएँ करते हैं, तो आपकी त्रुटि क्षमता हर ऑपरेशन के साथ बढ़ती है (जासूसी, घटाव के साथ सावधान!), इसलिए आपकी त्रुटि सहिष्णुता तदनुसार बढ़ने की आवश्यकता है।


-1

समानता / असमानता के लिए युगल की तुलना करने का सबसे अच्छा तरीका है उनके अंतर का पूर्ण मूल्य लेना और इसकी तुलना एक छोटे से पर्याप्त (आपके संदर्भ के आधार पर) मूल्य से करना।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.