০.০ স্ট্যান্ডার্ড :: জেনারেট_কোনোনিকাল থেকে বৈধ আউটপুট?


124

আমি সবসময় চিন্তা র্যান্ডম সংখ্যা, শূন্য এবং এক মধ্যে মিথ্যা বলব ছাড়া1 , IE তারা অর্ধ খোলা বিরতি [0,1) থেকে নম্বর আছে। এর cppreferences.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, যা আমার এমসি সংহতকরণে সমস্যা সৃষ্টি করে। এটি বৈধ আচরণ বা আমার পক্ষে কোনও ত্রুটি আছে? এটি জি ++ 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 : গিট থেকে জি ++ একই সমস্যাতে ভুগছে বলে মনে হচ্ছে। আমিও আছি

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)

2 সম্পাদনা করুন : আমি আচরণটি এখানে রিপোর্ট করেছি: 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
@ ডেভিডলিভলি: না, তা নয়। তুলনা সর্বদা সঠিক। এটি আপনার ক্রিয়াকলাপগুলি হ'ল আক্ষরিক না হলে তা গণনা করা ঠিক নাও হতে পারে ।
হালকা ঘোড়দৌড়

2
০.০ এর নীচে যেকোন ধনাত্মক সংখ্যা গালিক একটি বৈধ ফলাফল। 1.0 নয়। এটা ঐটার মতই সহজ. রাউন্ডিং অপ্রাসঙ্গিক: কোডটি একটি এলোমেলো নম্বর পায় এবং এতে কোনও রাউন্ডিং করে না।
আর মার্টিনহো ফার্নান্দেস

7
@ ডেভিডলাইভলি তিনি বলছেন যে কেবলমাত্র একটি মানই 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 এর চেয়ে কম।
আর মার্টিনহো ফার্নান্দেস

4
@ গালিক হ্যাঁ, এটি কার্যকর করতে সমস্যা হবে। তবে সেই ঝামেলাটি বাস্তবায়নকারীকে মোকাবেলা করার জন্য। ব্যবহারকারীর অবশ্যই কখনও কোনও 1.0 দেখতে হবে না এবং ব্যবহারকারীকে সর্বদা সমস্ত ফলাফলের সমান বন্টন দেখতে হবে।
আর মার্টিনহো ফার্নান্দেস

উত্তর:


121

std::mt19937( std::uint_fast32_t) এর কোডোমেন থেকে ম্যাপিংয়ে সমস্যাটি রয়েছে float; স্ট্যান্ডার্ড দ্বারা বর্ণিত অ্যালগরিদম ভুল ফলাফল দেয় (অ্যালগোরিদমের আউটপুট সম্পর্কিত বর্ণনার সাথে অসামঞ্জস্যপূর্ণ) যখন বর্তমান আইইইই 7575 রাউন্ডিং মোডটি রাউন্ড-টু-নেগেটিভ-ইনফিনিটি ব্যতীত অন্য কিছু হয় তবে দ্রষ্টব্য: ডিফল্টটি গোলাকার -to-নিকটতম)।

আপনার বীজের সাথে mt19937 এর 7549723 তম আউটপুটটি 4294967257 ( 0xffffffd9u) হয়, যা 32-বিট 0x1p+32ফ্লোটকে দেওয়া হয় যা এমটি 19937, 4294967295 ( 0xffffffffu) এর সর্বাধিক মানের সমান হয় যখন এটি 32-বিট ফ্লোটেও গোল হয়।

মান সঠিক আচরণ নিশ্চিত করতে পারে যদি এটা উল্লেখ ছিল যে, যখন URNG আউটপুট থেকে রূপান্তর RealTypeএর generate_canonical, রাউন্ডইং নেতিবাচক অনন্ত প্রতি সঞ্চালিত হবে হয়; এটি এই ক্ষেত্রে একটি সঠিক ফলাফল দিতে হবে। কিউআইআই হিসাবে, 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ফলাফলটি বিতরণকে এবং negativeণাত্মক অনন্তের দিকে ফলাফলকে বৃত্তাকারে বিশেষীকরণ করা float


2
@ বাস্তবায়নের ব্যবহারকারীর গুণমান - সমস্ত কিছু যা একটি উপকরণ বাস্তবায়ন করে তোলে অন্য উদাহরণগুলির চেয়ে কার্য সম্পাদন, প্রান্তের ক্ষেত্রে আচরণ, ত্রুটির বার্তাগুলির সহায়ক।
ইকামত্মর

2
@ সুপের্যাট: কিছুটা সন্ধান করার জন্য, ছোট কোণগুলির জন্য যথাসম্ভব যথাযথভাবে সাইন ফাংশন করার চেষ্টা করার ভাল কারণ রয়েছে, উদাহরণস্বরূপ, কারণ পাপ (x) এর ছোট ত্রুটিগুলি পাপ (x) / x (যা বড় ত্রুটিতে পরিণত হতে পারে) রিয়াল-ওয়ার্ল্ড গণনায় বেশিরভাগ সময় ঘটে থাকে ) যখন x শূন্যের কাছাকাছি থাকে। Extra এর গুণকগুলির নিকটে "অতিরিক্ত স্পষ্টতা" সাধারণত এটির একটি পার্শ্ব প্রতিক্রিয়া।
ইলমারি করোনেন

1
@ ইলমারি কারোনেন: পর্যাপ্ত ছোট কোণগুলির জন্য, পাপ (এক্স) কেবলমাত্র এক্স is জাভা এর সাইন ফাংশনে আমার স্কাউকের সাথে পাইগুলির গুণমানগুলির কাছাকাছি এমন কোণগুলির সাথে সম্পর্কিত। আমি পোস্ট করব যে 99% সময়, যখন কোড জিজ্ঞাসা করবে sin(x), আসলে এটি কী চায় তা হল (π / ম্যাথ.পিআই) সময়ের x এর সাইন। জাভা রক্ষণাবেক্ষণ করা লোকেরা জোর দিয়ে বলেছেন যে গণিতের ধীরে ধীরে ধীরে ধীরে রিপোর্ট করা ভাল যে ম্যাথ.পি.আই এর π এবং ম্যাথ.পি.আই এর মধ্যে পার্থক্য যা এটির চেয়ে কিছু কম, যদিও এটি 99% অ্যাপ্লিকেশনেই রয়েছে আরও ভাল হবে ...
সুপারক্যাট

3
@ কেটমুর পরামর্শ; std::uniform_real_distribution<float>এটির ফলাফল হিসাবে একই সমস্যা ভুগছে তা উল্লেখ করতে এই পোস্টটি আপডেট করুন update (যাতে ইউনিফর্ম_রিয়াল_ড্রিট্রিবিউশনের সন্ধানকারী লোকেরা এই প্রশ্নোত্তর আসতে পারে)।
এমএম

1
@ ক্যাটমুর, আপনি কেন নেতিবাচক অনন্তের দিকে যেতে চান তা আমি নিশ্চিত নই। যেহেতু generate_canonicalপরিসীমাটিতে একটি সংখ্যা উত্পন্ন করা উচিত [0,1), এবং আমরা এমন একটি ত্রুটির কথা বলছি যেখানে এটি মাঝে মাঝে 1.0 তৈরি করে, শূন্যের দিকে গোল করা ঠিক তেমন কার্যকর হবে না?
মার্শাল ক্লো

39

মান অনুযায়ী, 1.0বৈধ নয়।

সি ++ 11 §26.5.7.2 ফাংশন টেম্পলেট জেনারেট_কেনোনিক্যাল

এই বিভাগে বর্ণিত টেমপ্লেট থেকে প্রতিটি ফাংশন ইনস্ট্যান্ট করা g26.5.7.2 নির্দিষ্ট রিয়েলটাইপের এক সদস্যকে সরবরাহ করা অভিন্ন র্যান্ডম নম্বর জেনারেটরের এক বা একাধিক অনুরোধের ফলাফলকে ম্যাপ করে , যদি আমি উত্পাদিত মানগুলি একতভাবে gবিতরণ করা হয় তবে ইনস্ট্যান্টেশন এর ফলাফল t j , 0 ≤ t j <1 , নীচে নির্দিষ্ট হিসাবে যতটা সম্ভব সমানভাবে বিতরণ করা হয়।


25
+1 আমি ওপির প্রোগ্রামে কোনও ত্রুটি দেখতে পাচ্ছি না, তাই আমি এটিকে একটি লিবিস্টডিসি ++ এবং লিবিসি ++ বাগ বলছি ... যা নিজেই কিছুটা অসম্ভব বলে মনে হয়, তবে আমরা সেখানে চলে যাই।
হালকাতা রেস

-2

আমি স্রেফ একটি অনুরূপ প্রশ্নের মধ্যে দৌড়েছি uniform_real_distribution, এবং এখানে আমি কীভাবে স্ট্যান্ডার্ডের পার্সিমোনিয়াস শব্দটিকে এই বিষয়ে ব্যাখ্যা করি:

স্ট্যান্ডার্ড সবসময় পরিপ্রেক্ষিতে ম্যাথ ফাংশন সংজ্ঞায়িত গণিত কখনোই আইইইই ফ্লোটিং পয়েন্ট নিরিখে (কারণ স্ট্যান্ডার্ড এখনও ভান করে ফ্লোটিং পয়েন্ট নাও করতে পারে গড় আইইইই ফ্লোটিং পয়েন্ট)। সুতরাং, আপনি যে কোনও সময় স্ট্যান্ডার্ডে গাণিতিক শব্দ দেখতে পাচ্ছেন, এটি আইইইই নয়, আসল গণিতের কথা বলছে ।

স্ট্যান্ডার্ড বলছেন যে উভয় uniform_real_distribution<T>(0,1)(g)এবং generate_canonical<T,1000>(g)অর্ধ-খোলা পরিসর [0,1) মান ফেরত পাঠাবেন। তবে এগুলি গাণিতিক মান। আপনি যখন অর্ধ-খোলার পরিসীমা [0,1) এ আসল নম্বর নেন এবং এটি আইইইই ভাসমান-পয়েন্ট হিসাবে উপস্থাপন করেন, ভাল, সময়ের একটি উল্লেখযোগ্য ভগ্নাংশ এটি শেষ হবে 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। এবং কয়েক দশক ধরে আমাদের শেখানো হয়েছে সঠিক সমতার জন্য ভাসমান-পয়েন্ট সংখ্যাগুলির তুলনা না করা।

আপনি যে কোনও অ্যালগরিদমকে এলোমেলো নম্বরগুলিতে খাওয়ান তা কখনই ঠিকঠাক হয়ে ওঠে তা যত্ন নেবে না 1.0। সেখানে কিছুই আপনাকে পারে না গাণিতিক অপারেশন ছাড়া একটি ফ্লোটিং পয়েন্ট নম্বর দিয়ে এবং খুব শীঘ্রই হিসাবে হিসাবে আপনি কিছু গাণিতিক অপারেশন করবেন, আপনার কোড rounding সাথে মোকাবিলা করতে হবে। আপনি বৈধভাবে এটি ধরে নিতে পারলেওgenerate_canonical<float,1000>(g) != 1.0f , আপনি এখনও এটি ধরে নিতে সক্ষম হবেন না generate_canonical<float,1000>(g) + 1.0f != 2.0f- গোলাকার কারণে। আপনি কেবল এ থেকে দূরে যেতে পারবেন না; তাহলে আমরা কেন এই একক দৃষ্টিতে ভান করব?


2
আমি এই দৃ .়তার সাথে দৃ strongly়ভাবে একমত নই। যদি স্ট্যান্ডার্ডটি অর্ধ-খোলা ব্যবধান থেকে মানগুলি নির্ধারণ করে এবং একটি বাস্তবায়ন এই নিয়মটি ভঙ্গ করে, বাস্তবায়নটি ভুল। দুর্ভাগ্যক্রমে, একাটামুর তার উত্তরে সঠিকভাবে উল্লেখ করেছেন, মানটি একটি অ্যালগরিদমকেও নির্দেশ করে যার একটি বাগ রয়েছে has এটি সরকারীভাবে এখানেও স্বীকৃত: open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2524
cschwan

@ সিছুওয়ান: আমার ব্যাখ্যাটি হ'ল বাস্তবায়ন বিধি ভঙ্গ করছে না । মান [0,1) থেকে মান নির্ধারণ করে; বাস্তবায়ন [0,1) থেকে মানগুলি ফেরত দেয়; এই মানগুলির মধ্যে কিছু আইইইই পর্যন্ত হয় 1.0fতবে আপনি যখন আইইইই ফ্লোটে কাস্ট করেন তখন তা কেবল অনিবার্য। আপনি যদি খাঁটি গাণিতিক ফলাফল চান, একটি প্রতীকী গণনা সিস্টেম ব্যবহার করুন; যদি আপনি eps1 এর মধ্যে থাকা সংখ্যার প্রতিনিধিত্ব করতে আইইইই ফ্লোটিং-পয়েন্ট ব্যবহার করার চেষ্টা করছেন , আপনি পাপ অবস্থায় আছেন।
কুক্সপ্লসোন

হাইপোথিটিক্যাল উদাহরণ যা এই বাগ দ্বারা ভেঙে যাবে: কিছু দিয়ে ভাগ করুন canonical - 1.0f। প্রতিটি প্রতিনিধিত্বমূলক ভাসমানের জন্য [0, 1.0), x-1.0fশূন্য নয়। ঠিক 1.0f এর সাহায্যে, আপনি খুব ক্ষুদ্র ক্ষুদ্র বিভাজনের পরিবর্তে শূন্যের বিভাজন পেতে পারেন।
পিটার কর্ডেস
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.