2012-11-12 22 views
16

फ़्लोटिंग-पॉइंट अंकगणितीय समस्या से निपटने का प्रयास करते समय मुझे थोड़ा उलझन में सामना करना पड़ा।जीडीबी सी ++ से अलग-अलग फ्लोटिंग-पॉइंट अंकगणित का मूल्यांकन क्यों करता है?

पहला, कोड। मैं इस उदाहरण में मेरी समस्या का सार आसुत है:

#include <iostream> 
#include <iomanip> 

using namespace std; 
typedef union {long long ll; double d;} bindouble; 

int main(int argc, char** argv) { 
    bindouble y, z, tau, xinum, xiden; 
    y.d = 1.0d; 
    z.ll = 0x3fc5f8e2f0686eee; // double 0.17165791262311053 
    tau.ll = 0x3fab51c5e0bf9ef7; // double 0.053358253178712838 
    // xinum = double 0.16249854626123722 (0x3fc4ccc09aeb769a) 
    xinum.d = y.d * (z.d - tau.d) - tau.d * (z.d - 1); 
    // xiden = double 0.16249854626123725 (0x3fc4ccc09aeb769b) 
    xiden.d = z.d * (1 - tau.d); 
    cout << hex << xinum.ll << endl << xiden.ll << endl; 
} 

xinum और xiden एक ही मूल्य है जाना चाहिए (जब y == 1), लेकिन फ्लोटिंग प्वाइंट roundoff त्रुटि के कारण वे नहीं है। वह हिस्सा मुझे मिलता है।

सवाल उठ गया जब मैंने इस कोड को (वास्तव में, मेरा वास्तविक कार्यक्रम) जीडीबी के माध्यम से विसंगति को ट्रैक करने के लिए चलाया। अगर मैं GDB का उपयोग मूल्यांकन कोड में किया पुन: पेश करने में, यह xiden के लिए एक अलग परिणाम देता है:

$ gdb mathtest 
GNU gdb (Gentoo 7.5 p1) 7.5 
... 
This GDB was configured as "x86_64-pc-linux-gnu". 
... 
(gdb) break 16 
Breakpoint 1 at 0x4008ef: file mathtest.cpp, line 16. 
(gdb) run 
Starting program: /home/diazona/tmp/mathtest 
... 
Breakpoint 1, main (argc=1, argv=0x7fffffffd5f8) at mathtest.cpp:16 
16   cout << hex << xinum.ll << endl << xiden.ll << endl; 
(gdb) print xiden.d 
$1 = 0.16249854626123725 
(gdb) print z.d * (1 - tau.d) 
$2 = 0.16249854626123722 

आपको लगता है कि अगर मैं पूछना z.d * (1 - tau.d) गणना करने के लिए GDB पर ध्यान देंगे, यह .16249854626123722 (0x3fc4ccc09aeb769a) देता है, जबकि वास्तविक सी ++ कोड जो प्रोग्राम में एक ही चीज़ की गणना करता है 0.16249854626123725 (0x3fc4ccc09aeb769b) देता है। तो जीडीबी फ्लोटिंग-पॉइंट अंकगणितीय के लिए एक अलग मूल्यांकन मॉडल का उपयोग करना चाहिए। क्या कोई इस पर कुछ और प्रकाश डाल सकता है? जीडीबी का मूल्यांकन मेरे प्रोसेसर के मूल्यांकन से अलग कैसे है?

मैंने this related question पर देखा था कि जीडीबी sqrt(3) से 0 का मूल्यांकन करने के बारे में पूछ रहा है, लेकिन यह वही बात नहीं होनी चाहिए क्योंकि इसमें कोई फ़ंक्शन कॉल शामिल नहीं है।

उत्तर

2

यह प्रोसेसर बनाम जीडीबी नहीं है, यह मेमोरी प्रोसेसर बनाम है। एक्स 64 प्रोसेसर वास्तव में स्मृति की तुलना में सटीकता के अधिक बिट्स स्टोर करता है (80 बिट बनाम 64 बिट्स)। जब तक यह सीपीयू और रजिस्टरों में रहता है, यह सटीकता के 80ish बिट्स को बरकरार रखता है, लेकिन जब यह स्मृति में भेजा जाता है तो यह निर्धारित करेगा कि यह कब और कैसे गोल हो जाता है। यदि जीडीबी सीपीयू से सभी अंतःक्रियात्मक गणना परिणामों को भेजता है (मुझे कोई जानकारी नहीं है कि यह मामला है, या कहीं भी करीब है), तो यह पर चरण पर गोल करेगा, जिससे थोड़ा अलग परिणाम सामने आते हैं।

5

ऐसा इसलिए हो सकता है क्योंकि x86 एफपीयू 80 बिट्स सटीकता के लिए रजिस्टरों में काम करता है, लेकिन जब मान स्मृति में संग्रहीत किया जाता है तो 64 बिट्स तक गोल होता है। जीडीबी (व्याख्या) गणना के हर चरण पर स्मृति को संग्रहीत करेगा।

+0

दरअसल, जीडीबी का परिणाम गणितीय रूप से अधिक सही है, इसलिए ऐसा लगता है कि जीडीबी एफपीयू की बड़ी परिशुद्धता का उपयोग करता है, जबकि जी ++ शायद एसएसई निर्देशों का उपयोग करता है। –

4

जीडीबी की रनटाइम अभिव्यक्ति मूल्यांकन प्रणाली निश्चित रूप से आपके फ़्लोटिंग पॉइंट ऑपरेशंस के लिए एक ही प्रभावी मशीन कोड को निष्पादित करने की गारंटी नहीं है, जो आपके कंपाइलर द्वारा उत्पन्न एकमात्र प्रतीकात्मक अभिव्यक्ति के परिणाम की गणना करने के लिए अनुकूलित और पुनर्वित्तित मशीन कोड है। दरअसल, यह गारंटी दी जाती है कि दिए गए अभिव्यक्ति z.d * (1 - tau.d) के मान की गणना करने के लिए उसी मशीन कोड को निष्पादित न करें, क्योंकि इसे आपके प्रोग्राम का सबसेट माना जा सकता है जिसके लिए अलग-अलग अभिव्यक्ति मूल्यांकन कुछ मनमाने ढंग से रनटाइम पर किया जाता है, "प्रतीकात्मक रूप से सही" मार्ग।

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

आपको बहुत अंतर्दृष्टि नहीं मिलेगी कि क्यों जीडीबी के रनटाइम मूल्यांकनकर्ता ने जीडीबी स्रोत कोड, सेटिंग्स बनाने, और अपने स्वयं के संकलन-समय उत्पन्न कोड में गहरी अंतर्दृष्टि के साथ किए गए निर्देशों को निष्पादित किया।

आप की एक विचार के लिए अपने प्रक्रिया के लिए उत्पन्न विधानसभा पर जा पहुंचा सकता है कि कैसे में z, tau और [विपरीत] xiden काम अंतिम भंडार। फ्लोटिंग-पॉइंट ऑपरेशंस के लिए डेटा प्रवाह उन स्टोरों की ओर अग्रसर होता है, ऐसा लगता है कि ऐसा नहीं लगता है।

अधिक आसान, कोड संकलन को सभी संकलक अनुकूलन (उदाहरण के लिए, -O0 जीसीसी पर) अक्षम करके और कोई अस्थायी/स्वचालित चर का उपयोग करने के लिए फ़्लोटिंग-पॉइंट एक्सप्रेशन को पुन: लिखकर अधिक दृढ़ संकल्प बनाने का प्रयास करें। फिर जीडीबी में हर पंक्ति को तोड़ें और तुलना करें।

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

संबंधित मुद्दे