2014-11-24 2 views
22

다음 C 프로그램의 출력에 대해서는 의문의 여지가 있습니다. Visual C++ 6.0과 MinGW32 (gcc 3.4.2)를 사용하여 컴파일하려고했습니다.C에서 float하기 위해 int를 캐스팅 할 때 이상한 동작이 발생했습니다

#include <stdio.h> 

int main() { 
    int x = 2147483647; 
    printf("%f\n", (float)2147483647); 
    printf("%f\n", (float)x); 
    return 0; 
} 

출력은 다음과 같습니다

2147483648.000000 
2147483647.000000 

내 질문은 왜 두 라인은 다른가요? 정수 값 2147483647을 IEEE 754 부동 소수점 형식으로 변환하면 2147483648.0으로 근사됩니다. 그래서 두 줄 모두 2147483648.000000과 같을 것으로 예상했습니다.

편집 : 번호 2147483647가의 손실없이 IEEE 754 단 정밀도 부동 소수점 형식으로 정확하게 표시 할 수 없기 때문에 값은 "2147483647.000000는"단일 정밀도 부동 소수점 값이 될 수 없습니다 정도.

+6

컴파일러와 비슷하게 보입니다. 이데 폰은 동일한 수를 제공합니다. MinGW GCC 4.5.2는 자신이하는 것과 동일한 결과를 제공합니다. –

+4

나는 이것이 컴파일러에 의존한다고 생각한다. gcc 4.8.2의 경우 두 경우 모두 2147483648.000000이 표시됩니다. – rocker

+6

최적화 버그. 두 컴파일러, mingw/gcc-3.4.2 및 vs6에서 동일한 출력입니까? BTW : 둘 다 ** 오래된 **입니다. – Deduplicator

답변

11

이 코드는 가변 인수 함수에 전달되는 값은 float 같이 double 변환이 발생 .. doublefloat하고 일부 정수형 변환하고자한다.

FLT_EVAL_METHOD의 설정을 확인하십시오. 값이 1 또는 2로 추정됩니다 (최소 1 개의 컴파일러가있는 OP 보고서 2). 따라서 컴파일러는 float "... 범위와 정밀도에 대한 작업 및 상수"를 float보다 크게 계산할 수 있습니다.

컴파일러는 (float)x에 직접 최적화되어 int에서 double까지 계산됩니다. 이것은 런타임 동안의 성능 향상입니다. 성능이 여기에 문제가되지 않습니다으로

(float)2147483647 컴파일 시간 캐스트 및 double 정확성 floatint에 최적화 된 컴파일러입니다.


[Edit2가] C11 사양은 "할당 및 캐스트를 제외하고 ..."의 추가와 C99 사양보다 더 구체적이다 재미있다. 이는 C99 컴파일러가 float을 거치지 않고 int에서 double으로 직접 변환을 허용하고 C11이 캐스트를 건너 뛰는 것을 분명히 허용하지 않도록 수정했다는 것을 의미합니다.

공식적으로이 동작을 제외하면 현대 작성자가 OP를 할 수있는 것처럼 오래된 수식을 사용해서는 안됩니다. 따라서 C11 표준에 의한 버그 일 수 있습니다. 다른 C99 또는 C89 사양이 다른 것으로 밝혀지지 않는 한, 이는 허용 가능한 컴파일러 동작 인 것으로 보입니다.


는 [편집]이 심지어 비 - 제로 FLT_EVAL_METHOD와 @Keith 톰슨 @tmyklebu, @ 매트 McNabb 컴파일러에 의해 함께 코멘트를 복용 2147483648.0...을 생성 할 것으로 예상한다. 따라서 컴파일러 최적화 플래그가 명시 적으로 올바른 동작을 오버라이드하거나 컴파일러에 모서리 버그가 있습니다.


C99dr §5.2.4.2.2 8 플로팅 피연산자 연산 값과 일반적인 산술 변환 및 부동 상수 값은 그 대상 범위보다 정밀도 요구 될 수있는 형식으로 평가 유형별. 평가 형식의 사용 FLT_EVAL_METHOD 구현 정의 값을 특징으로한다 :

-1 확정 할;

0은 유형의 범위와 정밀도에 대한 모든 연산과 상수를 평가합니다.

1 범위 및 long double type`의 정밀도 long double 연산 상수를 평가 및 조작 범위 및 double 타입의 정밀도 및 유형 floatdouble의 정수를 평가;

2는 모든 연산과 상수를 long double 유형의 범위와 정밀도로 평가합니다.


C11dr는 (모든 여분의 범위와 정밀도를 제거) 할당 및 캐스트를 제외하고 §5.2.4.2.2 9, 부동 피연산자 운영자에 의해 산출 된 값 및 일반 산술 변환과의 주제 값 부동 소수점 형은 범위와 정밀도가 형식에 필요한 것보다 클 수있는 형식으로 평가됩니다. 평가 형식의 사용 구현 정의 값 FLT_EVAL_METHOD 특징

-1 (동일 C99 등)

0 (동일 C99 등)

1 (동일 C99 등)

2 (C99과 동일)

+0

저는 사람들이 그 단락을 어떻게 읽었는지 전혀 이해하지 못했습니다. 왜냐하면 컴파일러가 모든 여분의 범위와 정밀도를 제거하더라도 할당과 캐스트를 제거하는 것이 좋습니다. – tmyklebu

+0

@tmyklebu 'FLT_EVAL_METHOD == 1'이라도 두 방법 모두 '2147483648'을 생성해야한다는 것에 동의하기 쉽습니다. 내 주장은'FLT_EVAL_METHOD == 1 '때문에, 컴파일러는'(double) x'를'double (x)'로하고 2147483647을 렌더링했습니다. 이것이 합법적인지 여부는 언어 변호사보다 남겨 둡니다. – chux

+2

단락은 "배정과 캐스트 제외"로 시작하지만 논의중인 코드는 * 캐스트 *이므로이 단락의 나머지는 적용되지 않습니다 (특히 FLT_EVAL_METHOD의 관련성) –

2

첫 번째 printf에서 정수에서 float 로의 변환은 컴파일러에서 수행합니다. 두 번째 방법은 C 런타임 라이브러리에서 수행합니다. 정확성의 한계에서 동일한 대답을 만들어야하는 특별한 이유는 없습니다. 두 경우

+0

컴파일러는 두 번째 변환도 C 런타임에 두지 않아도됩니다. –

+0

글쎄요, 한 가지 특별한 이유는 최적화가 관찰 가능한 결과를 바꾸지 않기를 바랄 것이라는 것입니다. –

+0

@OliverCharlesworth : 그러나 행동이 지정되지 않은 경우에 그렇게 할지도 모릅니다. (C89에서입니까?) – Deduplicator

0

비주얼 C++ 6.0은 지난 세기를 발표하고, 나는 그것이 표준 C++보다 앞서 생각했다. VC++ 6.0이 깨진 행동을 보여주는 것은 전혀 놀랄 일이 아닙니다.

또한 gcc-3.4.2는 2004 년에 나온 것입니다. 실제로 32 비트 컴파일러를 사용하고 있습니다. x86 plays rather fast and loose with floating-point math의 gcc. gcc가 FLT_EVAL_METHOD을 0이 아닌 값으로 설정하면 이것은 C 표준에 의해 기술적으로 정당화 될 수 있습니다.

+1

"VC6은 오래되었고 나는 놀랍지 않습니다."여기에는 많은 흥미로운 정보 나 설명이 없습니다. –

+1

@JasonC : 나는 그렇지 않다고 추측한다. 그러나 그것이 나온 지 두 달 후에 발표 된 표준을 따르는 것은 아무런 주장도하지 않는다. 그 모든 광기 때문에 그것을 비난하는 것이 힘들다. – tmyklebu

+0

참고 : "Visual C++ 6.0이 지난 2000 년에 출시되었습니다"는 어떻습니까? 이유가없는 – chux

-1

여러분 중 일부는 최적화 버그라고 말했지만 다소 동의하지 않습니다. 합리적인 부동 소수점 정밀도 오류와 부동 소수점 작동 방식을 보여주는 좋은 예라고 생각합니다.

http://ideone.com/Ssw8GR

어쩌면 영업 이익은 컴퓨터에 내 프로그램을 붙여 넣은 컴파일러로 컴파일하고 어떻게되는지하려고 시도 할 수 있습니다. 또는 시도 :

http://ideone.com/OGypBC

(명시 적 부동 소수점 변환 포함).

우리가 오류를 계산하면 그 값은 4.656612875245797e-10이며 꽤 정확해야합니다.

도 선호도 printf과 관련 될 수 있습니다.

+0

투표가 다운 되셨나요? – HuStmpHrrr

+6

이것은 부동 소수점이 작동하는 방식이 아닙니다. 둥글게하거나 둥글게하기로 결정하지 않습니다. 그것은 따라야 할 잘 정의 된 의미를 가지고 있습니다. – tmyklebu

7

이것은 확실히 컴파일러 버그입니다. C11 표준에서 우리는 (C99이 유사) 다음과 같은 보장을 가지고

  • 유형 표현 가능한 값의 집합이 float하여 표현할 (묵시적)
  • 모든 값은 double (6.2.5/10도 표현할 수있다) int 값을 표현할 float 대한 값들의 세트에있을 때, 값 (6.3.1.5/1) floatint 주조
  • 변경되지 않는 floatdouble에 변환
  • , 그 값을 제공한다.
  • 주조 int int 값의 크기보다 작 FLT_MAXintfloat 대한 표현 가능한 값이 아닌 것이다 어느 다음 - 최고 또는 다음 - 최하위 float 값이 선택되도록하고, 어느 하나가 float에 선택된 것은 구현 정의입니다. (6.3.1.4/2)는

이러한 점 셋째 printf에 공급 float 값 기본 인자 프로모션에 의해 변형되지 않도록 보장한다.

2147483647하는 경우는, float(float)x 표현할 수 있으며 (float)21474836472147483647.000000를 제공해야합니다. 2147483647 만약

(float)x(float)2147483647 다음-가장 높은 또는 다음-가장 낮은 float을 부여해야 하나, float에서 표현할 수 없습니다. 둘 다 동일한 선택을해야하는 것은 아닙니다. 그러나 이것은 2147483647.000000의 인쇄물이 허용되지 않음을 의미합니다. 각각은 더 높은 값이거나 더 낮은 값이어야합니다.


1 음 - 그 다음 최하위 플로트 값을 다음 printf가 6 자리 정밀도로 표시 할 때 2147483646.9999999...이 때문에 본 무슨 수득 반올림 것을 이론적으로 가능하다. 그러나 이것은 IEEE754에서 사실이 아니므로이 가능성을 할인하기 위해 쉽게 실험 할 수 있습니다.

+0

다음으로 낮은 'float'은'2147483646.9999999 ...'가 아닌'2147483520.0'과 비슷합니까? 다음으로 낮은 double은'2147483646.9999998 ...'이 될 수 있습니다. – chux

+0

@chux IDK, IEEE754 –

+0

C99는 1999 년부터 읽을 수 있습니다. VC89에서이 버그를 호출하려는 경우 C89가 참조 할 수있는 유일한 합리적인 C 표준이 될 것입니다. (나는 VC++ 6 버그라고 확신하지는 않는다. 사람들의 부동 소수점 코드를 도용하는 것이 C와 C++ 구현 자에게 감명을 받기 전에 만들어진 오래된 컴파일러이다.) – tmyklebu

관련 문제