32-बिट इंट को डबल करने के लिए एक तेज़ विधि को समझाया गया


169

लुआ के स्रोत कोड को पढ़ते समय , मैंने देखा कि लुआ 32-बिट के macroचक्कर लगाने के लिए एक का उपयोग करता है । मैंने निकाला , और यह इस तरह दिखता है:doubleintmacro

union i_cast {double d; int i[2]};
#define double2int(i, d, t)  \
    {volatile union i_cast u; u.d = (d) + 6755399441055744.0; \
    (i) = (t)u.i[ENDIANLOC];}

यहां एंडियनस केENDIANLOC रूप में परिभाषित किया गया है , छोटे एंडियन के लिए, बड़े एंडियन के लिए। लुआ सावधानी से धीरज धरता है। पूर्णांक प्रकार के लिए खड़ा है, जैसे या ।01tintunsigned int

मैंने थोड़ा शोध किया और इसका एक सरल प्रारूप है macroजो समान विचार का उपयोग करता है:

#define double2int(i, d) \
    {double t = ((d) + 6755399441055744.0); i = *((int *)(&t));}

या सी ++ में - शैली:

inline int double2int(double d)
{
    d += 6755399441055744.0;
    return reinterpret_cast<int&>(d);
}

यह ट्रिक IEEE 754 (जिसका अर्थ है कि आज की हर मशीन का उपयोग करता है) का उपयोग करके किसी भी मशीन पर काम किया जा सकता है । यह धनात्मक और ऋणात्मक दोनों संख्याओं के लिए काम करता है, और गोलाई बैंकर के नियम का अनुसरण करता है । (यह आश्चर्य की बात नहीं है, क्योंकि यह IEEE 754 का अनुसरण करता है।)

मैंने इसे परीक्षण करने के लिए एक छोटा सा कार्यक्रम लिखा:

int main()
{
    double d = -12345678.9;
    int i;
    double2int(i, d)
    printf("%d\n", i);
    return 0;
}

और यह आउटपुट -12345679, जैसा कि अपेक्षित था।

मैं विस्तार से प्राप्त करना चाहूंगा कि यह मुश्किल macroकाम कैसे करता है। जादू की संख्या 6755399441055744.0वास्तव में है 2^51 + 2^52, या 1.5 * 2^52, और 1.5बाइनरी के रूप में प्रतिनिधित्व किया जा सकता है 1.1। जब कोई 32-बिट पूर्णांक इस जादू की संख्या में जोड़ा जाता है, ठीक है, मैं यहाँ से खो गया हूं। यह ट्रिक कैसे काम करती है?

पुनश्च: यह Lua स्रोत कोड, Llimits.h में है

अद्यतन :

  1. जैसा कि @Mysticial बताते हैं, यह विधि केवल 32-बिट तक ही सीमित नहीं है int, इसे 64-बिट intतक भी विस्तारित किया जा सकता है, जब तक कि यह संख्या 2 ^ 52 की सीमा में है। ( macroकुछ संशोधन की जरूरत है।)
  2. कुछ सामग्रियों का कहना है कि इस विधि का उपयोग Direct3D में नहीं किया जा सकता है ।
  3. जब Microsoft असेंबलर के साथ x86 के लिए काम कर रहा है, तो इसमें और भी तेज़ी से macroलिखा गया है assembly(यह लुआ स्रोत से भी निकाला गया है):

    #define double2int(i,n)  __asm {__asm fld n   __asm fistp i}
  4. एकल सटीक संख्या के लिए एक समान मैजिक नंबर है: 1.5 * 2 ^23


3
"उपवास" की तुलना में क्या?
कोरी नेल्सन

3
@CoryNelson फास्ट एक साधारण कलाकारों की तुलना में। यह विधि, जब ठीक से लागू की जाती है (एसएसई इंट्रिंसिक्स के साथ) एक कास्ट की तुलना में सचमुच एक सौ गुना तेज है। (जो एक बहुत महंगा रूपांतरण कोड के लिए एक गंदा फ़ंक्शन कॉल को आमंत्रित करता है)
रहस्यवादी

2
सही - मैं इसे तेजी से होने से देख सकता हूं ftoi। लेकिन अगर आप एसएसई की बात कर रहे हैं, तो सिर्फ एक निर्देश का उपयोग क्यों न करें CVTTSD2SI?
कोरी नेल्सन

3
@tmyklebu उपयोग के कई ऐसे मामले हैं जो double -> int64वास्तव में 2^52सीमा के भीतर हैं । फ्लोटिंग-पॉइंट एफएफटी का उपयोग करते हुए पूर्णांक आक्षेप करते समय ये विशेष रूप से सामान्य हैं।
रहस्यपूर्ण

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

जवाबों:


161

A doubleको इस तरह दर्शाया गया है:

दोहरा प्रतिनिधित्व

और इसे दो 32-बिट पूर्णांक के रूप में देखा जा सकता है; अब, intआपके कोड के सभी संस्करणों में लिया गया (यह 32-बिट को दबाते हुए int) आंकड़ा में दाईं ओर एक है, इसलिए आप अंत में जो कर रहे हैं, वह सिर्फ सबसे कम 32 बिट्स मंटिसा ले रहा है।


अब, जादू की संख्या के लिए; जैसा कि आपने ठीक कहा, 6755399441055744 2 ^ 51 + 2 ^ 52 है; इस तरह की संख्या को जोड़ने पर double2 ^ 52 और 2 ^ 53 के बीच "स्वीट रेंज" में जाने के लिए मजबूर किया जाता है, जिसे विकिपीडिया द्वारा यहाँ बताया गया है , एक दिलचस्प संपत्ति है:

2 52 = 4,503,599,627,370,496 और 2 53 = 9,007,199,254,740,992 के बीच प्रतिनिधित्व करने योग्य संख्या पूर्णांक हैं

यह इस तथ्य से अनुसरण करता है कि मंटिसा 52 बिट्स चौड़ा है।

2 51 +2 52 को जोड़ने के बारे में अन्य दिलचस्प तथ्य यह है कि यह केवल दो उच्चतम बिट्स में मंटिसा को प्रभावित करता है - जो वैसे भी त्याग दिए जाते हैं, क्योंकि हम केवल इसके सबसे कम 32 बिट्स ले रहे हैं।


अंतिम लेकिन कम से कम नहीं: संकेत।

IEEE 754 फ़्लोटिंग पॉइंट एक परिमाण और हस्ताक्षर प्रतिनिधित्व का उपयोग करता है, जबकि "सामान्य" मशीनों पर पूर्णांक 2 के पूरक अंकगणित का उपयोग करते हैं; यहाँ कैसे संभाला जाता है?

हमने केवल सकारात्मक पूर्णांक के बारे में बात की; अब मान लें कि हम 32-बिट द्वारा प्रतिनिधित्व योग्य सीमा में एक नकारात्मक संख्या के साथ काम कर रहे हैं int, (-2 ^ 31 + 1) की तुलना में कम (पूर्ण मूल्य में); इसे बुलाओ -a। इस तरह की संख्या को स्पष्ट रूप से जादू की संख्या जोड़कर सकारात्मक बनाया जाता है, और परिणामी मान 2 52 +2 51 + (- a) है।

अब, यदि हम 2 के पूरक प्रतिनिधित्व में मंटिसा की व्याख्या करते हैं तो हमें क्या मिलेगा? यह 2 (2 52 +2 51 ) और (-a) के पूरक योग का परिणाम होना चाहिए । फिर से, पहला शब्द केवल ऊपरी दो बिट्स को प्रभावित करता है, बिट्स में क्या रहता है 0 ~ 50 2 -a का पूरक प्रतिनिधित्व है (-ए) (फिर से, ऊपरी दो बिट्स)।

चूंकि छोटी चौड़ाई में 2 के पूरक संख्या में कमी बस बाईं ओर के अतिरिक्त बिट्स को काटकर की जाती है, इसलिए निचले 32 बिट्स को 32 बिट में, 2 के पूरक अंकगणित में सही ढंग से (-ए) देता है।


"" "2 ^ 51 + 2 ^ 52 को जोड़ने के बारे में अन्य दिलचस्प तथ्य यह है कि यह केवल दो उच्चतम बिट्स में मंटिसा को प्रभावित करता है - जो वैसे भी खारिज कर दिया जाता है, क्योंकि हम केवल इसके सबसे कम 32 बिट्स ले रहे हैं" "" वह क्या है? इसे जोड़ने से सभी मंटिसा शिफ्ट हो सकती हैं!
यवसगेरे

@ जॉन: बेशक, उन्हें जोड़ने का पूरा बिंदु मूल्य को उस सीमा में होने के लिए मजबूर करना है, जो स्पष्ट रूप से मूल मूल्य के संबंध में मंटिसा (अन्य चीजों के बीच) को स्थानांतरित कर सकता है। जो मैं यहाँ कह रहा था, वह यह है कि एक बार जब आप उस सीमा में होते हैं, तो केवल ५३ बिट्स के पूर्णांक से भिन्न बिट्स ५१ और ५२ होते हैं, जो वैसे भी छूट जाते हैं।
मट्टियो इटालिया

2
उन लोगों के लिए जो int64_tआपको परिवर्तित करना चाहते हैं, वे कर सकते हैं कि बायीं ओर मंटिसा को शिफ्ट करके और फिर 13 बिट्स द्वारा दाईं ओर। यह 'मैजिक' नंबर से घातांक और दो बिट्स को साफ करेगा, लेकिन साइन को पूरे 64-बिट हस्ताक्षरित पूर्णांक में रखेगा और प्रचारित करेगा। union { double d; int64_t l; } magic; magic.d = input + 6755399441055744.0; magic.l <<= 13; magic.l >>= 13;
वोज्शिएच मिग्डा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.