सबसे सटीक परिणाम प्राप्त करने के लिए किस क्रम में फ़्लोट्स को जोड़ा जाना चाहिए?


105

यह एक ऐसा प्रश्न था जो मुझे अपने हालिया साक्षात्कार में पूछा गया था और मैं जानना चाहता हूं (मुझे वास्तव में संख्यात्मक विश्लेषण का सिद्धांत याद नहीं है, इसलिए कृपया मेरी मदद करें :)

यदि हमारे पास कुछ फ़ंक्शन है, जो फ्लोटिंग-पॉइंट संख्याओं को जमा करता है:

std::accumulate(v.begin(), v.end(), 0.0);

vएक है std::vector<float>उदाहरण के लिए,।

  • क्या उन्हें संचित करने से पहले इन संख्याओं को क्रमबद्ध करना बेहतर होगा?

  • कौन सा आदेश सबसे सटीक उत्तर देगा?

मुझे संदेह है कि आरोही क्रम में संख्याओं को क्रमबद्ध करने से वास्तव में संख्यात्मक त्रुटि कम होगी , लेकिन दुर्भाग्य से मैं इसे स्वयं साबित नहीं कर सकता।

PS मुझे एहसास है कि शायद वास्तविक दुनिया प्रोग्रामिंग के साथ इसका कोई लेना-देना नहीं है, बस जिज्ञासु होने के नाते।


17
यह वास्तव में वास्तविक दुनिया प्रोग्रामिंग के साथ करने के लिए सब कुछ है। हालांकि, कई अनुप्रयोग वास्तव में गणना के पूर्ण सर्वोत्तम सटीकता के बारे में परवाह नहीं करते हैं जब तक कि यह 'बहुत करीब' है। इंजीनियरिंग अनुप्रयोगों? अत्यंत महत्वपूर्ण। चिकित्सा अनुप्रयोगों? अत्यंत महत्वपूर्ण। बड़े पैमाने पर आँकड़े? कुछ हद तक कम सटीकता स्वीकार्य है।
ज़ेचिन

18
कृपया जवाब न दें जब तक आप वास्तव में नहीं जानते हैं और किसी पृष्ठ पर इंगित कर सकते हैं, विवरण में आपके तर्क को समझाता है। हम पहले से ही फ़्लोटिंग पॉइंट नंबरों के बारे में बहुत बकवास कर रहे हैं, हम इसे जोड़ना नहीं चाहते हैं। अगर आपको लगता है कि आप जानते हैं। रूक जा। क्योंकि अगर आपको लगता है कि आप केवल जानते हैं तो आप शायद गलत हैं।
मार्टिन

4
@ Zéychin "इंजीनियरिंग अनुप्रयोग? अत्यंत महत्वपूर्ण। चिकित्सा अनुप्रयोग? अत्यंत महत्वपूर्ण है।" मुझे लगता है कि आप आश्चर्यचकित होंगे अगर आपको सच्चाई पता थी :)
Bћови

3
@Zeychin पूर्ण त्रुटि अप्रासंगिक है। क्या महत्वपूर्ण है सापेक्ष त्रुटि। यदि रेडियन के कुछ सौवें हिस्से में 0.001% है, तो कौन परवाह करता है?
B:30овиЈ

3
मैं वास्तव में इस पढ़ने की सलाह देता हूं: "हर कंप्यूटर वैज्ञानिक को फ्लोटिंग पॉइंट के बारे में जानने की जरूरत है" perso.ens-lyon.fr/jean-michel.muller/goldberg.pdf
मोहम्मद अलागन

जवाबों:


108

आपकी वृत्ति मूल रूप से सही है, आरोही क्रम में (परिमाण की) छँटाई आमतौर पर कुछ हद तक सुधार करती है। उस मामले पर विचार करें जहां हम एकल-परिशुद्धता (32 बिट) तैर रहे हैं, और 1/1 (1 बिलियन) के बराबर 1 बिलियन मान हैं, और 1 के बराबर एक मान। यदि 1 पहले आता है, तो योग आएगा 1 से, 1 के बाद से (1/1 बिलियन) 1 परिशुद्धता के नुकसान के कारण है। प्रत्येक जोड़ पर कुल मिलाकर कोई प्रभाव नहीं होता है।

यदि छोटे मूल्य पहले आते हैं, तो वे कम से कम कुछ करने के लिए योग करेंगे, हालांकि तब भी मेरे पास उनमें से 2 ^ 30 हैं, जबकि 2 ^ 25 के बाद या इसलिए मैं उस स्थिति में वापस आ गया हूं जहां प्रत्येक व्यक्ति व्यक्तिगत रूप से कुल को प्रभावित नहीं कर रहा है। और भी। इसलिए मुझे अभी और तरकीबें चाहिए।

यह एक चरम मामला है, लेकिन सामान्य तौर पर समान परिमाण के दो मूल्यों को जोड़ना बहुत अलग परिमाण के दो मूल्यों को जोड़ने से अधिक सटीक है, क्योंकि आप छोटे मूल्य में सटीक तरीके से कम बिट्स को "त्याग" करते हैं। संख्याओं को क्रमबद्ध करके, आप समान परिमाण के मानों को एक साथ जोड़ते हैं, और उन्हें बढ़ते क्रम में जोड़कर आप छोटे मानों को संचयी रूप से बड़ी संख्याओं के परिमाण तक पहुंचने का "मौका" देते हैं।

फिर भी, यदि नकारात्मक संख्याएं शामिल हैं, तो इस दृष्टिकोण को "आउट" करना आसान है। योग करने के लिए तीन मूल्यों पर विचार करें {1, -1, 1 billionth}। अंकगणितीय रूप से सही योग है 1 billionth, लेकिन यदि मेरे पहले जोड़ में छोटे मूल्य शामिल हैं, तो मेरी अंतिम राशि 6 ​​में से 0. संभावित आदेश होंगे, केवल 2 "सही" हैं - {1, -1, 1 billionth}और {-1, 1, 1 billionth}। सभी 6 आदेश परिणाम देते हैं जो इनपुट में सबसे बड़े-परिमाण मान के पैमाने पर सटीक हैं (0.0000001% बाहर), लेकिन उनमें से 4 के लिए परिणाम सही समाधान (100% बाहर) के पैमाने पर गलत है। आपके द्वारा हल की जा रही विशेष समस्या आपको बताएगी कि पूर्व पर्याप्त है या नहीं।

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

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


8
स्पष्टीकरण के लिए +1। यह कुछ हद तक प्रति-सहज है क्योंकि जोड़ आमतौर पर संख्यात्मक रूप से स्थिर होता है (घटाव और विभाजन के विपरीत)।
कोनराड रुडोल्फ

2
@Konrad, यह संख्यात्मक रूप से स्थिर हो सकता है, लेकिन यह परिचालनों के विभिन्न परिमाणों को देखते हुए सटीक नहीं है :)
एमएसएन

3
@ 6502: वे परिमाण के क्रम में क्रमबद्ध हैं, इसलिए -1 अंत में आता है। यदि कुल का सही मान 1 परिमाण का है, तो यह ठीक है। यदि आप एक साथ तीन मान जोड़ रहे हैं: 1 / बिलियन, 1 और -1, तो, आपको 0 मिलेगा, जिस बिंदु पर आपको दिलचस्प व्यावहारिक प्रश्न का उत्तर देना है - क्या आपको उस उत्तर की आवश्यकता है जो कि इस पैमाने पर सटीक हो सच्चा योग, या क्या आपको केवल एक उत्तर की आवश्यकता है जो सबसे बड़े मूल्यों के पैमाने पर सटीक हो? कुछ व्यावहारिक अनुप्रयोगों के लिए, बाद वाला काफी अच्छा है, लेकिन जब आपको अधिक परिष्कृत दृष्टिकोण की आवश्यकता नहीं होती है। क्वांटम भौतिकी पुनर्मूल्यांकन का उपयोग करती है।
स्टीव जेसोप

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

2
@ केविन पैंको: सरल संस्करण यह है कि एकल-सटीक फ्लोट में 24 द्विआधारी अंक होते हैं, जिनमें से सबसे बड़ी संख्या में सबसे बड़ा सेट बिट होता है। इसलिए यदि आप दो संख्याओं को जोड़ते हैं जो 2 ^ 24 से अधिक परिमाण में भिन्न होती हैं, तो आपको छोटे मूल्य का कुल नुकसान होता है, और यदि वे परिमाण में एक छोटी डिग्री से भिन्न होते हैं, तो आप छोटे की सटीकता के बिट संख्या को खो देते हैं। नंबर।
स्टीव जेसप

88

इस तरह के संचय ऑपरेशन के लिए डिज़ाइन किया गया एक एल्गोरिथ्म भी है, जिसे कहन सारांश कहा जाता है , जिसे आपको शायद पता होना चाहिए।

विकिपीडिया के अनुसार,

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

छद्मकोड में, एल्गोरिथ्म है:

function kahanSum(input)
 var sum = input[1]
 var c = 0.0          //A running compensation for lost low-order bits.
 for i = 2 to input.length
  y = input[i] - c    //So far, so good: c is zero.
  t = sum + y         //Alas, sum is big, y small, so low-order digits of y are lost.
  c = (t - sum) - y   //(t - sum) recovers the high-order part of y; subtracting y recovers -(low part of y)
  sum = t             //Algebraically, c should always be zero. Beware eagerly optimising compilers!
 next i               //Next time around, the lost low part will be added to y in a fresh attempt.
return sum

3
इस धागे को +1 प्यारा जोड़। कोई भी संकलक जो उन बयानों पर "उत्सुकता से अनुकूलन करता है" पर प्रतिबंध लगाया जाना चाहिए।
क्रिस ए।

1
यह दो योग चर sumऔर cअलग परिमाण का उपयोग करके, परिशुद्धता को लगभग दोगुना करने की एक सरल विधि है । यह तुच्छ रूप से N चरों तक बढ़ाया जा सकता है।
MSalters

2
@ChrisA। अच्छी तरह से आप स्पष्ट रूप से उन सभी कंपाइलरों पर नियंत्रण कर सकते हैं जो गिनती (जैसे -ffast-mathजीसीसी पर) के माध्यम से करते हैं।
कोनराड रूडोल्फ

6
@ कोनराड रूडोल्फ ने इंगित करने के लिए धन्यवाद कि यह एक संभव अनुकूलन है -ffast-math। मैंने इस चर्चा और इस लिंक से जो सीखा है , वह यह है कि यदि आप संख्यात्मक सटीकता की परवाह करते हैं, तो आपको शायद इसका उपयोग करने से बचना चाहिए -ffast-mathलेकिन कई अनुप्रयोगों में जहां आप सीपीयू-बाउंड हो सकते हैं, लेकिन सटीक संख्यात्मक गणनाओं की परवाह नहीं करते हैं, (उदाहरण के लिए गेम प्रोग्रामिंग ), -ffast-mathउपयोग करने के लिए उचित है। इस प्रकार, मैं अपनी दृढ़ता से "प्रतिबंधित" टिप्पणी पर अमल करना चाहूंगा।
क्रिस ए।

के लिए दोहरे सटीक चर का उपयोग करने sum, c, t, yसे मदद मिलेगी। तुम भी जोड़ने की जरूरत sum -= cसे पहले कम से return sum
जी। कोहेन

34

मैंने स्टीव जेसप द्वारा दिए गए उत्तर में चरम उदाहरण की कोशिश की।

#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
    long billion = 1000000000;
    double big = 1.0;
    double small = 1e-9;
    double expected = 2.0;

    double sum = big;
    for (long i = 0; i < billion; ++i)
        sum += small;
    std::cout << std::scientific << std::setprecision(1) << big << " + " << billion << " * " << small << " = " <<
        std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    sum = 0;
    for (long i = 0; i < billion; ++i)
        sum += small;
    sum += big;
    std::cout  << std::scientific << std::setprecision(1) << billion << " * " << small << " + " << big << " = " <<
        std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    return 0;
}

मुझे निम्नलिखित परिणाम मिला:

1.0e+00 + 1000000000 * 1.0e-09 = 2.000000082740371    (difference = 0.000000082740371)
1000000000 * 1.0e-09 + 1.0e+00 = 1.999999992539933    (difference = 0.000000007460067)

पहली पंक्ति में त्रुटि दूसरी में दस गुना से अधिक बड़ी है।

यदि मैं उपरोक्त कोड में doubles को floats में बदलता हूं , तो मुझे:

1.0e+00 + 1000000000 * 1.0e-09 = 1.000000000000000    (difference = 1.000000000000000)
1000000000 * 1.0e-09 + 1.0e+00 = 1.031250000000000    (difference = 0.968750000000000)

न तो उत्तर 2.0 के करीब है (लेकिन दूसरा थोड़ा करीब है)।

doubleडेनियल प्राइडेन द्वारा वर्णित कहन समन ( एस के साथ ) का उपयोग करना :

#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
    long billion = 1000000000;
    double big = 1.0;
    double small = 1e-9;
    double expected = 2.0;

    double sum = big;
    double c = 0.0;
    for (long i = 0; i < billion; ++i) {
        double y = small - c;
        double t = sum + y;
        c = (t - sum) - y;
        sum = t;
    }

    std::cout << "Kahan sum  = " << std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    return 0;
}

मुझे ठीक 2.0 मिलता है:

Kahan sum  = 2.000000000000000    (difference = 0.000000000000000)

और भले ही मैं उपरोक्त कोड में doubles को floats में बदल दूं, मुझे मिल जाएगा:

Kahan sum  = 2.000000000000000    (difference = 0.000000000000000)

ऐसा लगेगा कि कहन जाने का रास्ता है!


मेरा "बड़ा" मान 1 के बराबर है, न कि 1e9। आपका दूसरा उत्तर, बढ़ते आकार के क्रम में जोड़ा गया है, गणितीय रूप से सही है (1 बिलियन, एक बिलियन बिलियन से अधिक, 1 बिलियन और 1 है), हालांकि भाग्य द्वारा विधि के किसी भी सामान्य ध्वनि से अधिक :-) नोट जो doubleखराब नहीं होता है एक बिलियन अरबों को एक साथ जोड़ने में परिशुद्धता का नुकसान, क्योंकि इसमें 52 महत्वपूर्ण बिट्स हैं, जबकि IEEE में floatकेवल 24 और हैं।
स्टीव जेसोप

@ मेरी गलती, क्षमा करें। मैंने आपके द्वारा बताए गए उदाहरण कोड को अपडेट किया है।
एंड्रयू स्टीन

4
कहन में अभी भी सीमित सटीकता है, लेकिन एक हत्यारे के मामले का निर्माण करने के लिए आपको मुख्य योग और त्रुटि संचायक दोनों की आवश्यकता cहै ताकि अगले समन की तुलना में मान बड़ा हो। इसका मतलब यह है कि सम्मन मुख्य राशि की तुलना में बहुत छोटा है, इसलिए उनमें से बहुत कुछ जोड़ने के लिए बहुत सारे होने जा रहे हैं। खासकर doubleअंकगणित से।
स्टीव जेसोप

14

एल्गोरिदम का एक वर्ग है जो डेटा को सॉर्ट करने या फिर से ऑर्डर करने की आवश्यकता के बिना , इस सटीक समस्या को हल करता है

दूसरे शब्दों में, डेटा पर एक पास में योग किया जा सकता है। यह ऐसे एल्गोरिदम को उन स्थितियों में भी लागू करता है जहां डेटा पहले से ज्ञात नहीं है, उदाहरण के लिए यदि डेटा वास्तविक समय में आता है और रनिंग राशि को बनाए रखने की आवश्यकता होती है।

यहाँ हाल के एक पेपर का सार है:

हम फ़्लोटिंग-पॉइंट संख्याओं की एक धारा के सटीक योग के लिए एक उपन्यास, ऑनलाइन एल्गोरिथ्म प्रस्तुत करते हैं। "ऑनलाइन" से हमारा मतलब है कि एल्गोरिथ्म को एक समय में केवल एक इनपुट देखने की आवश्यकता है, और केवल निरंतर मेमोरी की आवश्यकता होने पर इस तरह के इनपुट की एक मनमाना लंबाई इनपुट स्ट्रीम ले सकती है। "सटीक" से हमारा तात्पर्य है कि हमारे एल्गोरिथ्म के आंतरिक सरणी का योग सभी इनपुट के योग के बराबर है, और लौटा परिणाम सही-सही का योग है। शुद्धता का प्रमाण सभी निविष्टियों के लिए मान्य है (गैर-सामान्यीकृत संख्याएँ लेकिन मोडुलो मध्यवर्ती अतिप्रवाह सहित), और समन की संख्या या योग की स्थिति संख्या से स्वतंत्र है। एल्गोरिथ्म asymptotically को सारांश के अनुसार केवल 5 FLOPs की आवश्यकता होती है, और निर्देश-स्तरीय समानता के कारण स्पष्ट रूप से केवल 2--3 गुना धीमी गति से चलता है। जब तेजी से ग्रीष्मकाल की संख्या 10,000 से अधिक हो जाती है तो तेजी से गूंगा "साधारण पुनरावर्ती योग" लूप। इस प्रकार, हमारे ज्ञान के लिए, यह ज्ञात एल्गोरिदम में सबसे तेज़, सबसे सटीक और सबसे अधिक स्मृति कुशल है। वास्तव में, यह देखना मुश्किल है कि हार्डवेयर के सुधार के बिना एक तेज एल्गोरिथ्म या काफी कम FLOPs की आवश्यकता कैसे हो सकती है। बड़ी संख्या में सम्मन के लिए एक आवेदन प्रदान किया जाता है।

स्रोत: एल्गोरिथम 908: फ्लोटिंग-पॉइंट धाराओं का ऑनलाइन सटीक योग


1
@ अंत: अभी भी चारों ओर ईंट-और-मोर्टार पुस्तकालय हैं। वैकल्पिक रूप से, पीडीएफ ऑनलाइन खरीदने पर $ 5- $ 15 खर्च होते हैं (यह निर्भर करता है कि आप ACM सदस्य हैं)। अंत में, दीपदेवी $ 2.99 के लिए 24 घंटे के लिए कागज़ उधार देने की पेशकश करती प्रतीत होती हैं (यदि आप दीपदेवी के लिए नए हैं, तो आप इसे उनके नि: शुल्क परीक्षण के हिस्से के रूप में मुफ्त में प्राप्त करने में सक्षम हो सकते हैं): deepdyve.com/lpp-acm /…
एनपीई

2

बढ़ते क्रम में संख्याओं को क्रमबद्ध करने के स्टीव के जवाब पर बिल्डिंग, मैं दो और विचार पेश करूंगा:

  1. उपरोक्त दो संख्याओं के प्रतिपादक के अंतर पर निर्णय लें जिससे आप यह तय कर सकते हैं कि आप बहुत अधिक सटीकता खो देंगे।

  2. फिर संख्याओं को क्रम में जोड़ें जब तक कि संचायक का घातांक अगली संख्या के लिए बहुत बड़ा नहीं हो जाता है, तब संचायक को एक अस्थायी कतार में रख दें और अगले संख्या के साथ संचायक को शुरू करें। जारी रखें जब तक आप मूल सूची को समाप्त नहीं करते।

आप अस्थायी कतार के साथ प्रक्रिया को दोहराते हैं (इसे हल किया है) और घातांक में संभवतः बड़े अंतर के साथ।

मुझे लगता है कि यह काफी धीमा होगा यदि आपको हर समय घातांक की गणना करनी है।

मुझे एक कार्यक्रम के साथ जल्दी जाना था और परिणाम 1.99903 था


2

मुझे लगता है कि आप संख्याओं को जमा करने से पहले बेहतर कर सकते हैं क्योंकि आप उन्हें जमा करते हैं, क्योंकि संचय की प्रक्रिया के दौरान, संचायक बड़ा और बड़ा हो जाता है। यदि आपके पास समान संख्या में बड़ी संख्या है, तो आप जल्दी से सटीक खोना शुरू कर देंगे। यहाँ मैं इसके बजाय क्या सुझाव दूंगा:

while the list has multiple elements
    remove the two smallest elements from the list
    add them and put the result back in
the single element in the list is the result

बेशक यह एल्गोरिथ्म एक सूची के बजाय प्राथमिकता कतार के साथ सबसे अधिक कुशल होगा। C ++ कोड:

template <typename Queue>
void reduce(Queue& queue)
{
    typedef typename Queue::value_type vt;
    while (queue.size() > 1)
    {
        vt x = queue.top();
        queue.pop();
        vt y = queue.top();
        queue.pop();
        queue.push(x + y);
    }
}

चालक:

#include <iterator>
#include <queue>

template <typename Iterator>
typename std::iterator_traits<Iterator>::value_type
reduce(Iterator begin, Iterator end)
{
    typedef typename std::iterator_traits<Iterator>::value_type vt;
    std::priority_queue<vt> positive_queue;
    positive_queue.push(0);
    std::priority_queue<vt> negative_queue;
    negative_queue.push(0);
    for (; begin != end; ++begin)
    {
        vt x = *begin;
        if (x < 0)
        {
            negative_queue.push(x);
        }
        else
        {
            positive_queue.push(-x);
        }
    }
    reduce(positive_queue);
    reduce(negative_queue);
    return negative_queue.top() - positive_queue.top();
}

कतार में संख्याएं नकारात्मक हैं क्योंकि सबसे बड़ी संख्या में topपैदावार होती है , लेकिन हम सबसे छोटा चाहते हैं । मैं कतार में अधिक टेम्पलेट तर्क प्रदान कर सकता था, लेकिन यह दृष्टिकोण सरल लगता है।


2

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

इंटरवल अंकगणित पर एक नज़र डालें जहाँ आप इस तरह से सभी गणित करते हैं, जैसे ही आप जाते हैं, उच्चतम और निम्नतम मान रखते हैं। यह कुछ दिलचस्प परिणाम और अनुकूलन की ओर जाता है।


0

सटीकता को बेहतर बनाने वाला सबसे सरल प्रकार आरोही निरपेक्ष मान द्वारा सॉर्ट करना है। यह छोटे परिमाण मूल्यों को संचय का नुकसान ट्रिगर करने वाले बड़े परिमाण मूल्यों के साथ बातचीत करने से पहले जमा करने या रद्द करने का मौका देता है।

उस ने कहा, आप कई गैर-अतिव्यापी आंशिक रकमों को ट्रैक करके बेहतर कर सकते हैं। यहाँ तकनीक का वर्णन करने वाला और प्रमाण प्रस्तुत करने वाला एक पेपर है: www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps

उस एल्गोरिथ्म और सटीक फ्लोटिंग पॉइंट समेशन के अन्य दृष्टिकोणों को सरल पायथन में लागू किया गया है: http://code.activestate.com/recipes/393090/ उनमें से कम से कम दो को तुच्छ रूप से C ++ में बदला जा सकता है।


0

IEEE 754 सिंगल या डबल सटीक या ज्ञात प्रारूप संख्याओं के लिए, एक अन्य विकल्प घातांक द्वारा अनुक्रमित संख्याओं (कॉलर द्वारा पारित, या C ++ के लिए एक वर्ग में) का उपयोग करना है। सरणी में संख्याओं को जोड़ते समय, केवल उसी घातांक के साथ संख्याएं जोड़ी जाती हैं (जब तक कि एक खाली स्लॉट नहीं मिलता है और संग्रहीत संख्या)। जब एक राशि के लिए बुलाया जाता है, तो छोटी से बड़ी से छोटी छंटनी के लिए सरणी को संक्षेप में प्रस्तुत किया जाता है। एकल सटीक उदाहरण:

/* clear array */
void clearsum(float asum[256])
{
size_t i;
    for(i = 0; i < 256; i++)
        asum[i] = 0.f;
}

/* add a number into array */
void addtosum(float f, float asum[256])
{
size_t i;
    while(1){
        /* i = exponent of f */
        i = ((size_t)((*(unsigned int *)&f)>>23))&0xff;
        if(i == 0xff){          /* max exponent, could be overflow */
            asum[i] += f;
            return;
        }
        if(asum[i] == 0.f){     /* if empty slot store f */
            asum[i] = f;
            return;
        }
        f += asum[i];           /* else add slot to f, clear slot */
        asum[i] = 0.f;          /* and continue until empty slot */
    }
}

/* return sum from array */
float returnsum(float asum[256])
{
float sum = 0.f;
size_t i;
    for(i = 0; i < 256; i++)
        sum += asum[i];
    return sum;
}

डबल सटीक उदाहरण:

/* clear array */
void clearsum(double asum[2048])
{
size_t i;
    for(i = 0; i < 2048; i++)
        asum[i] = 0.;
}

/* add a number into array */
void addtosum(double d, double asum[2048])
{
size_t i;
    while(1){
        /* i = exponent of d */
        i = ((size_t)((*(unsigned long long *)&d)>>52))&0x7ff;
        if(i == 0x7ff){         /* max exponent, could be overflow */
            asum[i] += d;
            return;
        }
        if(asum[i] == 0.){      /* if empty slot store d */
            asum[i] = d;
            return;
        }
        d += asum[i];           /* else add slot to d, clear slot */
        asum[i] = 0.;           /* and continue until empty slot */
    }
}

/* return sum from array */
double returnsum(double asum[2048])
{
double sum = 0.;
size_t i;
    for(i = 0; i < 2048; i++)
        sum += asum[i];
    return sum;
}

यह कुछ हद तक मैल्कम 1971 की विधि की तरह लगता है , या इससे भी ज्यादा, इसका वैरिएंट है जो डेमोल और हेडा ("एल्गोरिथम 3") द्वारा प्रतिपादक का उपयोग करता है । वहाँ एक और एल्गोरिथ्म है जो आपकी तरह कैरी-आधारित लूप करता है, लेकिन मैं इसे फिलहाल नहीं ढूंढ सकता।
ZachB

@ZachB - अवधारणा लिंक की गई सूची के लिए नीचे मर्ज सॉर्ट के समान है , जो एक छोटे सरणी का भी उपयोग करता है, जहां सरणी [i] 2 ^ i नोड्स के साथ सूचीबद्ध करने के लिए इंगित करता है। मुझे नहीं पता कि यह कितना पीछे जाता है। मेरे मामले में, यह 1970 में आत्म खोज थी।
rcgldr

-1

आपकी झांकियों को दोहरी सटीकता में जोड़ा जाना चाहिए। यह आपको किसी भी अन्य तकनीक की तुलना में अधिक अतिरिक्त परिशुद्धता देगा। थोड़ा और अधिक सटीक और काफी अधिक गति के लिए, आप कह सकते हैं कि चार रकम बनाई जा सकती हैं, और उन्हें अंत में जोड़ सकते हैं।

यदि आप डबल सटीक संख्या जोड़ रहे हैं, तो राशि के लिए लंबे समय तक डबल का उपयोग करें - हालांकि, यह केवल कार्यान्वयन में एक सकारात्मक प्रभाव पड़ेगा जहां लंबे डबल वास्तव में डबल (आमतौर पर x86, कंपाइलर सेटिंग्स के आधार पर पावरपीसी) से अधिक सटीक है।


1
"यह आपको किसी भी अन्य तकनीक की तुलना में अधिक अतिरिक्त सटीकता प्रदान करेगा" क्या आपको लगता है कि आपका उत्तर एक साल से अधिक समय के बाद आता है, जो पहले दिए गए उत्तर के बाद एक बार सटीक विवरण का उपयोग करने का वर्णन करता है?
पास्कल क्यूक

"लंबी डबल" प्रकार भयानक है और आपको इसका उपयोग नहीं करना चाहिए।
जेफ

-1

छँटाई के संबंध में, यह मुझे प्रतीत होता है कि यदि आप रद्द करने की अपेक्षा करते हैं तो संख्याएँ परिमाण के अवरोही क्रम में जोड़ी जानी चाहिए , आरोही नहीं। उदाहरण के लिए:

(-1 (1 + 1) + 1e-20) 1e-20 देगा

परंतु

((1e-20 + 1) - 1) 0 देगा

पहले समीकरण में कि दो बड़ी संख्या को रद्द कर दिया गया है, जबकि दूसरे में 1e-20 शब्द 1 में जोड़े जाने पर खो जाता है, क्योंकि इसे बनाए रखने के लिए पर्याप्त सटीकता नहीं है।

साथ ही, सम संख्याओं के योग के लिए जोड़ीदार योग बहुत ही अच्छा है।

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