정수에서 Java의 로그베이스 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
이것의 성능을 어떻게 테스트 했습니까? 내 시스템 (Core i7, jdk 1.6 x64)에서 정수 버전은 부동 소수점 버전보다 거의 10 배 빠릅니다. 실제로 JIT가 계산을 완전히 제거 할 수 없도록 함수의 결과로 무언가를 수행하십시오!
— x4u

당신이 올바른지. 나는 계산 결과를 사용하지 않았고 컴파일러가 무언가를 최적화했습니다. 정수 함수가 10 배 빠릅니다 (Core 2 Duo, jdk 1.6 c64)
— Nulldevice

6
이것은 실제로 당신에게 Math.floor(Math.log(n)/Math.log(2))로그베이스 2를 계산하지는 않습니다.
— Dori

답변:


74

정수 산술을 돕기 위해 부동 소수점을 사용하려는 경우주의해야합니다.

나는 보통 가능할 때마다 FP 계산을 피하려고 노력합니다.

부동 소수점 연산이 정확하지 않습니다. 당신은 무엇을 (int)(Math.log(65536)/Math.log(2))평가 할지 확실히 알 수 없습니다 . 예를 들어, Math.ceil(Math.log(1<<29) / Math.log(2))수학적으로 정확히 29이어야하는 내 PC의 30입니다. x의 값을 찾지 못했습니다.(int)(Math.log(x)/Math.log(2)) 32 개의 "위험한"값만 있기 때문에 실패한 작동하지는 않습니다. 모든 PC에서 동일한 방식으로.

여기에서 일반적인 트릭은 반올림 할 때 "엡실론"을 사용하는 것입니다. 처럼(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 사이의 epsilon을 추가해야했습니다. 테스트하기 전에 이것을 말할 수 있습니까? 나는 확실히 할 수 없었다.


3
"그것은 모든 PC에서 동일한 방식으로 작동되는 것은 아닙니다"- 그것은 것 당신이 사용하는 경우 strictfp아니?
— Ken

@Ken : 아마도 ... 그러나 가능한 모든 입력 값을 철저히 열거 한 후에 만 ​​확신 할 수 있습니다. (우리는 여기에 너무 적은 것이 운이 좋다)
— Rotsor

2
기술적으로는 가능하지만 모든 기능에 해당됩니다. 어떤 시점에서, 사용 가능한 문서를 사용하고, "가능한 모든 입력 값"중에서 잘 선택되었지만 거의 소량을 테스트하면 프로그램이 제대로 작동 할 것임을 신뢰해야합니다. strictfp사실 엄밀히 말해서 많은 쓰레기를 얻은 것 같습니다. :-)
— Ken

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 );
}

Integer.numberOfLeadingZeros () (20-30 %)보다 약간 빠르며 다음과 같이 Math.log () 기반 구현보다 거의 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 );
}

두 함수 모두 가능한 모든 입력 값에 대해 동일한 결과를 반환합니다.

업데이트 : Java 1.7 서버 JIT는 몇 가지 정적 수학 함수를 CPU 내장 함수를 기반으로하는 대체 구현으로 대체 할 수 있습니다. 이러한 함수 중 하나는 Integer.numberOfLeadingZeros ()입니다. 따라서 1.7 이상의 서버 VM을 사용하면 문제의 구현과 같은 구현이 실제로 binlog위의 것보다 약간 빠릅니다 . 불행히도 클라이언트 JIT에는이 최적화가없는 것 같습니다.

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

이 구현은 또한 위에 게시 한 다른 두 구현과 동일한 2 ^ 32 가능한 입력 값에 대해 동일한 결과를 반환합니다.

내 PC의 실제 런타임은 다음과 같습니다 (Sandy Bridge 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 - numberOfLeadingZeros0이지만 undefined이므로 (JIT) 컴파일러는 필요하지 않다는 것을 증명할 수 없으면 0이 아닌 값을 확인해야합니다. BMI 명령어 세트 확장 (Haswell 및 newer)이 도입 되어 단일 명령어로 정확히 LZCNT구현 numberOfLeadingZeros됩니다. 둘 다 3 사이클 대기 시간, 사이클 당 1 처리량입니다. 따라서 numberOfLeadingZeros좋은 JVM을 쉽게 사용할 수 있기 때문에을 사용하는 것이 좋습니다 . (에 대한 한 가지 이상한 점은 lzcnt그것이 덮어 쓰는 레지스터의 이전 값에 잘못된 의존성을 가지고 있다는 것입니다.)
— Peter Cordes

Java 1.7 서버 JIT CPU 내장 대체에 대한 의견에 가장 관심이 있습니다. 참조 URL이 있습니까? (JIT 소스 코드 링크도 괜찮습니다.)
— kevinarpe

37

시험 Math.log(x) / Math.log(2)


8
수학적으로는 정확하지만 Rotsor의 답변에 설명 된대로 부정확 한 부동 소수점 산술로 인해 계산 오류가 발생할 위험이 있습니다.
— leeyuiwah

28

당신은 정체성을 사용할 수 있습니다

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

그래서 이것은 log2에 적용됩니다.

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

이것을 Java Math log10 메소드에 연결하십시오 ....

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


3
수학적으로는 정확하지만 Rotsor의 답변에 설명 된대로 부정확 한 부동 소수점 산술로 인해 계산 오류가 발생할 위험이 있습니다.
— leeyuiwah

18

왜 안되 겠어요 :

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

6
수학적으로는 정확하지만 Rotsor의 답변에 설명 된대로 부정확 한 부동 소수점 산술로 인해 계산이 잘못 될 위험이 있습니다.
— leeyuiwah

9

구아바 라이브러리에는 다음과 같은 기능이 있습니다.

LongMath.log2()

그래서 나는 그것을 사용하는 것이 좋습니다.


이 패키지를 어플리케이션에 어떻게 추가 할 수 있습니까?
— Elvin Mammadov

여기 에서 jar을 다운로드 하여 프로젝트의 빌드 경로에 추가하십시오.
— Debosmit Ray

2
하나의 함수를 사용하기 위해 라이브러리를 애플리케이션에 추가해야합니까?
— Tash Pemhiwa

7
왜 정확히 사용하는 것이 좋습니다? 구아바 소스를 빠르게 읽으면 쓸모없는 의존성을 추가하는 비용으로 OP의 방법 (매우 명확하게 이해되는 코드 라인)과 동일한 기능을 수행한다는 것을 알 수 있습니다. Google이 무언가를 제공한다고해서 문제와 해결책을 직접 이해하는 것보다 나아지는 것은 아닙니다.
— Dave

3

x4u 응답에 추가하여 숫자의 이진 로그 바닥을 제공하기 위해이 함수는 이진 로그의 ceil을 숫자로 반환합니다.

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


그것은 룩업 테이블을 만드는 것입니다. OP는 로그를 "계산"하는 더 빠른 방법을 요청했습니다.
— Dave

-4

n의 log base 2를 계산하기 위해 다음 표현식을 사용할 수 있습니다.

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

2
이 답변은 이미 여러 번 게시되었으며 반올림 오류로 인해 잠재적으로 부정확 한 것으로 나타났습니다. OP는 적분 값을 요구했습니다. 여기에서 정수로 가져 오기 위해 반올림 정밀도를 사용해야하는 것은 분명하지 않습니다.
— AnotherParker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.