정수에서 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.