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 double
128비트.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 && ./test
test.c
Pascal 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배 정밀도 인수의 를 다시 쓰는 것인 이유를 알 수 .libm
387년 - C99에서 그렇게 하고 IEEE-754 규칙을 고려하는 것은 하드웨어에 의해 달성될 수 있습니다.결국, 387 제어 워드를 식의 정밀도에 맞게 수정함으로써 컴파일러에 의해 올바른 연산이 쉽게 달성된다.그리고 이 동작을 강제하는 컴파일러 옵션을 지정하면 ---std=c99 -ffloat-store -fexcess-precision=standard
FLT_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.h
x86_64의 경우 "libm은 확장된 정밀도를 필요로 합니다."라는 코멘트가 있습니다.다행히 GNU C 라이브러리에서 387년도의 실장을 가져와 헤더 파일에 실장하거나 이미 알고 있는 작업을 작성할 수 있습니다.libm
, 만약 당신이 필요로 하는필요한 경우math.h
Functionality. 사실에서, 저는 제가 거기서 도와 줄 수 있을 것 같다.기능성. 사실 제가 도와드릴 수 있을 것 같습니다.
참고로 아래는 실험 결과입니다.다음 프로그램은 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
★★★r1
r5
"모든 최적화 수준"은 "모든 최적화 수준"입니다.r6
뿐이다r3
-O2
.
언급URL : https://stackoverflow.com/questions/17663780/is-there-a-document-describing-how-clang-handles-excess-floating-point-precision
'programing' 카테고리의 다른 글
Vue-cli를 사용하여 CSS를 "dist/vue-COMPONT_NAME.js" 파일에 빌드할 수 있습니까? (0) | 2022.08.07 |
---|---|
priority 사용방법큐? (0) | 2022.08.07 |
vuex에서 nuxt-i18n 패키지의 localRoute를 사용하는 방법 (0) | 2022.08.03 |
Visual Studio C/C++ 콘솔 응용 프로그램에서 콘솔 창 닫힘 방지 (0) | 2022.08.03 |
잭슨 JSON과 휴지 상태 JPA의 무한 재귀 문제 (0) | 2022.08.03 |