لماذا لا `` int pow (int base ، int outponent) `في مكتبات C ++ القياسية؟

StackOverflow https://stackoverflow.com/questions/2398442

سؤال

أشعر أنني يجب أن أكون غير قادر على العثور عليه. هل هناك أي سبب يدل على C ++ pow الوظيفة لا تنفذ وظيفة "القوة" لأي شيء باستثناء floatرمل doubleس؟

أعلم أن التنفيذ تافه ، أشعر فقط أنني أقوم بعمل يجب أن يكون في مكتبة قياسية. إن وظيفة القوة القوية (أي تعاملات في التدفق بطريقة متسقة وصريحة) ليست ممتعة للكتابة.

هل كانت مفيدة؟

المحلول

في الواقع ، فإنه يفعل.

نظرًا لأن C ++ 11 هناك تنفيذ محول لـ pow(int, int) --- وحتى الحالات الأكثر عمومية ، انظر (7) فيhttp://en.cppreference.com/w/cpp/numeric/math/pow


تحرير: قد يجادل purists أن هذا غير صحيح ، حيث يوجد في الواقع "تم الترويج" للكتابة المستخدمة. بطريقة أو بأخرى ، يحصل أحدهم على صواب int النتيجة ، أو خطأ ، int المعلمات.

نصائح أخرى

اعتبارا من C++11, ، تمت إضافة حالات خاصة إلى مجموعة وظائف الطاقة (وغيرها). C++11 [c.math] /11 الدول ، بعد سرد كل float/double/long double الحمولة الزائدة (تركيزي ، وأعيد صياغتها):

علاوة على ذلك ، يجب أن يكون هناك حمولات إضافية كافية لضمان ، إذا كانت أي حجة تتوافق مع أ double المعلمة لها نوع double أو نوع عدد صحيح ، ثم جميع الوسائط المقابلة ل double يتم إلقاء المعلمات بشكل فعال ل double.

لذلك ، في الأساس ، سيتم ترقية معلمات عدد صحيح إلى الزوجي لأداء العملية.


قبل ذ لك C++11 (وهو ما كان عندما تم طرح سؤالك) ، لم يكن هناك حمولة عصر عدد صحيح.

منذ أن لم أكن مرتبطًا بشكل وثيق مع المبدعين C ولا C++ في أيام إنشائها (على الرغم من أنني صباحا إلى حد ما) ، ولا جزء من لجان ANSI/ISO التي خلقت المعايير ، وهذا بالضرورة رأي من جانبي. أود أن أعتقد أنه كذلك أُبلغ الرأي ولكن ، كما ستخبرك زوجتي (في كثير من الأحيان ودون الكثير من التشجيع) ، كنت مخطئًا من قبل :-)

الافتراض ، لما يستحق ، يتبع.

أنا مشتبه فيه أن السبب في ما قبل الانسان الأصلي C لم يكن لديك هذه الميزة لأنها غير ضرورية تماما. أولاً ، كانت هناك بالفعل طريقة جيدة تمامًا لفعل قوى عدد صحيح (مع الزوجي ومن ثم ببساطة تحويل مرة أخرى إلى عدد صحيح ، والتحقق من تدفق عدد صحيح وتدفق قبل التحويل).

ثانياً ، شيء آخر يجب أن تتذكره هو أن القصد الأصلي لـ C كان كما الأنظمة لغة البرمجة ، ومن المشكوك فيها ما إذا كانت النقطة العائمة مرغوبة في تلك الساحة على الإطلاق.

نظرًا لأن إحدى حالات الاستخدام الأولية كانت ترميز UNIX ، فإن النقطة العائمة كانت بجوار عديمة الفائدة. BCPL ، الذي استند إليه C ، لم يكن له أي فائدة أيضًا للسلطات (لم يكن لديه نقطة عائمة على الإطلاق ، من الذاكرة).

جانبا ، من المحتمل أن يكون مشغل الطاقة المتكامل مشغلًا ثنائيًا بدلاً من مكالمة مكتبة. لا تضيف اثنين من الأعداد الصحيحة مع x = add (y, z) ولكن مع x = y + z - جزء من اللغة المناسبة بدلا من المكتبة.

ثالثًا ، نظرًا لأن تنفيذ القوة المتكاملة أمر تافلي نسبيًا ، فمن المؤكد تقريبًا أن مطوري اللغة سيستخدمون وقتهم بشكل أفضل في توفير المزيد من الأشياء المفيدة (انظر التعليقات أدناه على تكلفة الفرصة البديلة).

هذا هو أيضا ذات صلة للأصل C++. نظرًا لأن التنفيذ الأصلي كان مجرد مترجم تم إنتاجه C رمز ، حملت على العديد من سمات C. كانت نيتها الأصلية هي الفئات C-With ، وليس C-With-Plus-A-Little-Of-Extra-Math-Stuff.

لماذا لم تتم إضافتها إلى المعايير من قبل C++11, ، عليك أن تتذكر أن هيئات وضع المعايير لديها إرشادات محددة لمتابعة. على سبيل المثال ، Ansi C تم تكليفه على وجه التحديد بتدوين الممارسة الحالية ، ليس لإنشاء لغة جديدة. خلاف ذلك ، كان يمكن أن يكونوا مجنونين وأعطونا ADA :-)

في وقت لاحق تكرارات هذا المعيار لها أيضًا إرشادات محددة ويمكن العثور عليها في المستندات المنطقية (الأساس المنطقي لسبب اتخاذ اللجنة قرارات معينة ، وليس الأساس المنطقي للغة نفسها).

على سبيل المثال C99 المستند المنطقي يحمل على وجه التحديد اثنين من C89 المبادئ التوجيهية التي تحد من ما يمكن إضافته:

  • الحفاظ على اللغة صغيرة وبسيطة.
  • توفير طريقة واحدة فقط للقيام بعملية.

المبادئ التوجيهية (وليس بالضرورة تلك محدد يتم وضعها لمجموعات العمل الفردية وبالتالي تحد من C++ اللجان (وجميع مجموعات ISO الأخرى) كذلك.

بالإضافة إلى ذلك ، تدرك أجسام وضع المعايير أن هناك تكلفة الفرصة (وهو مصطلح اقتصادي يعني ما يجب أن تتخلى عنه لاتخاذ قرار) لكل قرار يتخذونه. على سبيل المثال ، فإن تكلفة الفرصة البديلة لشراء آلة ألعاب Uber بقيمة 10،000 دولار هي العلاقات الودية (أو ربما الكل العلاقات) مع النصف الآخر لمدة ستة أشهر.

يشرح إريك غونرنسون هذا جيدًا مع -100 نقطة شرح فيما يتعلق بالأشياء التي لا تتم إضافتها دائمًا إلى منتجات Microsoft- تبدأ ميزة في الأساس 100 نقطة في الحفرة ، لذا يجب أن تضيف قدرًا كبيرًا من القيمة حتى يتم النظر فيها.

بمعنى آخر ، هل تفضل أن يكون لديك مشغل طاقة متكامل (والذي ، بصراحة ، يمكن لأي مبرمج نصف خارق أن يتجول في غضون عشر دقائق) أو إضافة متعدد الخيوط إلى المعيار؟ بالنسبة لنفسي ، أفضل أن أحصل على الأخير وليس مضطرًا إلى التخلص من التطبيقات المختلفة تحت UNIX و Windows.

أود أيضًا أن أرى الآلاف والآلاف من المجموعات المكتبة القياسية (تجزئة ، Btrees ، أشجار الأسود الأحمر ، القاموس ، الخرائط التعسفية وما إلى ذلك) ولكن ، كما ينص الأساس المنطقي:

المعيار هو معاهدة بين المنفذ والمبرمج.

وعدد المنفذين على هيئات المعايير يفوق عدد المبرمجين (أو على الأقل هؤلاء المبرمجين الذين لا يفهمون تكلفة الفرصة البديلة). إذا تمت إضافة كل هذه الأشياء ، فإن المعيار التالي C++ سيكون C++215x وربما يتم تنفيذها بالكامل من قبل مطوري التحويل البرمجي بعد ثلاثمائة عام من ذلك.

على أي حال ، هذه هي أفكاري (الضخمة إلى حد ما) حول هذه المسألة. إذا تم توزيع الأصوات فقط على قواعد على الكمية بدلاً من الجودة ، فأنا سرعان ما أخرج الجميع من الماء. شكرا على الإنصات :-)

بالنسبة لأي نوع متكامل للعرض الثابت ، تتفوق جميع أزواج الإدخال الممكنة تقريبًا على النوع ، على أي حال. ما هو استخدام توحيد وظيفة لا تعطي نتيجة مفيدة للغالبية العظمى من مدخلاتها المحتملة؟

تحتاج إلى حد كبير إلى أن يكون لديك نوع عدد صحيح كبير لجعل الوظيفة مفيدة ، وتوفر معظم مكتبات عدد صحيح كبير الوظيفة.


تعديل: في تعليق على السؤال ، يكتب static_rtti "معظم المدخلات تسبب في تدفقها؟ وينطبق الشيء نفسه على EXP و Double Pow ، لا أرى أي شخص يشتكي." هذا غير صحيح.

دعنا نترك جانبا exp, ، لأن هذا بجانب النقطة (على الرغم من أنه سيجعل حالتي أقوى بالفعل) ، والتركيز على double pow(double x, double y). لأي جزء من أزواج (x ، y) ، تقوم هذه الوظيفة بشيء مفيد (أي ، وليس مجرد تدفق أو تدفق Under)؟

سأركز فعليًا فقط على جزء صغير من أزواج الإدخال التي pow من المنطقي ، لأن ذلك سيكون كافياً لإثبات وجهة نظري: إذا كانت x إيجابية و | y | <= 1 ، ثم pow لا يفيض أو تدفق. ويتألف هذا من ما يقرب من ربع أزواج النقطة العائمة (بالضبط نصف أرقام النقاط غير الناجمة الإيجابية ، وأقل من نصف أرقام النقطة العائمة من غير نان أقل من 1). من الواضح أن هناك كثير من أزواج المدخلات الأخرى التي pow ينتج عن نتائج مفيدة ، لكننا تأكدنا من أنه ربع على الأقل من جميع المدخلات.

الآن دعونا نلقي نظرة على وظيفة الطاقة الثابتة (أي غير متكافئة). لأي جزء من المدخلات لا تتفوق ببساطة؟ لزيادة عدد أزواج المدخلات ذات معنى ، يجب توقيع القاعدة والأسعار غير موقعة. لنفترض أن القاعدة والأسس كلاهما n بت واسعة. يمكننا بسهولة الحصول على ملزم بجزء من المدخلات ذات معنى:

  • إذا كان الأسس 0 أو 1 ، فإن أي قاعدة ذات معنى.
  • إذا كان الأسس 2 أو أكثر ، فلا توجد قاعدة أكبر من 2^(n/2) تنتج نتيجة ذات معنى.

وهكذا ، من 2^(2n) أزواج الإدخال ، أقل من 2^(n + 1) + 2^(3n/2) تنتج نتائج ذات معنى. إذا نظرنا إلى ما هو المحتمل الاستخدام الأكثر شيوعًا ، الأعداد الصحيحة 32 بت ، فهذا يعني أن شيئًا بترتيب 1/1000 من واحد في المائة من أزواج المدخلات لا يتفوق ببساطة.

لأنه لا توجد طريقة لتمثيل جميع قوى عدد صحيح في INT على أي حال:

>>> print 2**-4
0.0625

هذا في الواقع سؤال مثير للاهتمام. إحدى الحجة التي لم أجدها في المناقشة هي الافتقار البسيط إلى قيم الإرجاع الواضحة للحجج. دعونا نحسب الطرق الهادئة int pow_int(int, int) يمكن أن تفشل الوظيفة.

  1. الفائض
  2. نتيجة غير محددة pow_int(0,0)
  3. لا يمكن تمثيل النتيجة pow_int(2,-1)

الوظيفة لديها ما لا يقل عن 2 أوضاع الفشل. لا يمكن أن تمثل الأعداد الصحيحة هذه القيم ، وسيحتاج سلوك الوظيفة في هذه الحالات إلى تحديد المعيار - وسيحتاج المبرمجون إلى إدراك كيفية تعامل الوظيفة تمامًا مع هذه الحالات.

بشكل عام ، يبدو ترك الوظيفة خارج الخيار الوحيد المعقول. يمكن للمبرمج استخدام إصدار النقطة العائمة مع كل الإبلاغ عن الأخطاء المتاحة بدلاً من ذلك.

اجابة قصيرة:

تخصص pow(x, n) إلى أين n هو العدد الطبيعي غالبًا ما يكون مفيدًا ل أداء الوقت. لكن المكتبة القياسية العامة pow() لا تزال تعمل جميلة (من المثير للدهشة!) حسنًا لهذا الغرض ومن الأهمية بمكان تضمين أقل قدر ممكن في مكتبة C القياسية بحيث يمكن صنعها على أنها محمولة وسهلة التنفيذ قدر الإمكان. من ناحية أخرى ، لا يمنع ذلك على الإطلاق من التواجد في مكتبة C ++ القياسية أو STL ، والتي أنا متأكد من أن لا أحد يخطط لاستخدامه في نوع من النظام الأساسي المضمن.

الآن ، للإجابة الطويلة.

pow(x, n) يمكن أن يكون أسرع بكثير في كثير من الحالات من خلال التخصص n إلى عدد طبيعي. لقد اضطررت إلى استخدام تنفيذ هذه الوظيفة لكل برنامج تقريبًا أكتب (لكنني أكتب الكثير من البرامج الرياضية في ج). يمكن إجراء العملية المتخصصة في O(log(n)) الوقت ، ولكن متى n صغير ، يمكن أن تكون نسخة خطية أبسط أسرع. فيما يلي تطبيقات على حد سواء:


    // Computes x^n, where n is a natural number.
    double pown(double x, unsigned n)
    {
        double y = 1;
        // n = 2*d + r. x^n = (x^2)^d * x^r.
        unsigned d = n >> 1;
        unsigned r = n & 1;
        double x_2_d = d == 0? 1 : pown(x*x, d);
        double x_r = r == 0? 1 : x;
        return x_2_d*x_r;
    }
    // The linear implementation.
    double pown_l(double x, unsigned n)
    {
        double y = 1;
        for (unsigned i = 0; i < n; i++)
            y *= x;
        return y;
    }

(أغادر x وقيمة الإرجاع كما يتضاعف لأن نتيجة pow(double x, unsigned n) سوف يتناسب مع ضعف مثلما هو الحال pow(double, double) إرادة.)

(نعم، pown متكرر ، لكن كسر المكدس مستحيل تمامًا لأن الحد الأقصى لحجم المكدس سيساوي تقريبًا log_2(n) و n هو عدد صحيح. لو n هو عدد صحيح 64 بت ، يمنحك الحد الأقصى لحجم المكدس حوالي 64. لا تحتوي الأجهزة على قيود على الذاكرة القصوى ، باستثناء بعض الصور المراوغة مع مداخن الأجهزة التي لا تصل إلى 3 إلى 8 وظائف فقط.)

أما بالنسبة للأداء ، فسوف تتفاجأ من مجموعة متنوعة الحديقة pow(double, double) قادر على. لقد اختبرت مائة مليون تكرار على IBM ThinkPad البالغ من العمر 5 سنوات x يساوي رقم التكرار و n يساوي 10. في هذا السيناريو ، pown_l وون. glibc pow() استغرق 12.0 ثانية مستخدم ، pown استغرق 7.4 ثواني المستخدم ، و pown_l استغرق 6.5 ثانية فقط. هذا ليس مفاجئًا جدًا. كنا أكثر أو أقل نتوقع هذا.

ثم تركت x كن ثابتًا (لقد قمت بتعيينه على 2.5) ، وقد حلقت n من 0 إلى 19 مليون مليون مرة. هذه المرة ، بشكل غير متوقع ، glibc pow فاز ، وبانهيار أرضي! استغرق الأمر 2.0 ثانية فقط. لي pown استغرق 9.6 ثانية ، و pown_l استغرق 12.2 ثانية. ماذا حدث هنا؟ لقد أجريت اختبارًا آخر لمعرفة ذلك.

لقد فعلت نفس الشيء على النحو الوارد أعلاه فقط مع x يساوي مليون. هذا الوقت، pown فاز في 9.6s. pown_l استغرق 12.2s واستغرق Glibc Pow 16.3s. الآن إنه واظح! glibc pow أداء أفضل من الثلاثة عندما x منخفض ، ولكن الأسوأ عندما x عالية. متى x عالية، pown_l أداء أفضل متى n منخفض ، و pown أداء أفضل متى x عالية.

لذا ، إليك ثلاثة خوارزميات مختلفة ، كل منها قادر على الأداء بشكل أفضل من الآخرين في ظل الظروف المناسبة. لذلك ، في نهاية المطاف ، والتي تستخدم على الأرجح تعتمد على كيفية التخطيط لاستخدامها pow, ، ولكن باستخدام الإصدار الصحيح هو يستحق كل هذا العناء ، ووجود جميع الإصدارات أمر لطيف. في الواقع ، يمكنك حتى أتمتة اختيار الخوارزمية مع وظيفة مثل هذه:

double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
    if (x_expected < x_threshold)
        return pow(x, n);
    if (n_expected < n_threshold)
        return pown_l(x, n);
    return pown(x, n);
}

طالما x_expected و n_expected هل يتم تحديد ثوابت في وقت الترجمة ، إلى جانب بعض التحذيرات الأخرى ، وسيقوم برنامج التحويل البرمجي المحسن الذي يستحق الملح تلقائيًا بإزالة بالكامل pown_auto استدعاء الوظيفة واستبدلها بالاختيار المناسب للخوارزميات الثلاثة. (الآن ، إذا كنت ستحاول بالفعل استعمال هذا ، من المحتمل أن تضطر إلى لعبها قليلاً ، لأنني لم أحاول بالضبط التجميع ما كتبته أعلاه. ؛))

من ناحية أخرى ، glibc pow تعمل و GLIBC كبيرة بما يكفي بالفعل. من المفترض أن يكون معيار C محمولًا ، بما في ذلك مختلف الأجهزة المدمجة (في الواقع ، يتفق المطورون المضمنون في كل مكان عمومًا على أن GLIBC أكبر من اللازم بالنسبة لهم) ، ولا يمكن أن يكون محمولًا إذا كان لكل وظيفة رياضيات بسيطة تضمين كل خوارزمية بديلة ربما كن مفيدًا. لذلك ، لهذا السبب ليس في معيار C.

الحاشية: في اختبار الأداء في الوقت ، أعطيت وظائف بلدي أعلام التحسين السخية نسبيا (-s -O2) من المحتمل أن تكون قابلة للمقارنة ، إن لم يكن أسوأ من ، ما كان يستخدم على الأرجح لتجميع GLIBC على نظامي (Archlox) ، وبالتالي فإن النتائج ربما تكون عادلة. لاختبار أكثر صرامة ، يجب أن أقوم بتجميع Glibc بنفسي وأنا reeally لا تشعر بالرغبة في القيام بذلك. اعتدت على استخدام gentoo ، لذلك أتذكر المدة التي يستغرقها ، حتى عندما تكون المهمة الآلي. النتائج قاطعة (أو غير حاسمة) بما يكفي بالنسبة لي. أنت بالطبع مرحبًا بك في القيام بذلك بنفسك.

جولة المكافأة: تخصص pow(x, n) لجميع الأعداد الصحيحة مفيدة إذا كان هناك حاجة إلى إخراج عدد صحيح دقيق ، وهو ما يحدث. النظر في تخصيص الذاكرة لمجموعة N- الأبعاد مع عناصر p^n. الحصول على p^n حتى من قبل واحد سوف يؤدي إلى segfault التي ربما تحدث عشوائيا.

أحد أسباب عدم وجود أحمال إضافية C ++ هو أن تكون متوافقة مع C.

C ++ 98 لديه وظائف مثل double pow(double, int), ، ولكن تمت إزالة هذه في C ++ 11 مع الحجة القائلة بأن C99 لم يتضمنها.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550

إن الحصول على نتيجة أكثر دقة قليلاً يعني أيضًا الحصول على القليل مختلف نتيجة.

العالم يتطور باستمرار وكذلك لغات البرمجة. ال الجزء الرابع من C العشري TR¹ يضيف المزيد من الوظائف إلى <math.h>. قد تكون عائلتان من هذه الوظائف ذات أهمية لهذا السؤال:

  • ال pown الوظائف ، التي تأخذ رقمًا عائمًا و intmax_t الأسس.
  • ال powr الوظائف ، التي تأخذ أرقام نقطتين عائمة (x و y) وحساب x إلى السلطة y مع الصيغة exp(y*log(x)).

يبدو أن اللاعبين القياسيين اعتبروا في النهاية هذه الميزات مفيدة بما يكفي لدمجها في المكتبة القياسية. ومع ذلك ، فإن العقلانية هي أن هذه الوظائف موصى بها من قبل ISO/IEC/IEEE 60559: 2011 المعيار لأرقام النقاط العائمة الثنائية والعشرية. لا أستطيع أن أقول على وجه اليقين ما الذي تم اتباعه "المعيار" في وقت C89 ، لكن التطورات المستقبلية <math.h> من المحتمل أن تتأثر بشدة بالتطورات المستقبلية لـ ISO/IEC/IEEE 60559 اساسي.

لاحظ أنه لن يتم تضمين الجزء الرابع من TR العشري في C2X (مراجعة C الرئيسية التالية) ، وربما سيتم تضمينها لاحقًا كميزة اختيارية. لم يكن هناك أي نية أعرفها لتضمين هذا الجزء من TR في مراجعة C ++ المستقبلية.


¹ يمكنك العثور على بعض وثائق العمل أثناء التقدم هنا.

ربما لأن ALU المعالج لم ينفذ مثل هذه الوظيفة للأعداد الصحيحة ، ولكن هناك تعليمات FPU (كما يشير ستيفن ، إنه في الواقع زوج). لذلك كان من الأسرع في الواقع أن يتم إلقاؤه للمضاعفة ، واتصل الأسرى مع الزوجي ، ثم اختبار الفائض والاستخلاص ، بدلاً من تنفيذها باستخدام حساب عدد صحيح.

(لسبب واحد ، تقلل اللوغاريتمات من القوى إلى الضرب ، لكن اللوغاريتمات من الأعداد الصحيحة تفقد الكثير من الدقة لمعظم المدخلات)

ستيفن محق في أنه لم يعد هذا صحيحًا على المعالجات الحديثة ، ولكن معيار C عندما تم اختيار وظائف الرياضيات (C ++ فقط تستخدم وظائف C) هو الآن ما هو 20 عامًا؟

سبب بسيط للغاية:

5^-2 = 1/25

يعتمد كل شيء في مكتبة STL على أكثر الأشياء دقة وقوية يمكن تخيلها. بالتأكيد ، ستعود INT إلى الصفر (من 1/25) ولكن هذا سيكون إجابة غير دقيقة.

أوافق ، إنه غريب في بعض الحالات.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top