आप पूर्णांक के लिए जावा में लॉग बेस 2 की गणना कैसे करते हैं?


138

पूर्णांक के लिए लॉग बेस 2 की गणना करने के लिए मैं निम्नलिखित फ़ंक्शन का उपयोग करता हूं:

public static int log2(int n){
    if(n <= 0) throw new IllegalArgumentException();
    return 31 - Integer.numberOfLeadingZeros(n);
}

क्या इसका इष्टतम प्रदर्शन है?

क्या कोई उस उद्देश्य के लिए तैयार J2SE API फ़ंक्शन जानता है?

UPD1 मेरे लिए हैरानी की बात है, फ्लोट प्वाइंट अंकगणित पूर्णांक अंकगणित की तुलना में तेज प्रतीत होता है।

UPD2 टिप्पणियों के कारण मैं अधिक विस्तृत जांच करूंगा।

UPD3 मेरा पूर्णांक अंकगणितीय फ़ंक्शन Math.log (n) /Math.log (2) से 10 गुना तेज है।


1
आपने इसके प्रदर्शन का परीक्षण कैसे किया? मेरे सिस्टम पर (कोर i7, jdk 1.6 x64) पूर्णांक संस्करण फ्लोटिंग पॉइंट संस्करण की तुलना में लगभग 10 गुना तेज है। वास्तव में फ़ंक्शन के परिणाम के साथ कुछ करना सुनिश्चित करें ताकि जेआईटी पूरी तरह से गणना को हटा न सके!
x4u

तुम सही हो। मैंने गणना के परिणामों का उपयोग नहीं किया और संकलक ने कुछ अनुकूलित किया है। अब मेरे पास आपके जैसा ही परिणाम है - पूर्णांक फ़ंक्शन 10 गुना तेज है (कोर 2 डुओ, jdk 1.6
c64

6
यह प्रभावी रूप से आपको देता है Math.floor(Math.log(n)/Math.log(2)), इसलिए इसकी वास्तव में लॉग बेस 2 की गणना नहीं है!
डोरी

जवाबों:


74

यदि आप पूर्णांक अंकगणित में मदद करने के लिए फ्लोटिंग-पॉइंट का उपयोग करने के बारे में सोच रहे हैं, तो आपको सावधान रहना होगा।

मैं आमतौर पर जब भी संभव हो एफपी गणना से बचने की कोशिश करता हूं।

फ्लोटिंग-पॉइंट ऑपरेशन सटीक नहीं हैं। आप कभी भी यह सुनिश्चित करने के लिए नहीं जान सकते हैं कि इसका (int)(Math.log(65536)/Math.log(2))मूल्यांकन क्या होगा । उदाहरण के लिए, Math.ceil(Math.log(1<<29) / Math.log(2))मेरे पीसी पर 30 है जहां गणितीय रूप से यह बिल्कुल 29 होना चाहिए। मुझे x के लिए कोई मान नहीं मिला जहां (int)(Math.log(x)/Math.log(2))विफल रहता है (सिर्फ इसलिए कि केवल 32 "खतरनाक" मूल्य हैं), लेकिन इसका मतलब यह नहीं है कि यह काम करेगा किसी भी पीसी पर उसी तरह।

गोलाई में यहाँ सामान्य चाल "एप्सिलॉन" का उपयोग किया जाता है। जैसे (int)(Math.log(x)/Math.log(2)+1e-10)कभी असफल नहीं होना चाहिए। इस "एप्सिलॉन" का विकल्प एक तुच्छ कार्य नहीं है।

अधिक सामान्य कार्य का उपयोग करते हुए अधिक प्रदर्शन, लागू करने की कोशिश int log(int x, int base):

परीक्षण कोड:

static int pow(int base, int power) {
    int result = 1;
    for (int i = 0; i < power; i++)
        result *= base;
    return result;
}

private static void test(int base, int pow) {
    int x = pow(base, pow);
    if (pow != log(x, base))
        System.out.println(String.format("error at %d^%d", base, pow));
    if(pow!=0 && (pow-1) != log(x-1, base))
        System.out.println(String.format("error at %d^%d-1", base, pow));
}

public static void main(String[] args) {
    for (int base = 2; base < 500; base++) {
        int maxPow = (int) (Math.log(Integer.MAX_VALUE) / Math.log(base));
        for (int pow = 0; pow <= maxPow; pow++) {
            test(base, pow);
        }
    }
}

यदि हम लघुगणक के सबसे सीधे-सीधे कार्यान्वयन का उपयोग करते हैं,

static int log(int x, int base)
{
    return (int) (Math.log(x) / Math.log(base));
}

यह प्रिंट:

error at 3^5
error at 3^10
error at 3^13
error at 3^15
error at 3^17
error at 9^5
error at 10^3
error at 10^6
error at 10^9
error at 11^7
error at 12^7
...

त्रुटियों से पूरी तरह से छुटकारा पाने के लिए मुझे एप्सिलॉन को जोड़ना पड़ा जो 1e-11 और 1e-14 के बीच है। क्या आप परीक्षण से पहले यह बता सकते थे? मैं निश्चित रूप से नहीं कर सका।


3
"इसका मतलब यह नहीं है कि यह किसी भी पीसी पर उसी तरह काम करेगा" - यदि आप इसका इस्तेमाल करते हैं strictfp, तो नहीं?
केन

@Ken: हो सकता है ... लेकिन आप सभी संभावित इनपुट मानों को पूरी तरह से पूरा करने के बाद ही सुनिश्चित हो सकते हैं। (हम भाग्यशाली हैं कि यहां उनमें से बहुत कम हैं)
रॉटस्टर

2
तकनीकी रूप से, हाँ, लेकिन यह किसी भी फ़ंक्शन का सच है। कुछ बिंदु पर आपको यह विश्वास करना होगा कि यदि आप उपलब्ध दस्तावेज का उपयोग करते हैं, और "सभी संभावित इनपुट मूल्यों" के कुछ अच्छी तरह से चुने हुए लेकिन लुप्त हो गए छोटे अंश का परीक्षण करते हैं, तो यह है कि आपका कार्यक्रम काफी अच्छा काम करेगा। strictfpलगता है कि वास्तव में, वास्तव में, सख्त होने के लिए बहुत सी बकवास मिल गई है। :-)
केन

कैसे return ((long)Math.log(x) / (long)Math.log(base));सभी त्रुटियों को हल करने के बारे में ?
बग

92

यह वह फ़ंक्शन है जो मैं इस गणना के लिए उपयोग करता हूं:

public static int binlog( int bits ) // returns 0 for bits=0
{
    int log = 0;
    if( ( bits & 0xffff0000 ) != 0 ) { bits >>>= 16; log = 16; }
    if( bits >= 256 ) { bits >>>= 8; log += 8; }
    if( bits >= 16  ) { bits >>>= 4; log += 4; }
    if( bits >= 4   ) { bits >>>= 2; log += 2; }
    return log + ( bits >>> 1 );
}

यह Math.log की तुलना में Integer.numberOfLeadingZeros () (20-30%) और लगभग 10 गुना तेज (jdk 1.6 x64) की तुलना में थोड़ा तेज़ है।

private static final double log2div = 1.000000000001 / Math.log( 2 );
public static int log2fp0( int bits )
{
    if( bits == 0 )
        return 0; // or throw exception
    return (int) ( Math.log( bits & 0xffffffffL ) * log2div );
}

दोनों फ़ंक्शन सभी संभावित इनपुट मानों के लिए समान परिणाम लौटाते हैं।

अद्यतन: जावा 1.7 सर्वर JIT CPU इंट्रिंसिक्स के आधार पर वैकल्पिक कार्यान्वयन के साथ कुछ स्थिर गणित कार्यों को बदलने में सक्षम है। उन कार्यों में से एक Integer.numberOfLeadingZeros () है। तो 1.7 या नए सर्वर वीएम के साथ, प्रश्न में एक जैसा कार्यान्वयन वास्तव में binlogउपरोक्त की तुलना में थोड़ा तेज है । दुर्भाग्य से क्लाइंट JIT के पास यह अनुकूलन नहीं है।

public static int log2nlz( int bits )
{
    if( bits == 0 )
        return 0; // or throw exception
    return 31 - Integer.numberOfLeadingZeros( bits );
}

यह कार्यान्वयन सभी 2 ^ 32 संभावित इनपुट मानों के लिए समान परिणाम देता है, जैसा कि अन्य दो कार्यान्वयन मैंने ऊपर पोस्ट किया है।

यहां मेरे पीसी (सैंडी ब्रिज i7) पर वास्तविक रनटाइम हैं:

JDK 1.7 32 बिट्स क्लाइंट VM:

binlog:         11.5s
log2nlz:        16.5s
log2fp:        118.1s
log(x)/log(2): 165.0s

JDK 1.7 x64 सर्वर VM:

binlog:          5.8s
log2nlz:         5.1s
log2fp:         89.5s
log(x)/log(2): 108.1s

यह परीक्षण कोड है:

int sum = 0, x = 0;
long time = System.nanoTime();
do sum += log2nlz( x ); while( ++x != 0 );
time = System.nanoTime() - time;
System.out.println( "time=" + time / 1000000L / 1000.0 + "s -> " + sum );

9
x86 का BSRनिर्देश करता है 32 - numberOfLeadingZeros, लेकिन 0 के लिए अपरिभाषित है, इसलिए एक (JIT) संकलक को गैर-शून्य के लिए जांचना होगा यदि यह साबित नहीं कर सकता है कि यह नहीं है। बीएमआई अनुदेश सेट एक्सटेंशन (हसवेल और नया) पेश किया LZCNT, जो पूरी तरह numberOfLeadingZerosसे एक ही निर्देश में लागू करता है। वे दोनों 3 चक्र विलंबता, 1 प्रति चक्र थ्रूपुट हैं। तो मैं बिल्कुल उपयोग करने की सलाह दूंगा numberOfLeadingZeros, क्योंकि यह एक अच्छे JVM के लिए आसान बनाता है। (इसके बारे lzcntमें एक अजीब बात यह है कि यह रजिस्टर के पुराने मूल्य पर एक गलत निर्भरता है जो इसे ओवरराइट करता है।)
पीटर कॉर्ड्स

मुझे जावा 1.7 सर्वर JIT CPU इंट्रिनिक्स रिप्लेसमेंट के बारे में आपकी टिप्पणी में सबसे ज्यादा दिलचस्पी है। क्या आपके पास एक संदर्भ URL है? (जेआईटी सोर्स कोड लिंक ओके भी है।)
केविनरपे

37

प्रयत्न Math.log(x) / Math.log(2)


8
जबकि गणितीय रूप से यह सही है, कृपया ध्यान रखें कि Rotsor के उत्तर में बताए अनुसार फ्लोटिंग-पॉइंट अंकगणितीय के कारण गलत गणना का जोखिम है।
लेययुवा

28

आप पहचान का उपयोग कर सकते हैं

            log[a]x
 log[b]x = ---------
            log[a]b

तो यह log2 के लिए लागू होगा।

            log[10]x
 log[2]x = ----------
            log[10]2

बस इसे जावा मठ log10 विधि में प्लग करें ...।

http://mathforum.org/library/drmath/view/55565.html


3
जबकि गणितीय रूप से यह सही है, कृपया ध्यान रखें कि Rotsor के उत्तर में बताए अनुसार फ्लोटिंग-पॉइंट अंकगणितीय के कारण गलत गणना का जोखिम है।
लेययुवा

18

क्यों नहीं:

public static double log2(int n)
{
    return (Math.log(n) / Math.log(2));
}

6
जबकि गणितीय रूप से यह सही है, कृपया ध्यान रखें कि Rotsor के उत्तर में बताए अनुसार फ्लोटिंग-पॉइंट अंकगणितीय के कारण गलत गणना का जोखिम है।
लेययुवा

9

अमरूद पुस्तकालयों में समारोह है:

LongMath.log2()

इसलिए मैं इसका उपयोग करने का सुझाव देता हूं।


मैं इस पैकेज को अपने आवेदन में कैसे जोड़ सकता हूं?
एल्विन ममाडोव

यहां से जार डाउनलोड करें और इसे अपनी परियोजना के निर्माण पथ में जोड़ें।
देबोसमित रे

2
क्या मुझे केवल एक फ़ंक्शन का उपयोग करने के लिए अपने आवेदन में एक पुस्तकालय जोड़ना चाहिए?
ताश पेमहिवा

7
वास्तव में आप इसका उपयोग करने का सुझाव क्यों देंगे? अमरूद स्रोत का एक त्वरित पढ़ने से पता चलता है कि यह ओपी की विधि (कोड की कुछ बहुत स्पष्ट रूप से समझी गई लाइनों) के समान काम करता है, अन्यथा एक बेकार निर्भरता को जोड़ने की कीमत पर। सिर्फ इसलिए कि Google कुछ प्रदान करता है यह समस्या को समझने और खुद को हल करने की तुलना में बेहतर नहीं बनाता है।
डेव

3

X4u उत्तर में जोड़ने के लिए, जो आपको किसी संख्या के बाइनरी लॉग की मंजिल देता है, यह फ़ंक्शन किसी संख्या के बाइनरी लॉग के छत को लौटाता है:

public static int ceilbinlog(int number) // returns 0 for bits=0
{
    int log = 0;
    int bits = number;
    if ((bits & 0xffff0000) != 0) {
        bits >>>= 16;
        log = 16;
    }
    if (bits >= 256) {
        bits >>>= 8;
        log += 8;
    }
    if (bits >= 16) {
        bits >>>= 4;
        log += 4;
    }
    if (bits >= 4) {
        bits >>>= 2;
        log += 2;
    }
    if (1 << log < number)
        log++;
    return log + (bits >>> 1);
}

"संख्या" चर कहाँ है?
barteks2x

3

कुछ मामलों में काम किया जब मैंने Math.log10 का उपयोग किया:

public static double log2(int n)
{
    return (Math.log10(n) / Math.log10(2));
}

0

आइए जोड़ते हैं:

int[] fastLogs;

private void populateFastLogs(int length) {
    fastLogs = new int[length + 1];
    int counter = 0;
    int log = 0;
    int num = 1;
    fastLogs[0] = 0;
    for (int i = 1; i < fastLogs.length; i++) {
        counter++;
        fastLogs[i] = log;
        if (counter == num) {
            log++;
            num *= 2;
            counter = 0;
        }
    }
}

स्रोत: https://github.com/pochuan/cs166/blob/master/ps1/rmq/SparseTableRMQ.java


यह एक लुकअप टेबल बनाया जाएगा। ओपी ने एक लघुगणक को "गणना" करने का तेज़ तरीका पूछा।
दवे

-4

N के लॉग बेस 2 की गणना करने के लिए, निम्नलिखित अभिव्यक्ति का उपयोग किया जा सकता है:

double res = log10(n)/log10(2);

2
यह उत्तर पहले ही कई बार पोस्ट किया जा चुका है, और पहले ही राउंड-ऑफ त्रुटि के कारण संभावित रूप से गलत पाया गया है। नोट ओपी ने अभिन्न मूल्य के लिए कहा; यह बिल्कुल स्पष्ट नहीं है कि यहां से पूर्णांक तक पहुंचने के लिए किस चक्कर की सटीकता का उपयोग करने की आवश्यकता है।
Parker
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.