जावा में बिना अंकगणित

जैसा कि आप जानते हैं, जावा में कोई अहस्ताक्षरित प्रकार नहीं हैं। यदि C में आप unsigned int ( char , long ) लिख सकते हैं, तो जावा में यह काम नहीं करेगा। हालांकि, अक्सर अहस्ताक्षरित संख्याओं के साथ अंकगणितीय संचालन करना आवश्यक है। पहली नज़र में, ऐसा लगता है कि अहस्ताक्षरित प्रकारों की वास्तव में आवश्यकता नहीं होती है (आधे से कम MaxInt संख्याओं के लिए MaxInt बारे में सोचें, यदि आपको अधिक संख्याओं की आवश्यकता है, तो मुझे बस long और आगे BigInteger लेना होगा)। लेकिन मुख्य अंतर वास्तव में नहीं है कि आप हस्ताक्षरित या अहस्ताक्षरित इंट में कितने अलग-अलग गैर-नकारात्मक नंबर डाल सकते हैं, लेकिन उन पर अंकगणितीय संचालन और तुलना कैसे की जाती है। यदि आप बाइनरी प्रोटोकॉल के साथ या बाइनरी अंकगणित के साथ काम करते हैं, जहां हर बिट का उपयोग महत्वपूर्ण है, तो आपको अहस्ताक्षरित मोड में सभी बुनियादी संचालन करने में सक्षम होना चाहिए। क्रम में इन कार्यों पर विचार करें:

बाइट को शॉर्ट में बदलें (int, long)


सामान्य कास्ट (int) myByte एक संकेत के साथ 32 बिट्स तक विस्तारित होगा - इसका मतलब है कि यदि बाइट का उच्च बाइट 1 पर सेट किया गया था, तो परिणाम एक ही नकारात्मक संख्या होगी, लेकिन 32-बिट प्रारूप में लिखा गया है:

0xff -> 0xffffffff (-1)

अक्सर यह वह नहीं है जो हम चाहेंगे। 32 अहस्ताक्षरित बिट्स का विस्तार करने और 0x000000ff प्राप्त करने के लिए, जावा में आप लिख सकते हैं:

 int myInt = myByte & 0xff; short myShort = myByte & 0xff; 

साइन-कम तुलना


अहस्ताक्षरित तुलना के लिए, एक संक्षिप्त सूत्र है:

 int compareUnsigned(int a, int b) { return Integer.compare( a ^ 0x80000000, b ^ 0x80000000 ); } 

बाइट के लिए, क्रमशः छोटा और लंबा, स्थिरांक 0x8000 , 0x8000 और 0x8000000000000000L

जोड़, घटाव और गुणा


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

विभाजन


लाभांश -256 को 256 हम -1 देंगे। और हम 0x00ffffff 0xffffff00 / 0x100 0x00ffffff देना 0x00ffffff , न कि 0xffffffff (-1)byte , short और int समाधान उच्च बिट संख्या पर स्विच करना होगा:

 int a = 0xffffff00; int b = 0x100; int c = (int) ((a & 0xffffffffL) / b); // convert a to long before division 

लेकिन long साथ क्या करना है? ऐसे मामलों में BigInteger स्विच करना आमतौर पर एक विकल्प नहीं है - बहुत धीमा। यह केवल अपने हाथों में सब कुछ लेने और विभाजन को मैन्युअल रूप से लागू करने के लिए बनी हुई है। सौभाग्य से, हमारे सामने सबकुछ पहले से ही चोरी हो गया है - Google अमरूद में long लिए अहस्ताक्षरित विभाजन का कार्यान्वयन है, और यह बहुत तेज़ है। यदि आप इस लाइब्रेरी का उपयोग नहीं कर रहे हैं, तो सबसे आसान तरीका यह है कि अनसाइन्ट किए गए लोंगों से सीधे कोड का एक टुकड़ा फाड़ दें। जावा फाइल:

  /** * Returns dividend / divisor, where the dividend and divisor are treated as unsigned 64-bit * quantities. * * @param dividend the dividend (numerator) * @param divisor the divisor (denominator) * @throws ArithmeticException if divisor is 0 */ public static long divide(long dividend, long divisor) { if (divisor < 0) { // ie, divisor >= 2^63: if (compare(dividend, divisor) < 0) { return 0; // dividend < divisor } else { return 1; // dividend >= divisor } } // Optimization - use signed division if dividend < 2^63 if (dividend >= 0) { return dividend / divisor; } /* * Otherwise, approximate the quotient, check, and correct if necessary. Our approximation is * guaranteed to be either exact or one less than the correct value. This follows from fact * that floor(floor(x)/i) == floor(x/i) for any real x and integer i != 0. The proof is not * quite trivial. */ long quotient = ((dividend >>> 1) / divisor) << 1; long rem = dividend - quotient * divisor; return quotient + (compare(rem, divisor) >= 0 ? 1 : 0); } 

कोड को संकलित करने के लिए, आपको compare(long, long) के कार्यान्वयन को उधार लेना होगा:

  /** * Compares the two specified {@code long} values, treating them as unsigned values between * {@code 0} and {@code 2^64 - 1} inclusive. * * @param a the first unsigned {@code long} to compare * @param b the second unsigned {@code long} to compare * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is * greater than {@code b}; or zero if they are equal */ public static int compare(long a, long b) { return Longs.compare(flip(a), flip(b)); } 

और Longs.compare(long, long) + flip(long) :

  /** * A (self-inverse) bijection which converts the ordering on unsigned longs to the ordering on * longs, that is, {@code a <= b} as unsigned longs if and only if {@code flip(a) <= flip(b)} * as signed longs. */ private static long flip(long a) { return a ^ Long.MIN_VALUE; } /** * Compares the two specified {@code long} values. The sign of the value * returned is the same as that of {@code ((Long) a).compareTo(b)}. * * @param a the first {@code long} to compare * @param b the second {@code long} to compare * @return a negative value if {@code a} is less than {@code b}; a positive * value if {@code a} is greater than {@code b}; or zero if they are equal */ public static int compare(long a, long b) { return (a < b) ? -1 : ((a > b) ? 1 : 0); } 

बिट पाली


अंत में बिट ऑपरेशन के विषय को कवर करने के लिए, हम भी बदलावों को याद करते हैं। X86 असेंबलर में अलग-अलग टीमों का एक पूरा समूह होता है जो बिटवाइज़ शिफ्ट बनाते हैं - SHL, SHR, SAL, SAR, ROR, ROL, RCR, RCL। अंतिम 4 चक्रीय बदलाव करते हैं; जावा में कोई समकक्ष नहीं हैं। लेकिन तार्किक और अंकगणितीय बदलाव मौजूद हैं। लॉजिकल शिफ्ट (साइन को ध्यान में नहीं रखता है) - SHL (शिफ्ट लेफ्ट) और SHR (शिफ्ट राईट) - को क्रमशः << और >>> ऑपरेटर्स द्वारा जावा में लागू किया जाता है। तार्किक पारियों का उपयोग करके, आप दो की शक्तियों द्वारा पूर्णांक गुणन और विभाजन को जल्दी से कर सकते हैं। अंकगणित शिफ्ट दाईं ओर SAR (साइन को ध्यान में रखता है) - SAR - ऑपरेटर द्वारा कार्यान्वित किया जाता है >> अंकगणित बाईं पारी तार्किक के बराबर है, और इसलिए इसके लिए कोई विशेष ऑपरेटर नहीं है। यह अजीब लग सकता है कि इस ऑपरेशन के लिए कोडांतरक के पास एक विशेष ओपकोड है, लेकिन वास्तव में यह वही करता है, अर्थात्, एसएएल पूरी तरह से एसएचएल व्यवहार को दोहराता है, और यह इंटेल से प्रलेखन द्वारा स्पष्ट रूप से संकेत दिया गया है:
शिफ्ट अंकगणित बाएं (SAL) और शिफ्ट लॉजिकल लेफ्ट (SHL) निर्देश एक ही ऑपरेशन करते हैं; वे गंतव्य ऑपरेंड में बिट्स को बाईं ओर स्थानांतरित करते हैं (अधिक महत्वपूर्ण बिट स्थानों की ओर)। प्रत्येक पारी की गिनती के लिए, गंतव्य ऑपरेंड का सबसे महत्वपूर्ण बिट सीएफ ध्वज में स्थानांतरित किया जाता है, और कम से कम महत्वपूर्ण बिट को मंजूरी दे दी जाती है (चित्र देखें Intel764 में 7-7 और IA-32 आर्किटेक्चर सॉफ्टवेयर डेवलपर का मैनुअल, वॉल्यूम 1 )।

यही है, SAL को केवल समरूपता के लिए जोड़ा गया था, इस तथ्य को ध्यान में रखते हुए कि दाईं ओर एक बदलाव के लिए तार्किक और अंकगणित में एक विभाजन है। खैर, गोसलिंग ने परेशान नहीं करने का फैसला किया (और, मुझे लगता है, सही काम किया)।

तो हमारे पास निम्नलिखित हैं:

 a << 1; //   ,    2 a >> 1; //      (   2) a >>> 1; //      (    2) 

अंतिम सिफारिशें


Source: https://habr.com/ru/post/In225901/


All Articles