Math.round (0.49999999999999994) 1 क्यों लौटाता है?


567

निम्नलिखित कार्यक्रम में आप देख सकते हैं कि प्रत्येक मूल्य थोड़ा कम है .5, केवल छोड़कर, गोल है 0.5

for (int i = 10; i >= 0; i--) {
    long l = Double.doubleToLongBits(i + 0.5);
    double x;
    do {
        x = Double.longBitsToDouble(l);
        System.out.println(x + " rounded is " + Math.round(x));
        l--;
    } while (Math.round(x) > i);
}

प्रिंट

10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 1
0.4999999999999999 rounded is 0

मैं जावा 6 अपडेट 31 का उपयोग कर रहा हूं।


1
जावा 1.7.0 पर यह ठीक काम करता है i.imgur.com/hZeqx.png
कॉफी

2
@ एडेल: ओली के जवाब पर मेरी टिप्पणी देखें , ऐसा लगता है कि जावा 6 इस तरह से लागू होता है (और दस्तावेज जो यह करता है ) एक तरह 0.5से संख्या को जोड़कर और फिर उपयोग करके सटीक नुकसान का कारण बन सकता है floor; जावा 7 अब इसे उस तरह से दस्तावेज नहीं करता है (संभवतः / उम्मीद है क्योंकि उन्होंने इसे तय किया है)।
टीजे क्राउडर

1
मैंने लिखा एक परीक्षण कार्यक्रम में यह एक बग था। ;)
पीटर लॉरी

1
एक और उदाहरण जो फ्लोटिंग पॉइंट वैल्यू दिखाता है, अंकित मूल्य पर नहीं लिया जा सकता है।
माइकल रॉय

1
इसके बारे में सोचने के बाद। मुझे कोई समस्या नहीं दिख रही है। 0.49999999999999994 0.5 से कम सबसे छोटी अभ्यावेदन संख्या से बड़ा है, और दशमलव मानव-पठनीय रूप में प्रतिनिधित्व स्वयं एक अनुमान है जो हमें बेवकूफ बनाने की कोशिश कर रहा है।
माइकल रॉय

जवाबों:


574

सारांश

जावा 6 में (और संभवतः पहले), के round(x)रूप में कार्यान्वित किया जाता है floor(x+0.5)1 यह एक विनिर्देश बग है, ठीक इस एक रोग संबंधी मामले के लिए। 2 जावा 7 अब इस टूटे हुए कार्यान्वयन को अनिवार्य नहीं करता है। 3

समस्या

0.5 + 0.49999999999999994 दोहरी सटीकता में 1 बिल्कुल है:

static void print(double d) {
    System.out.printf("%016x\n", Double.doubleToLongBits(d));
}

public static void main(String args[]) {
    double a = 0.5;
    double b = 0.49999999999999994;

    print(a);      // 3fe0000000000000
    print(b);      // 3fdfffffffffffff
    print(a+b);    // 3ff0000000000000
    print(1.0);    // 3ff0000000000000
}

ऐसा इसलिए है क्योंकि 0.49999999999999994 में 0.5 से छोटा एक्सपोनेंट है, इसलिए जब उन्हें जोड़ा जाता है, तो उसका मंटिसा शिफ्ट हो जाता है, और ULP बड़ा हो जाता है।

समाधान

जावा 7 के बाद से, OpenJDK (उदाहरण के लिए) इसे इस प्रकार लागू करता है: 4

public static long round(double a) {
    if (a != 0x1.fffffffffffffp-2) // greatest double value less than 0.5
        return (long)floor(a + 0.5d);
    else
        return 0;
}

1. http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#round%28double%29

2. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6430675 (इसे खोजने के लिए @SimonNickerson को क्रेडिट)

3. http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#round%28double%29

4. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/java/lang/Math.java#Math .the%28double%29


मैं उस परिभाषा को नहीं देख पा roundमें के लिए जावाडोकMath.round या का अवलोकन में Mathवर्ग।
टीजे क्राउडर

3
@ ओली: ओह अब यह दिलचस्प है, उन्होंने जावा 7 (डॉक्स से जुड़ा हुआ) के लिए उस बिट को बाहर निकाल लिया - शायद इस तरह के विषम व्यवहार को सटीक नुकसान को ट्रिगर करके इस तरह के अजीब व्यवहार से बचने के लिए।
टीजे क्राउडर

@TJCrowder: हाँ, यह दिलचस्प है। क्या आपको पता है कि व्यक्तिगत जावा संस्करणों के लिए किसी भी तरह के "रिलीज़ नोट" / "सुधार" डॉक्टर हैं, ताकि हम इस धारणा को सत्यापित कर सकें?
ओलिवर चार्ल्सवर्थ


1
मैं मदद नहीं कर सकता, लेकिन लगता है कि यह फिक्स केवल कॉस्मेटिक है, क्योंकि शून्य सबसे अधिक दिखाई देता है। इस राउंडिंग त्रुटि से प्रभावित कई अन्य फ़्लोटिंग पॉइंट मानों पर कोई संदेह नहीं है।
माइकल रॉय

232

यह एक ज्ञात बग ( जावा बग 6430675) प्रतीत होता है : Math.round में 0x1.fffffffffffp-2 के लिए आश्चर्यजनक व्यवहार है जो जावा 7 में तय किया गया है।


5
+1: अच्छा मिल गया! जावा 6 और 7 के बीच प्रलेखन में अंतर के साथ संबंध में जैसा कि मेरे उत्तर में बताया गया है।
ओलिवर चार्ल्सवर्थ


83

JDK 6 में स्रोत कोड:

public static long round(double a) {
    return (long)Math.floor(a + 0.5d);
}

JDK 7 में स्रोत कोड:

public static long round(double a) {
    if (a != 0x1.fffffffffffffp-2) {
        // a is not the greatest double value less than 0.5
        return (long)Math.floor(a + 0.5d);
    } else {
        return 0;
    }
}

जब मूल्य 0.49999999999999994d है, JDK 6 में, यह फोन करेगा मंजिल और इसलिए 1 दिखाए, लेकिन JDK 7 में, ifहालत जाँच कर रहा है संख्या सबसे बड़ी डबल मूल्य से कम 0.5 है या नहीं। जैसा कि इस मामले में संख्या 0.5 से कम नहीं सबसे बड़ा दोहरा मूल्य है, इसलिएelse ब्लॉक 0 देता है।

आप 0.49999999999999999d की कोशिश कर सकते हैं, जो 1 लौटेगा, लेकिन 0 नहीं, क्योंकि यह 0.5 से कम सबसे बड़ा दोहरा मूल्य है।


तब 1.499999999999999994 के साथ क्या होता है? 2 रिटर्न? इसे 1 वापस करना चाहिए, लेकिन यह आपको पहले की तरह ही त्रुटि मिलना चाहिए, लेकिन 1. के साथ?
एमएमएम

6
1.499999999999999994 डबल-सटीक फ्लोटिंग-पॉइंट में प्रतिनिधित्व नहीं किया जा सकता है। 1.4999999999999998 सबसे छोटा डबल 1.5 से कम है। जैसा कि आप सवाल से देख सकते हैं, floorविधि इसे सही ढंग से गोल करती है।
ऑरेंजडॉग

26

मैंने JDK 1.6 32-बिट पर समान प्राप्त किया है, लेकिन जावा 7 64-बिट पर मुझे 0.49999999999999994 के लिए 0 मिला है, जो गोल है 0 है और अंतिम पंक्ति मुद्रित नहीं है। यह एक वीएम मुद्दा लगता है, हालांकि, फ्लोटिंग पॉइंट्स का उपयोग करते हुए, आपको विभिन्न वातावरणों (सीपीयू, 32- या 64-बिट मोड) पर परिणामों को थोड़ा अलग करने की उम्मीद करनी चाहिए।

और, roundमैट्रिसेस आदि का उपयोग करते समय या इनरेटिंग करते समय, ये बिट्स एक बड़ा अंतर ला सकते हैं।

x64 आउटपुट:

10.5 rounded is 11
10.499999999999998 rounded is 10
9.5 rounded is 10
9.499999999999998 rounded is 9
8.5 rounded is 9
8.499999999999998 rounded is 8
7.5 rounded is 8
7.499999999999999 rounded is 7
6.5 rounded is 7
6.499999999999999 rounded is 6
5.5 rounded is 6
5.499999999999999 rounded is 5
4.5 rounded is 5
4.499999999999999 rounded is 4
3.5 rounded is 4
3.4999999999999996 rounded is 3
2.5 rounded is 3
2.4999999999999996 rounded is 2
1.5 rounded is 2
1.4999999999999998 rounded is 1
0.5 rounded is 1
0.49999999999999994 rounded is 0

जावा 7 में (इसका परीक्षण करने के लिए आप जिस संस्करण का उपयोग कर रहे हैं) बग तय हो गया है।
Iván Pérez

1
मुझे लगता है कि आपका मतलब 32 बिट था। मुझे संदेह है कि en.wikipedia.org/wiki/ZEBRA_%28computer%29 जावा चला सकता है और मुझे संदेह है कि 33 बिट मशीन आई है।
चक्स

@ स्पष्ट रूप से, क्योंकि मैंने पहले 32 बिट लिखा है :)
डेन्यूबियन सेलर

11

इसके बाद का उत्तर Oracle बग रिपोर्ट 6430675 का एक अंश है । पूर्ण विवरण के लिए रिपोर्ट पर जाएँ।

तरीके {मैथ, स्ट्रिक्टमैथ.ऑर्ग को ऑपरेशनल रूप से परिभाषित किया गया है

(long)Math.floor(a + 0.5d)

दोहरे तर्क के लिए। जबकि यह परिभाषा आमतौर पर उम्मीद के मुताबिक काम करती है, यह 0 के बजाय 0x1.fffffffffffp-2 (0.49999999999999994) के लिए 1 का आश्चर्यजनक परिणाम देती है।

मान 0.49999999999999994 सबसे कम फ्लोटिंग-पॉइंट मान 0.5 से कम है। हेक्साडेसिमल फ्लोटिंग-पॉइंट शाब्दिक के रूप में इसका मान 0x1.fffffffffffp-2 है, जो कि (2 - 2 ^ 52) * 2 ^ -2 के बराबर है। == (0.5 - 2 ^ 54)। इसलिए, राशि का सही मूल्य

(0.5 - 2^54) + 0.5

1 - 2 ^ 54 है। यह दो आसन्न फ़्लोटिंग-पॉइंट नंबरों (1 - 2 ^ 53) और 1. के बीच में है। IEEE 754 अंकगणितीय राउंड में जावा द्वारा उपयोग किए जाने वाले निकटतम राउंडिंग मोड के लिए, जब फ़्लोटिंग-पॉइंट परिणाम अक्षम होता है, तो दोनों के करीब प्रतिनिधित्व योग्य फ़्लोटिंग-पॉइंट मान जो सटीक परिणाम को ब्रैकेट करते हैं; यदि दोनों मूल्य समान रूप से पास हैं, तो वह जो इसका अंतिम बिट शून्य है, वापस आ गया है। इस स्थिति में ऐड से सही रिटर्न वैल्यू 1 है, न कि सबसे बड़ी वैल्यू 1 से कम है।

जबकि विधि परिभाषित के रूप में चल रही है, इस इनपुट पर व्यवहार बहुत ही आश्चर्यजनक है; विनिर्देश को कुछ और संशोधित किया जा सकता है जैसे "राउंड टू द क्लीयर लॉन्ग, राउंडिंग टाई अप," जो इस इनपुट पर व्यवहार को बदलने की अनुमति देगा।

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