आपकी पसंदीदा बिट-वार तकनीक क्या है? [बन्द है]


14

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

उस बिंदु पर, मैंने कभी भी हस्ताक्षरित पूर्णांक के साथ शिफ्ट ऑपरेटरों का उपयोग करने के बारे में नहीं सोचा था। मैंने मुख्य रूप से निम्न-स्तरीय सॉफ़्टवेयर विकास में इस तकनीक का उपयोग किया था; इसलिए, मैंने हमेशा अहस्ताक्षरित पूर्णांक का उपयोग किया। C अहस्ताक्षरित पूर्णांकों पर तार्किक बदलाव करता है। तार्किक शिफ्ट सही प्रदर्शन करते समय साइन बिट पर कोई ध्यान नहीं दिया जाता है। खाली बिट्स शून्य से भरे हुए हैं। हालाँकि, C एक अंकगणित शिफ्ट ऑपरेशन करता है जब एक हस्ताक्षरित पूर्णांक दाएं को स्थानांतरित करता है। खाली बिट्स साइन बिट से भरे हुए हैं। यह अंतर शून्य की ओर छंटनी होने के बजाय अनंत की ओर एक नकारात्मक मूल्य का कारण बनता है, जो हस्ताक्षरित पूर्णांक विभाजन की तुलना में एक अलग व्यवहार है।

कुछ मिनटों के विचार के परिणामस्वरूप पहले क्रम में समाधान हुआ। समाधान सशर्त रूप से नकारात्मक मूल्यों को स्थानांतरित करने से पहले सकारात्मक मूल्यों में परिवर्तित करता है। शिफ्ट ऑपरेशन किए जाने के बाद एक मान सशर्त रूप से अपने नकारात्मक रूप में परिवर्तित हो जाता है।

int a = -5;
int n = 1;

int negative = q < 0; 

a = negative ? -a : a; 
a >>= n; 
a = negative ? -a : a; 

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

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

int a = -5;
int n = 1;

register int sign = (a >> INT_SIZE_MINUS_1) & 1

a = (a - sign) >> n + sign;   

एक दो के पूरक नकारात्मक मूल्य एक को घटाकर एक नकारात्मक पूरक के मूल्य में परिवर्तित हो जाते हैं। दूसरी तरफ, किसी के पूरक ऋणात्मक मान को एक में जोड़कर नकारात्मक पूरक के दो पूरक में बदल दिया जाता है। ऊपर सूचीबद्ध कोड काम करता है क्योंकि साइन बिट का उपयोग दो के पूरक से किसी के पूरक में बदलने के लिए किया जाता है और इसके विपरीत । केवल नकारात्मक मानों में उनके साइन बिट सेट होंगे; इसलिए, चर संकेत शून्य के बराबर होगा जब एक सकारात्मक है।

उपर्युक्त के साथ, क्या आप अन्य बिट-वार हैक के बारे में सोच सकते हैं जैसे कि ऊपर वाले ने इसे आपके चाल की थैली में बनाया है? आपकी पसंदीदा बिट-वार हैक क्या है? मैं हमेशा नए प्रदर्शन-उन्मुख बिट-वार हैक की तलाश में हूं।


3
यह सवाल और आपके खाते का नाम - दुनिया फिर से समझ में आता है ...
जेके

+1 दिलचस्प सवाल मेरा अनुसरण करने के लिए और साथ ही साथ;)
Anto

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

2
क्या आप जानते हैं कि इन तकनीकों के बारे में एक पूरी किताब है? - हैकर्स डिलाईट amazon.com/Hackers-Delight-Henry-S-Warren/dp/0201914654
nikie

हाँ, एक वेब साइट है जो बिट संचालन के लिए भी समर्पित है। मैं URL भूल जाता हूँ लेकिन google इसे जल्द ही चालू कर देगा।
जल्दी_अगले

जवाबों:


23

मुझे गोस्पर का हैक (HAKMEM # 175) पसंद है, एक नंबर लेने का बहुत ही चालाक तरीका और उसी नंबर के बिट्स सेट के साथ अगला नंबर प्राप्त करना है। यह उपयोगी है, उदाहरण के लिए, इस प्रकार से kवस्तुओं के संयोजन उत्पन्न करने में n:

int set = (1 << k) - 1;
int limit = (1 << n);
while (set < limit) {
    doStuff(set);

    // Gosper's hack:
    int c = set & -set;
    int r = set + c;
    set = (((r^set) >>> 2) / c) | r;
}

7
+1। लेकिन अब से, मैं एक टिप्पणी के बिना डिबगिंग सत्र के दौरान इसे खोजने के बारे में दुःस्वप्न होगा ।
निकी

@ मिक्की, मुआहाहाहा! (मैं प्रोजेक्ट यूलर समस्याओं जैसी चीजों के लिए इसका उपयोग करता हूं - मेरा दिन का काम बहुत अधिक संयोजन शामिल नहीं है)।
पीटर टेलर

7

फास्ट व्युत्क्रम वर्ग जड़ विधि एक वर्गमूल का प्रतिलोम है कि मैंने कभी देखा है कंप्यूटिंग के लिए सबसे विचित्र सा स्तरीय तकनीक का उपयोग करता है:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

फास्ट sqrt भी अद्भुत है। कार्मैक सबसे बड़ी कोडर में से एक लगती है।
बेंजामिन

विकिपीडिया के पास और भी पुराने स्रोत हैं, उदाहरण के लिए परे 3d.com/content/articles/15
MSalters

0

3 से डिवीजन - एक रन-टाइम लाइब्रेरी कॉल का सहारा लिए बिना।

यह उस विभाजन को 3 से बाहर कर देता है (स्टैक ओवरफ्लो पर एक संकेत के लिए धन्यवाद) को इस प्रकार समझा जा सकता है:

X / 3 = [(x / 4) + (x / 12)]

और X / 12 है (x / 4) / 3. यहाँ पुन: प्रकट होने का एक तत्व है।

यह भी पता चला है कि यदि आप उन संख्याओं की सीमा को सीमित करते हैं जो आप खेल रहे हैं, तो आप आवश्यक पुनरावृत्तियों की संख्या को सीमित कर सकते हैं।

और इस प्रकार, अहस्ताक्षरित पूर्णांकों के लिए <2000, निम्नलिखित एक तेज और सरल / 3 एल्गोरिथ्म है। (बड़ी संख्या के लिए, बस अधिक चरणों को जोड़ें)। कम्पाइलर इस से बाहर निकलने के लिए हेक का अनुकूलन करते हैं ताकि यह जल्दी और छोटा हो जाए:

स्थिर अहस्ताक्षरित लघु FastDivide3 (const अहस्ताक्षरित लघु arg)
{
  अहस्ताक्षरित लघु रनिंगसम;
  अहस्ताक्षरित छोटा भग्न;

  रनसुम = arg >> 2;

  फ्रैक्शनलट्वेल्थ = रनिंगसुम >> 2;
  रनसुम + = फ्रैक्शनलट्वेल्थ;

  फ्रैक्शनलट्वेल्थ >> = 2;
  रनसुम + = फ्रैक्शनलट्वेल्थ;

  फ्रैक्शनलट्वेल्थ >> = 2;
  रनसुम + = फ्रैक्शनलट्वेल्थ;

  फ्रैक्शनलट्वेल्थ >> = 2;
  रनसुम + = फ्रैक्शनलट्वेल्थ;

  // अधिक सटीकता के लिए उपरोक्त 2 लाइनों के अधिक दोहराए

  रिटर्न रनिंगसम;
}

1
बेशक, यह केवल बहुत ही अस्पष्ट माइक्रोकंट्रोलर पर प्रासंगिक है। पिछले दो दशकों में किए गए किसी भी वास्तविक सीपीयू को पूर्णांक विभाजन के लिए रन-टाइम लाइब्रेरी की आवश्यकता नहीं है।
MSalters

1
ओह यकीन है, लेकिन एक हार्डवेयर गुणक के बिना छोटे micros वास्तव में बहुत आम हैं। और यदि आप एम्बेडेड भूमि में काम करते हैं और बेचे गए प्रत्येक दस लाख उत्पादों पर $ 0.10 बचाना चाहते हैं तो आप कुछ गंदे चालों को बेहतर तरीके से जानते हैं। उस पैसे को बचाया = अतिरिक्त लाभ जो आपके बॉस को बहुत खुश करता है।
जल्दी_अगले

अच्छा, गंदा ... यह सिर्फ .0101010101(लगभग 1/3) से गुणा है । प्रो टिप: आप भी गुणा कर सकते हैं .000100010001और 101(जो सिर्फ 3 .010101010101
बिटशिफ्ट

मैं केवल पूर्णांक और कोई फ़्लोटिंग बिंदु के साथ ऐसा कैसे कर सकता हूं?
जल्दी से जल्दी

1
बिटवाइज़, x * 101 = x + x << 2. इसी प्रकार, x * 0.000100010001 x है >> 4 + x >> 8 + x >> 12. 12.
MSalters

0

यदि आप एर्लैंग में देखते हैं तो बिट संचालन करने के लिए एक संपूर्ण डीएसएल है। तो आप बिट्स द्वारा एक डेटास्ट्रक्चर को कुछ इस तरह कह सकते हैं:

<> = << 1,17,42: 16 >>।

पूर्ण विवरण यहां: http://www.erlang.org/doc/reference_manual/expressions.html#id75782

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