programing

Clang이 초과 부동소수점 정밀도에 어떻게 대처하는지 설명하는 문서가 있습니까?

prostudy 2022. 8. 7. 16:04
반응형

Clang이 초과 부동소수점 정밀도에 어떻게 대처하는지 설명하는 문서가 있습니까?

1개의 부동소수점 명령이 387 명령어밖에 없는 상황에서 엄격한 IEEE 754 시멘틱스를 합리적인 비용으로 제공하는 것은 거의 불가능합니다(*).시켜, 「FPU」64의 FPU를 는, 특히 합니다.long double타이핑하다일반적인 "솔루션"은 사용 가능한 정밀도로만 중간 계산을 수행하고, 어느 정도 명확하게 정의된 경우에는 더 낮은 정밀도로 변환하는 것입니다.

GCC의 최근 버전은 Joseph S에 의해 설명된 해석에 따라 중간 계산에서 과도한 정밀도를 처리한다.마이어스는 2008년 GCC 메일링 리스트에 투고되었습니다.이 설명은 프로그램을 컴파일합니다.gcc -std=c99 -mno-sse2 -mfpmath=387버그이고 수정될 입니다.그리고 만약 그렇지 않다면, 그것은 버그이고 수정될 것입니다. S. s S Myers는 수 있게 것을 로 하고 .마이어스의 게시글에서 밝힌 의도는 그것을 예측 가능하게 만드는 것이다.

정밀도를 Clang의 ).-mno-sse2용)및 및및 및? ? ??

(*) EDIT: 이건 과장이에요.53비트의 중요도를 사용하도록 x87 FPU를 구성할 수 있는 경우 binary64를 에뮬레이트하는 것은 약간 귀찮지만 그다지 어렵지 않습니다.


R의 코멘트에 이어..다음은 제가 가지고 있는 Clang의 최신 버전과의 짧은 상호작용 로그입니다.

Hexa:~ $ clang -v
Apple clang version 4.1 (tags/Apple/clang-421.11.66) (based on LLVM 3.1svn)
Target: x86_64-apple-darwin12.4.0
Thread model: posix
Hexa:~ $ cat fem.c
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <fenv.h>

double x;
double y = 2.0;
double z = 1.0;

int main(){
  x = y + z;
  printf("%d\n", (int) FLT_EVAL_METHOD);
}
Hexa:~ $ clang -std=c99 -mno-sse2 fem.c
Hexa:~ $ ./a.out 
0
Hexa:~ $ clang -std=c99 -mno-sse2 -S fem.c
Hexa:~ $ cat fem.s 
…
    movl    $0, %esi
    fldl    _y(%rip)
    fldl    _z(%rip)
    faddp   %st(1)
    movq    _x@GOTPCREL(%rip), %rax
    fstpl   (%rax)
…

이것은 원래 제기된 질문에 대한 답변은 아니지만, 유사한 문제를 다루는 프로그래머라면 이 답변이 도움이 될 수 있습니다.

나는 정말로 어디에 인식된 어려움이 있는지 모르겠다.엄격한 IEEE-754 바이너리64 시멘틱스를 제공하고 80비트 길이의 더블 연산을 유지하는 것은 GCC-4.6.3과 clang-3.0(LLVM 3.0에 기반) 모두에서 잘 지정된 C99 캐스팅 규칙을 따르는 것 같습니다.

추가하도록 편집됨:그러나 Pascal Cuoq는 옳습니다.gcc-4.6.3이나 clang-llvm-3.0 모두 387 부동소수점 연산에서는 이러한 규칙을 올바르게 적용하지 않습니다.적절한 컴파일러 옵션을 지정하면 규칙은 컴파일 시 평가된 식에는 올바르게 적용되지만 런타임 식에는 적용되지 않습니다.다음 구분 뒤에 해결 방법이 나와 있습니다.

저는 분자역학 시뮬레이션 코드를 사용하고 있으며 반복성/예측성 요건을 잘 알고 있으며 가능한 한 최대한의 정밀도를 유지하고자 하는 욕구를 잘 알고 있기 때문에 제가 여기서 무슨 말을 하고 있는지 알고 있다고 주장합니다.이 답변은 툴이 존재하며 사용하기 쉽다는 것을 보여 줍니다.문제는 툴을 인식하지 못하거나 사용하지 않는 데서 발생합니다.

카한 요약C99 c c c ( : 예 c c c c c c c 。Wikipedia 예제 코드)에는 트릭이나 임시 변수가 전혀 필요하지 않습니다. 실장은 에는 컴파일러 최적화 레벨이 됩니다.예를 들어 다음과 같습니다.-O3 ★★★★★★★★★★★★★★★★★」-Ofast

C99는 (예: 5.4.2.2에서) 주조 및 할당 모두 모든 추가 범위와 정밀도를 제거한다고 명시적으로 명시합니다.은 '아, 하다, 하다, 하다'를 사용할 수 있다는 뜻입니다.long double 시 합니다.long double, 를 그 타입에 는, , 「IEE-754 바이너리」를 「IEE-754 바이너리」로 해 .IEEE-754 바이너리가 필요한 경우는, 그 타입에 캐스팅 해 주세요.double.

387 에서는, 상기의 양쪽의 컴파일러에 대해서, 캐스트가 할당과 부하를 생성합니다.이것에 의해, 80비트치는 IEEE-754 binary64 로 올바르게 반올림 됩니다.이 비용은 제 생각에 매우 합리적입니다.정확한 소요 시간은 아키텍처와 주변 코드에 따라 다릅니다.일반적으로 다른 코드와 인터리브하여 비용을 마이너스 수준으로 낮출 수 있습니다.MMX, SSE 또는 AVX를 사용할 수 있는 경우 해당 레지스터는 80비트 80387 레지스터와 분리되어 있으며 일반적으로 캐스트는 값을 MMX/SSE/AVX 레지스터로 이동함으로써 수행됩니다.

(을 사용하는 프로덕션 코드를 하는 것이 tempdouble임시변수에 는 '일시변수'로 할 수 .double ★★★★★★★★★★★★★★★★★」long double아키텍처와 속도/단축의 균형에 따라 달라집니다.

한마디로:

(expression)double모든 변수와 리터럴 상수가 정확하기 때문입니다.라고 쓰세요.(double)(expression)double★★★★★★ 。

이것은 복합 표현에도 적용되며, 때로는 많은 수준의 캐스팅으로 다루기 어려운 표현으로 이어질 수 있습니다.

「 」가 expr1 ★★★★★★★★★★★★★★★★★」expr2정밀도로 하고 싶지만된 각 80비트를 사용하십시오.

long double  expr1;
long double  expr2;
double       product = (double)(expr1) * (double)(expr2);

★★,product는 2개의 64비트 값의 곱으로 계산되며 80비트 정밀도로 계산되지 않고 반올림됩니다.제품을 80비트 정밀도로 계산한 후 반올림하면 다음과 같습니다.

double       other = expr1 * expr2;

아니면 무슨 일이 일어나고 있는지 정확히 알려주는 캐스팅을 추가해서

double       other = (double)((long double)(expr1) * (long double)(expr2));

알 수 합니다.product ★★★★★★★★★★★★★★★★★」other르르르르르

C99 캐스팅 규칙은 32비트/64비트/80비트/128비트 부동소수점 값을 혼재시킨 경우 사용할 수 있는 또 다른 도구일 뿐입니다. 플로트binary32와 binary64 플로트)를합니다.float ★★★★★★★★★★★★★★★★★」double대부분의 아키텍처에서)!

캐스팅 규칙을 올바르게 적용하기 위해 Pascal Cuoq의 탐색 코드를 다시 쓰면 더 명확해질까요?

#include <stdio.h>

#define TEST(eq) printf("%-56s%s\n", "" # eq ":", (eq) ? "true" : "false")

int main(void)
{
    double d = 1.0 / 10.0;
    long double ld = 1.0L / 10.0L;

    printf("sizeof (double) = %d\n", (int)sizeof (double));
    printf("sizeof (long double) == %d\n", (int)sizeof (long double));

    printf("\nExpect true:\n");
    TEST(d == (double)(0.1));
    TEST(ld == (long double)(0.1L));
    TEST(d == (double)(1.0 / 10.0));
    TEST(ld == (long double)(1.0L / 10.0L));
    TEST(d == (double)(ld));
    TEST((double)(1.0L/10.0L) == (double)(0.1));
    TEST((long double)(1.0L/10.0L) == (long double)(0.1L));

    printf("\nExpect false:\n");
    TEST(d == ld);
    TEST((long double)(d) == ld);
    TEST(d == 0.1L);
    TEST(ld == 0.1);
    TEST(d == (long double)(1.0L / 10.0L));
    TEST(ld == (double)(1.0L / 10.0));

    return 0;
}

GCC 와 clang 의 양쪽 모두의 출력은, 다음과 같습니다.

sizeof (double) = 8
sizeof (long double) == 12

Expect true:
d == (double)(0.1):                                     true
ld == (long double)(0.1L):                              true
d == (double)(1.0 / 10.0):                              true
ld == (long double)(1.0L / 10.0L):                      true
d == (double)(ld):                                      true
(double)(1.0L/10.0L) == (double)(0.1):                  true
(long double)(1.0L/10.0L) == (long double)(0.1L):       true

Expect false:
d == ld:                                                false
(long double)(d) == ld:                                 false
d == 0.1L:                                              false
ld == 0.1:                                              false
d == (long double)(1.0L / 10.0L):                       false
ld == (double)(1.0L / 10.0):                            false

이 GCC의 한다.ld == 0.1 늘리다ld == 0.1L을 산출합니다.true SSE/AVX의 long double128비트.128입니다.

387년도의 순수 테스트를 위해

gcc -W -Wall -m32 -mfpmath=387 -mno-sse ... test.c -o test
clang -W -Wall -m32 -mfpmath=387 -mno-sse ... test.c -o test

한 플래그 ... , 「」를 한다.-fomit-frame-pointer,-O0,-O1,-O2,-O3 , , , , 입니다.-Os.

결과가 됩니다, C99는 제외됩니다.long double 기및및및)ld == 1.0★★★★★★★★★★★★★★★★★★★★★」다른 점이 있으면 알려주시면 감사하겠습니다.사용자에게 이러한 컴파일러(컴파일러 버전)에 대해 경고해야 할 수도 있습니다.Microsoft ® C99 microsoft microsoft microsoft microsoft microsoft 、 microsoft microsoft microsoft microsoft microsoft microsoft microsoft microsoft microsoft 、 microsoft microsoft microsoft microsoft microsoft microsoft microsoft microsoft 。


Pascal Cuoq는 아래의 댓글 체인에서 흥미로운 문제를 제기하지만, 저는 바로 알아차리지 못했습니다.

할 때 "GCC" Clang"으로 표시됩니다.-mfpmath=387모든 식을 80비트 정밀도를 사용하여 평가하도록 지정합니다. 되면 예를 '먹다' 이런 됩니다.

7491907632491941888 = 0x1.9fe2693112e14p+62 = 110011111111000100110100100110001000100101110000101000000000000
5698883734965350400 = 0x1.3c5a02407b71cp+62 = 100111100010110100000001001000000011110110111000111000000000000

7491907632491941888 * 5698883734965350400 = 42695510550671093541385598890357555200 = 100000000111101101101100110001101000010100100001011110111111111111110011000111000001011101010101100011000000000000000000000000

바이너리 결과의 중간에 있는 문자열이 53비트 및 64비트 맨티사(각각 64비트 및 80비트 부동소수점 번호)의 차이에 불과하기 때문에 잘못된 결과를 얻을 수 있습니다.그래서 예상된 결과는

42695510550671088819251326462451515392 = 0x1.00f6d98d0a42fp+125 = 100000000111101101101100110001101000010100100001011110000000000000000000000000000000000000000000000000000000000000000000000000

로로 으로 수 -std=c99 -m32 -mno-sse -mfpmath=387하고 있다

42695510550671098263984292201741942784 = 0x1.00f6d98d0a43p+125 = 100000000111101101101100110001101000010100100001100000000000000000000000000000000000000000000000000000000000000000000000000000

이론적으로는 옵션을 사용하여 올바른 C99 반올림 규칙을 적용하도록 gcc와 clang에 지시할 수 있어야 합니다.

-std=c99 -m32 -mno-sse -mfpmath=387 -ffloat-store -fexcess-precision=standard

그러나 이는 컴파일러가 최적화하는 식에만 영향을 미치며 387 처리는 전혀 수정되지 않는 것으로 보입니다.★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ clang -O1 -std=c99 -m32 -mno-sse -mfpmath=387 -ffloat-store -fexcess-precision=standard test.c -o test && ./testtest.cPascal Cuoq의 예제 프로그램이므로 IEEE-754 규칙에 따라 올바른 결과를 얻을 수 있습니다.단, 컴파일러가 387을 전혀 사용하지 않고 식을 최적화하기 때문입니다.

간단히 말하면 컴퓨팅이 아니라

(double)d1 * (double)d2

gcc와 clang 둘 다 387년형에게 계산하라고 말한다.

(double)((long double)d1 * (long double)d2)

이것은 확실히 gcc-4.6.3과 clang-llvm-3.0 모두에 영향을 주는 컴파일러 버그이며 쉽게 재현할 수 있는 버그라고 생각합니다.(Pascal Cuoq는 다음과 같이 지적합니다)FLT_EVAL_METHOD=2, 에 대한 은 항상 , 2배 정밀도 인수의 를 다시 쓰는 것인 이유를 알 수 .libm387년 - C99에서 그렇게 하고 IEEE-754 규칙을 고려하는 것은 하드웨어에 의해 달성될 수 있습니다.결국, 387 제어 워드를 식의 정밀도에 맞게 수정함으로써 컴파일러에 의해 올바른 연산이 쉽게 달성된다.그리고 이 동작을 강제하는 컴파일러 옵션을 지정하면 ---std=c99 -ffloat-store -fexcess-precision=standardFLT_EVAL_METHOD=2동작은 실제로 바람직하며 하위 호환성 문제도 없습니다.)적절한 컴파일러 플래그를 지정하면 컴파일 시 평가된 식은 올바르게 평가되며 실행 시 평가된 식만 잘못된 결과를 얻는다는 점에 유의해야 합니다.

회피책과 은 ""를 사용하는 입니다.fesetround(FE_TOWARDZERO):fenv.h으로 반올림합니다0으로 하다

경우에 따라서는 0을 향해 반올림하는 것이 예측 가능성과 병리적인 경우에 도움이 될 수 있습니다. 히, 음, 음, 음, 음, 음, in, 음, 음, 음, 음, like, like, like, like, like 등x = [0,1)0을 향한 반올림은 반올림을 통해 상한에 도달하지 않음을 의미합니다. 예를 들어, 분할 스플라인을 평가할 경우 중요합니다.

다른 반올림 모드에서는 387 하드웨어를 직접 제어해야 합니다.

할 수 .__FPU_SETCW()부터에서#include <fpu_control.h>, 아니면 open-code.또는오픈 코드를 사용합니다.예를 들어, 예를들면,precision.c:

#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

#define FP387_NEAREST   0x0000
#define FP387_ZERO      0x0C00
#define FP387_UP        0x0800
#define FP387_DOWN      0x0400

#define FP387_SINGLE    0x0000
#define FP387_DOUBLE    0x0200
#define FP387_EXTENDED  0x0300

static inline void fp387(const unsigned short control)
{
    unsigned short cw = (control & 0x0F00) | 0x007f;
    __asm__ volatile ("fldcw %0" : : "m" (*&cw));
}

const char *bits(const double value)
{
    const unsigned char *const data = (const unsigned char *)&value;
    static char buffer[CHAR_BIT * sizeof value + 1];
    char       *p = buffer;
    size_t      i = CHAR_BIT * sizeof value;
    while (i-->0)
        *(p++) = '0' + !!(data[i / CHAR_BIT] & (1U << (i % CHAR_BIT)));
    *p = '\0';
    return (const char *)buffer;
}


int main(int argc, char *argv[])
{
    double  d1, d2;
    char    dummy;

    if (argc != 3) {
        fprintf(stderr, "\nUsage: %s 7491907632491941888 5698883734965350400\n\n", argv[0]);
        return EXIT_FAILURE;
    }

    if (sscanf(argv[1], " %lf %c", &d1, &dummy) != 1) {
        fprintf(stderr, "%s: Not a number.\n", argv[1]);
        return EXIT_FAILURE;
    }
    if (sscanf(argv[2], " %lf %c", &d2, &dummy) != 1) {
        fprintf(stderr, "%s: Not a number.\n", argv[2]);
        return EXIT_FAILURE;
    }

    printf("%s:\td1 = %.0f\n\t    %s in binary\n", argv[1], d1, bits(d1));
    printf("%s:\td2 = %.0f\n\t    %s in binary\n", argv[2], d2, bits(d2));

    printf("\nDefaults:\n");
    printf("Product = %.0f\n\t  %s in binary\n", d1 * d2, bits(d1 * d2));

    printf("\nExtended precision, rounding to nearest integer:\n");
    fp387(FP387_EXTENDED | FP387_NEAREST);
    printf("Product = %.0f\n\t  %s in binary\n", d1 * d2, bits(d1 * d2));

    printf("\nDouble precision, rounding to nearest integer:\n");
    fp387(FP387_DOUBLE | FP387_NEAREST);
    printf("Product = %.0f\n\t  %s in binary\n", d1 * d2, bits(d1 * d2));

    printf("\nExtended precision, rounding to zero:\n");
    fp387(FP387_EXTENDED | FP387_ZERO);
    printf("Product = %.0f\n\t  %s in binary\n", d1 * d2, bits(d1 * d2));

    printf("\nDouble precision, rounding to zero:\n");
    fp387(FP387_DOUBLE | FP387_ZERO);
    printf("Product = %.0f\n\t  %s in binary\n", d1 * d2, bits(d1 * d2));

    return 0;
}

clang-llvm-3.0을 사용하여 컴파일하고 실행하면 올바른 결과를 얻을 수 있습니다.

clang -std=c99 -m32 -mno-sse -mfpmath=387 -O3 -W -Wall precision.c -o precision
./precision 7491907632491941888 5698883734965350400

7491907632491941888:    d1 = 7491907632491941888
        0100001111011001111111100010011010010011000100010010111000010100 in binary
5698883734965350400:    d2 = 5698883734965350400
        0100001111010011110001011010000000100100000001111011011100011100 in binary

Defaults:
Product = 42695510550671098263984292201741942784
          0100011111000000000011110110110110011000110100001010010000110000 in binary

Extended precision, rounding to nearest integer:
Product = 42695510550671098263984292201741942784
          0100011111000000000011110110110110011000110100001010010000110000 in binary

Double precision, rounding to nearest integer:
Product = 42695510550671088819251326462451515392
          0100011111000000000011110110110110011000110100001010010000101111 in binary

Extended precision, rounding to zero:
Product = 42695510550671088819251326462451515392
          0100011111000000000011110110110110011000110100001010010000101111 in binary

Double precision, rounding to zero:
Product = 42695510550671088819251326462451515392
          0100011111000000000011110110110110011000110100001010010000101111 in binary

다시 말해, 당신은 그 컴파일러 이슈들을 즉,수 있습니다 해결할컴파일러의 문제를을 사용하여 일할 수 있다.fp387()그 정밀도와 반올림 모드를 설정하려면.정밀도 싱크 반올림모드를 설정합니다.

리디노미네이션의 단점은 어떤 사람들은 수학 도서관(단점은 일부있다는 것입니다 라이브러리가 수학 있다.libm.a,,libm.so)이라는 가정이 중간 결과 항상80-bit 정밀로 산정된다.) 쓸 수 있다.는 정밀도로 계산된다는 가정 하에 작성될 수 있습니다 항상 80비트 결과가 중간.적어도 GNUC라이브러리 적어도 GNUC라이브러리.fpu_control.hx86_64의 경우 "libm은 확장된 정밀도를 필요로 합니다."라는 코멘트가 있습니다.다행히 GNU C 라이브러리에서 387년도의 실장을 가져와 헤더 파일에 실장하거나 이미 알고 있는 작업을 작성할 수 있습니다.libm, 만약 당신이 필요로 하는필요한 경우math.hFunctionality. 사실에서, 저는 제가 거기서 도와 줄 수 있을 것 같다.기능성. 사실 제가 도와드릴 수 있을 것 같습니다.

참고로 아래는 실험 결과입니다.다음 프로그램은 Clang으로 컴파일할 때 다양한 동작을 보여줍니다.

#include <stdio.h>

int r1, r2, r3, r4, r5, r6, r7;

double ten = 10.0;

int main(int c, char **v)
{
  r1 = 0.1 == (1.0 / ten);
  r2 = 0.1 == (1.0 / 10.0);
  r3 = 0.1 == (double) (1.0 / ten);
  r4 = 0.1 == (double) (1.0 / 10.0);
  ten = 10.0;
  r5 = 0.1 == (1.0 / ten);
  r6 = 0.1 == (double) (1.0 / ten);
  r7 = ((double) 0.1) == (1.0 / 10.0);
  printf("r1=%d r2=%d r3=%d r4=%d r5=%d r6=%d r7=%d\n", r1, r2, r3, r4, r5, r6, r7);
}

결과는 최적화 수준에 따라 달라집니다.

$ clang -v
Apple LLVM version 4.2 (clang-425.0.24) (based on LLVM 3.2svn)
$ clang -mno-sse2 -std=c99  t.c && ./a.out
r1=0 r2=1 r3=0 r4=1 r5=1 r6=0 r7=1
$ clang -mno-sse2 -std=c99 -O2  t.c && ./a.out
r1=0 r2=1 r3=0 r4=1 r5=1 r6=1 r7=1

출연진은 출연자(double)하는 것r5 ★★★★★★★★★★★★★★★★★」r6-O2-O0 「」에 는, 「」를 참조해 주세요.r3 ★★★★★★★★★★★★★★★★★」r4 ★★★r1r5 "모든 최적화 수준"은 "모든 최적화 수준"입니다.r6 뿐이다r3-O2.

언급URL : https://stackoverflow.com/questions/17663780/is-there-a-document-describing-how-clang-handles-excess-floating-point-precision

반응형