1.0 std से एक मान्य आउटपुट है :: Gener_canonical?


124

मैंने हमेशा सोचा था कि यादृच्छिक संख्या शून्य और एक के बीच होगी, बिना1 , यानी वे आधे खुले अंतराल [0,1) से संख्या हैं। इस बात की पुष्टि cppreference.com पर किया गया हैstd::generate_canonical

हालाँकि, जब मैं निम्नलिखित कार्यक्रम चलाता हूँ:

#include <iostream>
#include <limits>
#include <random>

int main()
{
    std::mt19937 rng;

    std::seed_seq sequence{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    rng.seed(sequence);
    rng.discard(12 * 629143 + 6);

    float random = std::generate_canonical<float,
                   std::numeric_limits<float>::digits>(rng);

    if (random == 1.0f)
    {
        std::cout << "Bug!\n";
    }

    return 0;
}

यह मुझे निम्न आउटपुट देता है:

Bug!

यानी यह मुझे एक आदर्श बनाता है 1, जो मेरे एमसी एकीकरण में समस्याएं पैदा करता है। क्या वह वैध व्यवहार है या मेरी तरफ से कोई त्रुटि है? यह G ++ 4.7.3 के साथ एक ही आउटपुट देता है

g++ -std=c++11 test.c && ./a.out

और क्लेंग 3.3

clang++ -stdlib=libc++ -std=c++11 test.c && ./a.out

यदि यह सही व्यवहार है, तो मैं कैसे बच सकता हूं 1?

संपादन 1 : G ++ से git एक ही समस्या से ग्रस्त है। मै त्यार हूँ

commit baf369d7a57fb4d0d5897b02549c3517bb8800fd
Date:   Mon Sep 1 08:26:51 2014 +0000

और के साथ संकलन ~/temp/prefix/bin/c++ -std=c++11 -Wl,-rpath,/home/cschwan/temp/prefix/lib64 test.c && ./a.outएक ही उत्पादन, lddपैदावार देता है

linux-vdso.so.1 (0x00007fff39d0d000)
libstdc++.so.6 => /home/cschwan/temp/prefix/lib64/libstdc++.so.6 (0x00007f123d785000)
libm.so.6 => /lib64/libm.so.6 (0x000000317ea00000)
libgcc_s.so.1 => /home/cschwan/temp/prefix/lib64/libgcc_s.so.1 (0x00007f123d54e000)
libc.so.6 => /lib64/libc.so.6 (0x000000317e600000)
/lib64/ld-linux-x86-64.so.2 (0x000000317e200000)

संपादन २ : मैंने यहाँ व्यवहार की सूचना दी: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63176

संपादन 3 : क्लैंग टीम समस्या के बारे में जानती है: http://llvm.org/bugs/show_bug.cgi?id=18767


21
@ डेविड 1.f == 1.fसभी मामलों में जीवंत (सभी मामले क्या हैं? मैंने भी कोई चर नहीं देखा है 1.f == 1.f; यहां केवल एक ही मामला है: 1.f == 1.fऔर वह हमेशा के लिए है true)। कृपया इस मिथक को और न फैलाएं। फ्लोटिंग पॉइंट की तुलना हमेशा सटीक होती है।
आर। मार्टिनो फर्नांडिस 15

15
@DavidLively: नहीं, यह नहीं है। तुलना हमेशा सटीक होती है। यह आपके ऑपरेंड्स हैं जो सटीक नहीं हो सकते हैं यदि उनकी गणना की जाए और वे शाब्दिक न हों।
ऑर्बिट में

2
@ गालिक 1.0 से नीचे कोई भी सकारात्मक संख्या एक वैध परिणाम है। 1.0 नहीं है। यह इतना सरल है। गोलाई अप्रासंगिक है: कोड एक यादृच्छिक संख्या प्राप्त करता है और उस पर कोई गोलाई नहीं करता है।
आर। मार्टिनो फर्नांडीस

7
@DavidLively वह कह रहा है कि 1.0 के बराबर की तुलना में केवल एक ही मूल्य है। वह मान 1.0 है। 1.0 के करीब मान 1.0 के बराबर नहीं हैं। इससे कोई फर्क नहीं पड़ता कि पीढ़ी क्या कार्य करती है: यदि यह 1.0 देता है तो यह 1.0 के बराबर होगा। यदि यह 1.0 वापस नहीं आता है तो यह 1.0 के बराबर तुलना नहीं करेगा। आपका उदाहरण abs(random - 1.f) < numeric_limits<float>::epsilonचेक का उपयोग करता है यदि परिणाम 1.0 के करीब है , जो इस संदर्भ में पूरी तरह से गलत है: 1.0 के करीब संख्याएं हैं जो यहां वैध परिणाम हैं, अर्थात्, वे सभी जो 1.0 से कम हैं।
आर। मार्टिनो फर्नांडीस

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

जवाबों:


121

समस्या std::mt19937( std::uint_fast32_t) के कोडोमैन से मैपिंग में है float; मानक द्वारा वर्णित एल्गोरिथ्म गलत परिणाम देता है (एल्गोरिथ्म के आउटपुट के अपने विवरण के साथ असंगत) जब सटीक का नुकसान होता है यदि वर्तमान IEEE754 राउंडिंग मोड राउंड-नेगेटिव-इनफिनिटी के अलावा कुछ और है (ध्यान दें कि डिफ़ॉल्ट गोल है) करने वाली निकटतम)।

आपके बीज के साथ mt19937 का 7549723 वां आउटपुट 4294967257 ( 0xffffffd9u) है, जिसे जब 32-बिट फ्लोट के लिए गोल किया जाता है 0x1p+32, जो कि mt19937 के अधिकतम मूल्य के बराबर है, 4294967255 ( 0xffffffffu) जब वह भी 32-बिट फ्लोट के लिए गोल है।

मानक सही व्यवहार सुनिश्चित कर सकता है अगर यह निर्दिष्ट करने के लिए थे कि जब URNG के उत्पादन से परिवर्तित करने RealTypeकी generate_canonical, राउंडिंग नकारात्मक अनंत की ओर से किया जा रहा है; यह इस मामले में एक सही परिणाम देगा। QOI के रूप में, यह परिवर्तन करने के लिए libstdc ++ के लिए अच्छा होगा।

इस परिवर्तन के साथ, 1.0अब उत्पन्न नहीं होगा; इसके बजाय सीमा मानों 0x1.fffffep-Nको 0 < N <= 8अधिक बार (लगभग 2^(8 - N - 32)प्रति N, MT19937 के वास्तविक वितरण के आधार पर) उत्पन्न किया जाएगा ।

मैं का उपयोग नहीं करने के लिए सिफारिश करेंगे floatसाथ std::generate_canonicalसीधे; बल्कि doubleनकारात्मक अनंतता की ओर और फिर दौर उत्पन्न करें :

    double rd = std::generate_canonical<double,
        std::numeric_limits<float>::digits>(rng);
    float rf = rd;
    if (rf > rd) {
      rf = std::nextafter(rf, -std::numeric_limits<float>::infinity());
    }

इस समस्या के साथ भी हो सकता है std::uniform_real_distribution<float>; समाधान समान है, वितरण को विशेषज्ञ बनाने doubleऔर परिणाम को नकारात्मक अनंतता की ओर गोल करने के लिए float


2
@user कार्यान्वयन की गुणवत्ता - सभी चीजें जो एक अनुरूप कार्यान्वयन को दूसरे उदाहरण से बेहतर बनाती हैं, जैसे कि किनारे के मामलों में व्यवहार, त्रुटि संदेशों की सहायता।
20

2
@ सुपरकार्ट: थोड़ा सा खोदने के लिए, वास्तव में छोटे कोणों के लिए साइन कार्यों को यथासंभव सटीक बनाने की कोशिश करने के लिए अच्छे कारण हैं, जैसे कि पाप (x) में छोटी त्रुटियां पाप (x) / x में बड़ी त्रुटियों में बदल सकती हैं (जो वास्तविक-विश्व गणना में अक्सर होता है ) जब x शून्य के करीब होता है। The के गुणकों के पास "अतिरिक्त परिशुद्धता" आमतौर पर इसका एक साइड इफेक्ट है।
इल्मेरी करोनन

1
@ इल्मारियारोन: पर्याप्त रूप से छोटे कोणों के लिए, पाप (x) केवल x है। जावा के साइन फंक्शन में मेरा स्कवॉक उन कोणों के साथ है जो पी के गुणकों के पास हैं। मैं कहता हूं कि 99% समय, जब कोड पूछता है sin(x), जो वास्तव में चाहता है वह (π / Math.PI) गुना x की साइन है। जावा को बनाए रखने वाले लोग इस बात पर जोर देते हैं कि गणित की धीमी गति से चलने वाली रिपोर्ट के लिए बेहतर है कि मैथ.पिन की साइन, than और मैथ.प्रि के बीच अंतर हो, क्योंकि इसकी वैल्यू रिपोर्ट की जाए जो कि थोड़ा कम है, इसके बावजूद कि 99% अनुप्रयोगों में बेहतर होगा ...
सुपरकाट

3
@ecatmur सुझाव; इस पोस्ट को अद्यतन करने के लिए कहा कि std::uniform_real_distribution<float>इस के परिणामस्वरूप एक ही समस्या से ग्रस्त है। (ताकि यूनिफॉर्म_रिल_डिस्ट्रिएशन की खोज करने वाले लोगों के पास यह क्यू / ए ऊपर आए)।
एमएम

1
@ecatmur, मुझे यकीन नहीं है कि आप नकारात्मक अनंत की ओर क्यों चक्कर लगाना चाहते हैं। चूंकि generate_canonicalरेंज में एक नंबर उत्पन्न करना चाहिए [0,1), और हम एक त्रुटि के बारे में बात कर रहे हैं, जहां यह 1.0 कभी-कभी उत्पन्न होता है, शून्य की ओर चक्कर लगाना उतना प्रभावी नहीं होगा?
मार्शल क्लो

39

मानक के अनुसार, 1.0मान्य नहीं है।

C ++ 11 can26.5.7.2 फंक्शन टेम्प्लेट जनरेट_कोऑनिकल

इस खंड 26.5.7.2 में वर्णित टेम्प्लेट से तात्कालिक प्रत्येक फ़ंक्शन, आपूर्ति किए गए एक समान यादृच्छिक संख्या जनरेटर के एक या अधिक इनवॉइस के परिणाम gको निर्दिष्ट RealType के एक सदस्य को मैप करता है, जैसे कि, यदि मेरे द्वारा उत्पादित मान gसमान रूप से वितरित किए जाते हैं, तात्कालिकता के परिणाम t j , 0 j t j <1 , नीचे दिए गए अनुसार समान रूप से वितरित किए जाते हैं।


25
+1 मैं ओपी के कार्यक्रम में कोई दोष नहीं देख सकता, इसलिए मैं इसे एक libstdc ++ और libc ++ बग ... कह रहा हूं, जो कि खुद को थोड़ा असंभावित लगता है, लेकिन वहां हम जाते हैं।
ऑर्बिट में

-2

मैं सिर्फ इसी तरह के प्रश्न के साथ भाग गया uniform_real_distribution, और यहाँ मैंने इस विषय पर मानक के शब्दावलियों की व्याख्या कैसे की:

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

स्टैंडर्ड का कहना है कि दोनों uniform_real_distribution<T>(0,1)(g)और generate_canonical<T,1000>(g)आधे खुले क्षेत्र [0,1) में मान चाहिए। लेकिन ये गणितीय मूल्य हैं। जब आप आधी खुली सीमा [0,1) में एक वास्तविक संख्या लेते हैं और इसे IEEE फ़्लोटिंग-पॉइंट के रूप में दर्शाते हैं, ठीक है, उस समय का एक महत्वपूर्ण अंश जो इसे पूरा करेगा T(1.0)

जब Tहै float(24 अपूर्णांश बिट्स), हम देखने की उम्मीद uniform_real_distribution<float>(0,1)(g) == 1.0f2 25 में ^ 1 समय के बारे में। Libc ++ के साथ मेरा पाशविक बल प्रयोग इस अपेक्षा की पुष्टि करता है।

template<class F>
void test(long long N, const F& get_a_float) {
    int count = 0;
    for (long long i = 0; i < N; ++i) {
        float f = get_a_float();
        if (f == 1.0f) {
            ++count;
        }
    }
    printf("Expected %d '1.0' results; got %d in practice\n", (int)(N >> 25), count);
}

int main() {
    std::mt19937 g(std::random_device{}());
    auto N = (1uLL << 29);
    test(N, [&g]() { return std::uniform_real_distribution<float>(0,1)(g); });
    test(N, [&g]() { return std::generate_canonical<float, 32>(g); });
}

उदाहरण आउटपुट:

Expected 16 '1.0' results; got 19 in practice
Expected 16 '1.0' results; got 11 in practice

जब Tहै double(53 अपूर्णांश बिट्स), हम देखने की उम्मीद uniform_real_distribution<double>(0,1)(g) == 1.02 54 में ^ 1 समय के बारे में। मेरे पास इस उम्मीद को परखने का धैर्य नहीं है। :)

मेरी समझ यह है कि यह व्यवहार ठीक है। यह "आधा-खुला-रंग" की हमारी भावना को रोक सकता है कि संख्या "1.0 से कम" लौटने का दावा करने वाला वितरण वास्तव में वापसी की संख्या जो इसके बराबर है 1.0; लेकिन वे "1.0" के दो अलग-अलग अर्थ हैं, देखें? पहला गणितीय 1.0 है; दूसरा IEEE एकल-सटीक फ़्लोटिंग-पॉइंट संख्या है 1.0। और हमें दशकों से सिखाया गया है कि फ्लोटिंग-पॉइंट नंबरों की सटीक समानता के लिए तुलना न करें।

यदि आप कभी-कभी ठीक हो जाते हैं तो आप जिस भी एल्गोरिथम को रैंडम नंबर फीड करते हैं, उसकी देखभाल नहीं हो रही है 1.0। गणितीय कार्यों को छोड़कर आप एक फ्लोटिंग-पॉइंट नंबर के साथ कुछ भी नहीं कर सकते हैं , और जैसे ही आप कुछ गणितीय ऑपरेशन करते हैं, आपके कोड को गोलाई से निपटना होगा। यहां तक ​​कि अगर आप वैध तरीके से मान सकते हैं generate_canonical<float,1000>(g) != 1.0f, तब भी आप ऐसा नहीं कर पाएंगे generate_canonical<float,1000>(g) + 1.0f != 2.0f- क्योंकि गोलाई के कारण। तुम अभी इससे दूर नहीं हो सकते; तो हम इस एकल उदाहरण में आप क्यों कर सकते हैं?


2
मैं इस दृष्टिकोण से बहुत असहमत हूं। यदि मानक आधे-खुले अंतराल से मूल्यों को निर्धारित करता है और एक कार्यान्वयन इस नियम को तोड़ता है, तो कार्यान्वयन गलत है। दुर्भाग्य से, जैसा कि परमानंद ने अपने जवाब में सही ढंग से बताया, मानक भी एल्गोरिथ्म को निर्देशित करता है जिसमें बग है। इसे आधिकारिक रूप से यहां भी मान्यता दी गई है: open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2524
सीएसचवान

@ इस्कवान: मेरी व्याख्या यह है कि कार्यान्वयन नियम को नहीं तोड़ रहा है। मानक [0,1) से मूल्यों को निर्धारित करता है; कार्यान्वयन [0,1) से मान लौटाता है; उन मूल्यों में से कुछ IEEE के लिए राउंड अप करने के लिए होते हैं, 1.0fलेकिन यह सिर्फ अपरिहार्य है जब आप उन्हें IEEE फ़्लोट में डालते हैं। यदि आप शुद्ध गणितीय परिणाम चाहते हैं, तो एक प्रतीकात्मक गणना प्रणाली का उपयोग करें; यदि आप संख्याओं का प्रतिनिधित्व करने के लिए IEEE फ़्लोटिंग-पॉइंट का उपयोग करने की कोशिश कर रहे हैं epsजो 1 के भीतर हैं , तो आप पाप की स्थिति में हैं।
क्क्सप्लसोन

हाइपोथेटिकल उदाहरण जो इस बग से टूट जाएगा: कुछ करके विभाजित करें canonical - 1.0f। हर प्रतिनिधित्व योग्य फ्लोट के लिए [0, 1.0), x-1.0fगैर-शून्य है। ठीक 1.0f के साथ, आप केवल बहुत छोटे भाजक के बजाय एक विभाजन-शून्य प्राप्त कर सकते हैं।
पीटर कॉर्डेस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.