log (x)의 더 빠른 근사에 대하여


10

얼마 전에 라이브러리 함수를 사용하지 않고 를 계산하려고 시도한 코드를 작성했습니다 . 어제, 나는 이전 코드를 검토하고 있었고 가능한 한 빨리 (올바르게) 만들려고했습니다. 지금까지 내 시도는 다음과 같습니다.log(x)

const double ee = exp(1);

double series_ln_taylor(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now;
    int i, flag = 1;

    if ( n <= 0 ) return 1e-300;
    if ( n * ee < 1 )
        n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    for ( term = 1; term < n ; term *= ee, lgVal++ );
    n /= term;

    /* log(1 - x) = -x - x**2/2 - x**3/3... */
    n = 1 - n;
    now = term = n;
    for ( i = 1 ; ; ){
        lgVal -= now;
        term *= n;
        now = term / ++i;
        if ( now < 1e-17 ) break;
    }

    if ( flag == -1 ) lgVal = -lgVal;

    return lgVal;
}

여기에 내가 찾으려고 노력하고 그래서 전자 A는 그냥 n은, 그리고 그때의 로그 값을 추가 N을aea 는 1보다 작습니다.이 시점에서걱정없이log(1-x)의 Taylor 확장을사용할 수 있습니다.nealog(1  x)

최근에 수치 분석에 관심을 가지게되었으므로 질문을하는데 도움이되지 않는 이유는이 코드 세그먼트를 실제로 얼마나 빨리 실행할 수 있을까요? 내가 좋아하는, 계속 분수를 사용하여, 예를 들어, 다른 방법으로 전환해야합니까 ?

C 표준 라이브러리가 제공 기능은 거의 5.1 배 빠른 구현이보다 길다.log(x)

업데이트 1 : Wikipedia에 언급 쌍곡선 arctan 시리즈를 사용하면 계산이 C 표준 라이브러리 로그 함수보다 거의 2.2 배 느립니다. 비록 성능을 광범위하게 확인하지는 않았지만 더 많은 수의 경우 현재 구현이 매우 느립니다. 관리 할 수 ​​있다면 넓은 범위의 숫자에 대한 오류 바운드 및 평균 시간에 대한 구현을 모두 확인하고 싶습니다. 두 번째 노력은 다음과 같습니다.

double series_ln_arctanh(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now, sm;
    int i, flag = 1;

    if ( n <= 0 ) return 1e-300;
    if ( n * ee < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    for ( term = 1; term < n ; term *= ee, lgVal++ );
    n /= term;

    /* log(x) = 2 arctanh((x-1)/(x+1)) */
    n = (1 - n)/(n + 1);

    now = term = n;
    n *= n;
    sm = 0;
    for ( i = 3 ; ; i += 2 ){
        sm += now;
        term *= n;
        now = term / i;
       if ( now < 1e-17 ) break;
    }

    lgVal -= 2*sm;

    if ( flag == -1 ) lgVal = -lgVal;
    return lgVal;
}

어떤 제안이나 비판도 감사합니다.

1e81e3084e15

double series_ln_better(double n){ /* n = e^a * b, where a is an non-negative integer */
    double lgVal = 0, term, now, sm;
    int i, flag = 1;

    if ( n == 0 ) return -1./0.; /* -inf */
    if ( n < 0 ) return 0./0.;   /* NaN*/
    if ( n < 1 ) n = 1.0 / n, flag = -1; /* for extremely small n, use e^-x = 1/n */

    /* the cutoff iteration is 650, as over e**650, term multiplication would
       overflow. For larger numbers, the loop dominates the arctanh approximation
       loop (with having 13-15 iterations on average for tested numbers so far */

    for ( term = 1; term < n && lgVal < 650 ; term *= ee, lgVal++ );
    if ( lgVal == 650 ){
        n /= term;
        for ( term = 1 ; term < n ; term *= ee, lgVal++ );
    }
    n /= term;

    /* log(x) = 2 arctanh((x-1)/(x+1)) */
    n = (1 - n)/(n + 1);

    now = term = n;
    n *= n;
    sm = 0;

    /* limiting the iteration for worst case scenario, maximum 24 iteration */
    for ( i = 3 ; i < 50 ; i += 2 ){
        sm += now;
        term *= n;
        now = term / i;
        if ( now < 1e-17 ) break;
    }

    lgVal -= 2*sm;

    if ( flag == -1 ) lgVal = -lgVal;

    return lgVal;
}

답변:


17

이것은 실제로 권위있는 답변 이 아니며 , 고려해야 할 문제 목록이 많으며 코드를 테스트하지 않았습니다.

log2.15.1

f(x)doublen12

n1.7976e+308term=infn=11017nterm *= e709.78266108405500745

1030000

약간의 노력만으로도 인수 범위를 제한하거나 약간 덜 정확한 결과를 반환함으로써 성능에 대한 견고성을 희생 할 수 있다고 생각합니다.

3. 이러한 종류의 코드의 성능은 실행중인 CPU 아키텍처에 따라 크게 달라질 수 있습니다. 이 주제는 심오하고 관련이 있지만 Intel과 같은 CPU 제조업체는 코드와 실행중인 CPU 간의 다른 상호 작용을 설명하는 최적화 안내서를 게시합니다. 캐싱은 비교적 간단 할 수 있지만 데이터 종속성으로 인한 분기 예측, 명령 수준 병렬 및 파이프 라인 중단과 같은 것은 높은 수준의 코드에서 정확하게보기 어렵지만 성능에는 큰 문제가됩니다.

x~y~=f~(x~)y=f(x~)정확한?). 이는 부동 소수점 반올림 오류로 인해 Taylor 계열이 수렴한다는 것을 보여주는 것과 다릅니다.

4.5. 테스트되지 않은 함수의 정확성을 테스트하는 좋은 방법은 40 억 개 중 하나에서 평가하고 (여기서와 같이 인수 축소를 올바르게 수행하면 적음) 단 정밀도 부동 소수점을 사용하여 오류를 표준 로그와 비교하는 것입니다 libm. 약간의 시간이 걸리지 만 적어도 철저합니다.

5. 처음부터 double의 정밀도를 알고 있으므로 무한 루프를 가질 필요는 없습니다. 반복 횟수를 미리 파악할 수 있습니다 (약 50 개 정도). 이것을 사용하여 코드에서 분기를 제거하거나 최소한 반복 횟수를 미리 설정하십시오.

루프 언 롤링에 대한 모든 일반적인 아이디어도 적용됩니다.

6. Taylor 계열 이외의 근사 기법을 사용할 수 있습니다. 또한 Chebyshev 시리즈 (Clenshaw 되풀이 포함), Pade 근사법 및 때로는 함수가 더 간단한 함수 (예 : 유명한 sqrt 트릭 ) 의 근본으로 다시 변환 될 수있을 때마다 Newton의 방법과 같은 근본 찾기 방법이 있습니다 .

계속되는 분수는 곱셈 / 덧셈보다 훨씬 비싼 나눗셈을 포함하기 때문에 너무 크지 않을 것입니다. 당신이 보는 경우 _mm_div_sshttps://software.intel.com/sites/landingpage/IntrinsicsGuide/ , 부문은 3 ~ 5 / 0.5에 비해 아키텍처에 따라 13 ~ 14 사이클의 처리량 5-14의 대기 시간이 곱하기 / 추가 / 매드 따라서 일반적으로 (항상 그런 것은 아님) 분할을 가능한 한 많이 제거하는 것이 좋습니다.

불행하게도, 수학이없는 으로 표현하기 때문에, 여기에 좋은 가이드 짧은 공식이 반드시 가장 빠른 사람이 아니다. 예를 들어 수학은 나눗셈에 불이익을주지 않습니다.

x=m×2em12<m1exfrexp

8. 당신의 비교 logloglibm또는 openlibm(: 예 https://github.com/JuliaLang/openlibm/blob/master/src/e_log.c ). 이것은 다른 사람들이 이미 알아 낸 것을 찾는 가장 쉬운 방법입니다. libm CPU 제조업체 에 따라 특별히 최적화 된 버전도 있지만 일반적으로 소스 코드가 게시되어 있지 않습니다.

Boost :: sf에는 몇 가지 특수 기능이 있지만 기본 기능은 아닙니다. 그러나 log1p의 소스를 보는 것이 도움이 될 수 있습니다. http://www.boost.org/doc/libs/1_58_0/libs/math/doc/html/math_toolkit/powers/log1p.html

mpfr과 같은 오픈 소스 임의 정밀도 산술 라이브러리도 있으며, 이는 높은 정밀도로 인해 libm과 다른 알고리즘을 사용할 수 있습니다.

9. 수치 알고리즘의 Higham의 정확성 및 안정성은 수치 알고리즘의 오류를 분석하는 데 유용한 상위 레벨 소개입니다. 근사 알고리즘 자체의 경우 Trefethen의 근사 이론 근사 연습이 좋은 참고 자료입니다.

10. 나는 이것이 너무 자주 언급된다는 것을 알고 있지만, 합리적으로 큰 소프트웨어 프로젝트는 하나의 작은 함수가 계속해서 호출되는 런타임에 의존하지 않습니다. 그것은 아니에요 그래서 당신은 당신의 프로그램을 프로파일 있는지가 중요했습니다 않는 한, 로그의 성능에 대해 걱정할 필요가하는 것이 일반적.


26414e15

1.13e13term

 1e8

1
k=11071lnk

2
frexp x=m×2elnx=eln2+lnm

5

Kirill의 답변은 이미 많은 관련 문제를 다루었습니다. 실용적인 수학 라이브러리 디자인 경험을 바탕으로 그 중 일부를 확장하고 싶습니다. 참고 사항 : 수학 라이브러리 디자이너는 게시 된 모든 알고리즘 최적화뿐만 아니라 많은 머신 별 최적화를 사용하는 경향이 있습니다. 코드는 컴파일 된 코드를 사용하지 않고 어셈블리 언어로 작성되는 경우가 많습니다. 따라서 단순하고 컴파일 된 구현이 동일한 기능 세트 (정확도, 특수 사례 처리, 오류보고, 반올림 모드 지원)를 가정하여 기존 고품질 수학 라이브러리 구현의 성능의 75 % 이상을 달성 할 가능성은 없습니다.

explogerfcΓ

정확도는 일반적으로 (타사) 고정밀 기준과 비교하여 평가됩니다. 단일 인수 단정도 함수는 쉽게 철저하게 테스트 할 수 있으며, 다른 함수에는 (지정된) 임의 테스트 벡터를 사용한 테스트가 필요합니다. 분명히 정확한 참조 결과를 계산할 수는 없지만 Table-Maker의 딜레마 에 대한 연구는 많은 간단한 함수의 경우 목표 정밀도의 약 3 배의 정밀도로 참조를 계산하면 충분하다고 제안합니다. 예를 들어 :

Jean-Michel Muller, Vincent Lefèvre, "배정도에서 기본 함수의 올바른 반올림에 대한 최악의 사례". 에서 컴퓨터 산술에 대한 절차 15 IEEE 심포지엄 , 2001,111-118). (온라인 사전 인쇄)

성능면에서 대기 시간 최적화 (종속 작업의 실행 시간을 볼 때 중요)와 처리량 최적화 (독립 작업의 실행 시간을 고려할 때)를 구분해야합니다. 지난 20 년 동안 명령 수준 병렬 처리 (예 : 슈퍼 스칼라, 비 순차 프로세서), 데이터 수준 병렬 처리 (예 : SIMD 명령) 및 스레드 수준 병렬 처리 (예 : 하이퍼 스레딩, multi-core processor)는보다 관련성이 높은 메트릭으로서 계산 처리량에 중점을 두었습니다.

log(1+x)=p(x)log(x)=2atanh((x1)/(x+1))=p(((x1)/(x+1))2)p

25 년 전에 IBM이 처음 도입했으며 현재 모든 주요 프로세서 아키텍처에서 사용 가능한 FMA (fused multiply-add operation )는 현대 수학 라이브러리 구현의 중요한 구성 요소입니다. 반올림 오류 감소를 제공 하고 감산 취소 에 대한 제한적인 보호를 제공 하며 더블 더블 산술을 크게 단순화 합니다.

C99log()C99fma()233

#include <math.h>

/* compute natural logarithm

   USE_ATANH == 1: maximum error found: 0.83482 ulp @ 0.7012829191167614
   USE_ATANH == 0: maximum error found: 0.83839 ulp @ 1.2788954397331760
*/
double my_log (double a)
{
    const double LOG2_HI = 0x1.62e42fefa39efp-01; // 6.9314718055994529e-01
    const double LOG2_LO = 0x1.abc9e3b39803fp-56; // 2.3190468138462996e-17
    double m, r, i, s, t, p, f, q;
    int e;

    m = frexp (a, &e);
    if (m < 0.70703125) { // 181/256
        m = m + m;
        e = e - 1;
    }
    i = (double)e;

    /* m in [181/256, 362/256] */

#if USE_ATANH
    /* Compute q = (m-1) / (m+1) */
    p = m + 1.0;
    m = m - 1.0;
    q = m / p;

    /* Compute (2*atanh(q)/q-2*q) as p(q**2), q in [-75/437, 53/309] */
    s = q * q;
    r =             0x1.2f1da230fb057p-3;  // 1.4800574027992994e-1
    r = fma (r, s,  0x1.399f73f934c01p-3); // 1.5313616375223663e-1
    r = fma (r, s,  0x1.7466542530accp-3); // 1.8183580149169243e-1
    r = fma (r, s,  0x1.c71c51a8bf129p-3); // 2.2222198291991305e-1
    r = fma (r, s,  0x1.249249425f140p-2); // 2.8571428744887228e-1
    r = fma (r, s,  0x1.999999997f6abp-2); // 3.9999999999404662e-1
    r = fma (r, s,  0x1.5555555555593p-1); // 6.6666666666667351e-1
    r = r * s;

    /* log(a) = 2*atanh(q) + i*log(2) = LOG2_LO*i + p(q**2)*q + 2q + LOG2_HI*i.
       Use K.C. Ng's trick to improve the accuracy of the computation, like so:
       p(q**2)*q + 2q = p(q**2)*q + q*t - t + m, where t = m**2/2.
    */
    t = m * m * 0.5;
    r = fma (q, t, fma (q, r, LOG2_LO * i)) - t + m;
    r = fma (LOG2_HI, i, r);

#else // USE_ATANH

    /* Compute f = m -1 */
    f = m - 1.0;
    s = f * f;

    /* Approximate log1p (f), f in [-75/256, 106/256] */
    r = fma (-0x1.961d64ddd82b6p-6, f, 0x1.d35fd598b1362p-5); // -2.4787281515616676e-2, 5.7052533321928292e-2
    t = fma (-0x1.fcf5138885121p-5, f, 0x1.b97114751d726p-5); // -6.2128580237329929e-2, 5.3886928516403906e-2
    r = fma (r, s, t);
    r = fma (r, f, -0x1.b5b505410388dp-5); // -5.3431043874398211e-2
    r = fma (r, f,  0x1.dd660c0bd22dap-5); //  5.8276198890387668e-2
    r = fma (r, f, -0x1.00bda5ecdad6fp-4); // -6.2680862565391612e-2
    r = fma (r, f,  0x1.1159b2e3bd0dap-4); //  6.6735934054864471e-2
    r = fma (r, f, -0x1.2489f14dd8883p-4); // -7.1420614809115476e-2
    r = fma (r, f,  0x1.3b0ee248a0ccfp-4); //  7.6918491287915489e-2
    r = fma (r, f, -0x1.55557d3b497c3p-4); // -8.3333481965921982e-2
    r = fma (r, f,  0x1.745d4666f7f48p-4); //  9.0909266480136641e-2
    r = fma (r, f, -0x1.999999d959743p-4); // -1.0000000092767629e-1
    r = fma (r, f,  0x1.c71c70bbce7c2p-4); //  1.1111110722131826e-1
    r = fma (r, f, -0x1.fffffffa61619p-4); // -1.2499999991822398e-1
    r = fma (r, f,  0x1.249249262c6cdp-3); //  1.4285714290377030e-1
    r = fma (r, f, -0x1.555555555f03cp-3); // -1.6666666666776730e-1
    r = fma (r, f,  0x1.999999999759ep-3); //  1.9999999999974433e-1
    r = fma (r, f, -0x1.fffffffffff53p-3); // -2.4999999999999520e-1
    r = fma (r, f,  0x1.555555555555dp-2); //  3.3333333333333376e-1
    r = fma (r, f, -0x1.0000000000000p-1); // -5.0000000000000000e-1

    /* log(a) = log1p (f) + i * log(2) */
    p = fma ( LOG2_HI, i, f);
    t = fma (-LOG2_HI, i, p);
    f = fma ( LOG2_LO, i, f - t);
    r = fma (r, s, f);
    r = r + p;
#endif // USE_ATANH

    /* Handle special cases */
    if (!((a > 0.0) && (a <= 0x1.fffffffffffffp1023))) {
        r = a + a;  // handle inputs of NaN, +Inf
        if (a  < 0.0) r =  0.0 / 0.0; //  NaN
        if (a == 0.0) r = -1.0 / 0.0; // -Inf
    }
    return r;
}

(+1) 일반적인 오픈 소스 구현 (openlibm과 같은)이 가능한 한 좋은지 또는 특별한 기능을 향상시킬 수 있는지 알고 있습니까?
Kirill

1
@Kirill 마지막으로 (몇 년 전) 오픈 소스 구현을 살펴 봤지만 FMA의 이점을 활용하지 않았습니다. 당시 IBM Power와 Intel Itanium은이 작업을 포함하는 유일한 아키텍처였으며 이제는 하드웨어 지원이 어디에나 있습니다. 또한 테이블 플러스 다항식 근사법은 당시 최첨단 기술이었으며 이제는 테이블이 선호되지 않습니다. 메모리 액세스로 인해 에너지 사용량이 높아지고 벡터화를 방해 할 수 있으며 계산 처리량이 메모리 처리량보다 증가했습니다. 결과적으로 테이블에 부정적인 영향을 줄 수 있습니다.
njuffa
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.