'float a = 3.0;' 정확한 진술?


86

다음 선언이있는 경우 :

float a = 3.0 ;

그게 오류인가요? 나는 책에서 읽은 3.0A는 double가치와 내가 가지고로 지정 float a = 3.0f. 그렇습니까?


2
컴파일러는 double 리터럴 3.0을 float로 변환합니다 . 최종 결과는와 구별 할 수 없습니다 float a = 3.0f.
David Heffernan

6
@EdHeal : 그렇긴하지만 C ++ 규칙에 관한이 질문과 특별히 관련이 없습니다.
Keith Thompson

20
글쎄, 적어도 당신은 ;애프터 가 필요합니다 .
Hot Licks 2014-08-11

3
10 개의 반대표를 작성하고 댓글에 많이 적지 않았습니다. 매우 실망 스럽습니다. 이것은 OPs의 첫 번째 질문이며 사람들이 이것이 10 회 다운 보트의 가치가 있다고 생각한다면 몇 가지 설명이 있어야합니다. 이것은 분명하지 않은 의미와 답변과 의견을 통해 배울 수있는 많은 흥미로운 사항을 가진 유효한 질문입니다.
Shafik Yaghmour

3
@HotLicks 그것은 기분이 나쁘거나 좋은 것이 아니라 불공평하게 보일지 모르지만 그것이 삶입니다. 결국 유니콘 포인트입니다. 다우 보트는 당연히 당신이 싫어하는 업 보트를 취소하는 것이 아닙니다. 마치 업 보트가 당신이 싫어하는 다운 보트를 취소하지 않는 것처럼 말입니다. 사람들이 질문이 개선 될 수 있다고 생각한다면, 확실히 처음 질문하는 사람은 약간의 피드백을 받아야합니다. 나는 반대 투표를 할 이유가 없지만 다른 사람들이 그렇게 말할 수는 있지만 왜 그렇게하는지 알고 싶습니다.
Shafik Yaghmour 2014-08-12

답변:


159

선언하는 것은 오류가 아닙니다 float a = 3.0. 이렇게하면 컴파일러가 double literal 3.0을 float로 변환합니다.


그러나, 당신은 해야합니다 특정 시나리오에서 부동 소수점 리터럴 표기법을 사용합니다.

  1. 성능상의 이유로 :

    특히 다음 사항을 고려하십시오.

    float foo(float x) { return x * 0.42; }
    

    여기서 컴파일러는 반환 된 각 값에 대해 변환 (런타임에 지불)을 내 보냅니다. 이를 방지하려면 다음을 선언해야합니다.

    float foo(float x) { return x * 0.42f; } // OK, no conversion required
    
  2. 결과를 비교할 때 버그를 방지하려면 :

    예를 들어 다음 비교가 실패합니다.

    float x = 4.2;
    if (x == 4.2)
       std::cout << "oops"; // Not executed!
    

    float 리터럴 표기법으로 수정할 수 있습니다.

    if (x == 4.2f)
       std::cout << "ok !"; // Executed!
    

    (참고 : 물론 이것은 일반적으로 평등을 위해 float 또는 double 숫자를 비교하는 방법이 아닙니다. )

  3. 올바른 오버로드 된 함수를 호출하려면 (같은 이유로) :

    예:

    void foo(float f) { std::cout << "\nfloat"; }
    
    void foo(double d) { std::cout << "\ndouble"; }
    
    int main()
    {       
        foo(42.0);   // calls double overload
        foo(42.0f);  // calls float overload
        return 0;
    }
    
  4. Cyber 에서 언급했듯이 유형 추론 컨텍스트에서 컴파일러가 다음을 추론하는 데 도움이 필요합니다 float.

    다음의 경우 auto:

    auto d = 3;      // int
    auto e = 3.0;    // double
    auto f = 3.0f;   // float
    

    마찬가지로 템플릿 유형 추론의 경우 :

    void foo(float f) { std::cout << "\nfloat"; }
    
    void foo(double d) { std::cout << "\ndouble"; }
    
    template<typename T>
    void bar(T t)
    {
          foo(t);
    }
    
    int main()
    {   
        bar(42.0);   // Deduce double
        bar(42.0f);  // Deduce float
    
        return 0;
    }
    

라이브 데모


2
포인트 1 42은 정수로, 자동으로 승격되며 float(그리고 괜찮은 컴파일러에서 컴파일 타임에 발생합니다) 성능 저하가 없습니다. 아마도 당신은 같은 것을 의미했을 것 42.0입니다.
Matteo Italia

@MatteoItalia, 네, 42.0 ofc를 의미했습니다 (편집 됨, 감사합니다)
quantdev

2
@ChristianHackl 다음으로 변환 하면 컴파일러와 시스템에 따라 플래그 를 설정하는 부작용 4.24.2f있을 수 있으며, FE_INEXACT일부 (확실히 소수) 프로그램은 어떤 부동 소수점 연산이 정확한지, 그렇지 않은지 신경 쓰고 해당 플래그를 테스트합니다. . 이것은 간단하고 명백한 컴파일 타임 변환이 프로그램의 동작을 변경한다는 것을 의미합니다.

6
float foo(float x) { return x*42.0; }단 정밀도 곱셈으로 컴파일 할 수 있으며 마지막으로 시도했을 때 Clang에 의해 컴파일되었습니다. 그러나 float foo(float x) { return x*0.1; }단일 정밀도 곱셈으로 컴파일 할 수 없습니다. 이 패치 이전에는 약간 지나치게 낙관적이었을 수 있지만 패치 후에는 결과가 항상 같을 때 conversion-double_precision_op-conversion을 single_precision_op으로 결합해야합니다. article.gmane.org/gmane.comp.compilers.llvm.cvs/167800/match=
Pascal Cuoq 2014 년

1
의 1/10 인 값을 계산하려는 경우 someFloat표현식 someFloat * 0.1이보다 정확한 결과를 얻을 수 someFloat * 0.1f있지만 대부분의 경우 부동 소수점 나누기보다 저렴합니다. 예를 들어, (float) (167772208.0f * 0.1)은 16777222가 아닌 16777220으로 올바르게 반올림됩니다. 일부 컴파일러는 double부동 소수점 나누기 대신 곱하기를 대체 할 수 있지만 그렇지 않은 컴파일러는 곱하기를 대신 할 수 있습니다 (모든 값은 아니지만 많은 경우에 안전함). ) 곱하기는 유용한 최적화가 될 수 있지만 double역수로 수행되는 경우에만 가능합니다 .
supercat

22

컴파일러는 변수를 float로 선언했기 때문에 다음 리터럴을 float로 변환합니다.

float a = 3;     // converted to float
float b = 3.0;   // converted to float
float c = 3.0f;  // float

auto예를 들어 (또는 다른 유형의 공제 방법) 을 사용한 경우 중요합니다 .

auto d = 3;      // int
auto e = 3.0;    // double
auto f = 3.0f;   // float

5
유형은 템플릿을 사용할 때도 추론되므로 auto유일한 경우는 아닙니다.
Shafik Yaghmour

14

접미사가없는 부동 소수점 리터럴은 double 유형 이며 C ++ 표준 섹션 2.14.4 부동 리터럴 초안에서 다룹니다 .

[...] 부동 리터럴의 유형은 접미사로 명시 적으로 지정하지 않는 한 double입니다. [...]

그래서 할당 오류가 이중 문자를 A와 플로트 ?3.0

float a = 3.0

아니요, 변환되지 않습니다. 이는 4.8 부동 소수점 변환 섹션에서 다룹니다 .

부동 소수점 유형의 prvalue는 다른 부동 소수점 유형의 prvalue로 변환 될 수 있습니다. 소스 값이 대상 유형에 정확하게 표시 될 수있는 경우 변환 결과는 정확한 표시입니다. 소스 값이 인접한 두 대상 값 사이에있는 경우 변환 결과는 이러한 값 중 하나에 대한 구현 정의 선택입니다. 그렇지 않으면 동작이 정의되지 않습니다.

이것의 의미에 대한 자세한 내용은 GotW # 67 에서 읽을 수 있습니다 .

즉, 정밀도 (즉, 데이터)를 잃더라도 double 상수가 암시 적으로 (즉, 조용히) float 상수로 변환 될 수 있습니다. 이것은 C 호환성 및 사용성 이유 때문에 허용되었지만 부동 소수점 작업을 수행 할 때 명심할 가치가 있습니다.

품질 컴파일러는 정의되지 않은 동작, 즉 float가 나타낼 수있는 최소값보다 작거나 최대 값보다 큰 float에 이중 수량을 넣으려고하면 경고합니다. 정말 좋은 컴파일러는 정의 될 수 있지만 정보를 잃을 수있는 작업을하려고하면 선택적 경고를 제공합니다. 즉, float로 표현할 수있는 최소값과 최대 값 사이에있는 float에 이중 수량을 넣지 만 그렇지 않은 경우 float로 정확하게 표현되어야합니다.

따라서 알아야 할 일반적인 경우에 대한주의 사항이 있습니다.

실용적인 관점에서이 경우 결과는 기술적으로는 변환이 있어도 동일 할 가능성이 높습니다 . godbolt 에서 다음 코드를 시도하여이를 확인할 수 있습니다 .

#include <iostream>

float func1()
{
  return 3.0; // a double literal
}


float func2()
{
  return 3.0f ; // a float literal
}

int main()
{  
  std::cout << func1() << ":" << func2() << std::endl ;
  return 0;
}

우리는 대한 결과 볼 func1과가 func2모두 사용하여 동일 clanggcc:

func1():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret
func2():
    movss   xmm0, DWORD PTR .LC0[rip]
    ret

파스칼이 댓글에서 지적 당신은 항상 의지 할 수 없습니다. 0.10.1f각각을 사용 하면 변환이 이제 명시 적으로 수행되어야하므로 생성 된 어셈블리가 달라집니다. 다음 코드 :

float func1(float x )
{
  return x*0.1; // a double literal
}

float func2(float x)
{
  return x*0.1f ; // a float literal
}

결과는 다음과 같습니다.

func1(float):  
    cvtss2sd    %xmm0, %xmm0    # x, D.31147    
    mulsd   .LC0(%rip), %xmm0   #, D.31147
    cvtsd2ss    %xmm0, %xmm0    # D.31147, D.31148
    ret
func2(float):
    mulss   .LC2(%rip), %xmm0   #, D.31155
    ret

변환이 성능에 영향을 미칠지 여부를 결정할 수 있는지 여부에 관계없이 올바른 유형을 사용하면 의도를 더 잘 문서화 할 수 있습니다. 예를 들어 명시 적 변환을 사용하면 static_cast버그 또는 잠재적 버그를 의미 할 수있는 우발적 인 변환이 아니라 의도 된 변환임을 명확히하는 데 도움이됩니다.

노트

supercat이 지적했듯이 eg 0.1와 곱셈 0.1f은 동등하지 않습니다. 나는 그것이 훌륭했고 요약이 아마 그것을 정의하지 않을 것이기 때문에 그 주석을 인용 할 것입니다.

예를 들어 f가 100000224 (정확히 부동 소수점으로 표현 가능함)와 같으면 1/10을 곱하면 10000022로 내림하는 결과가 생성되지만 대신 0.1f를 곱하면 10000023으로 잘못 반올림되는 결과가 생성됩니다. 만약 의도가 10으로 나누는 것이라면, 곱셈 상수 0.1은 10f로 나누는 것보다 빠르고 0.1f로 나누는 것보다 더 정확할 것입니다.

내 원래 요점은 다른 질문에서 주어진 잘못된 예를 보여주는 것이었지만 이것은 장난감 예에 미묘한 문제가 존재할 수 있음을 잘 보여줍니다.


1
표현 f = f * 0.1;f = f * 0.1f; 다른 일을 한다는 점에 주목할 가치가 있습니다 . 예를 들어, f100000224 (으로 정확하게 표현할 수 있음)와 같으면 float10 분의 1을 곱하면 10000022로 내림되는 결과가 생성되지만 0.1f를 곱하면 10000023으로 잘못 올림되는 결과가 생성됩니다. 의도는 10으로 나누는 double것입니다. 상수 0.1로 곱하면으로 나누는 것보다 빠르며 10f곱하는 것보다 더 정확합니다 0.1f.
supercat 2014-08-11

@supercat 좋은 예를 들어 주셔서 감사합니다. 직접 인용했습니다. 적합하다고 생각되면 자유롭게 편집하십시오.
Shafik Yaghmour 2014-08-12

4

컴파일러가 거부한다는 의미에서 오류가 아니라 원하는 것이 아닐 수도 있다는 의미에서 오류입니다.

책에서 올바르게 언급했듯이은 3.0유형 값입니다 double. 이 암시 적 변환은 출신 doublefloat, 그래서 float a = 3.0;변수의 올바른 정의입니다.

그러나 적어도 개념적으로 이것은 불필요한 변환을 수행합니다. 컴파일러에 따라 변환은 컴파일 타임에 수행되거나 런타임을 위해 저장 될 수 있습니다. 런타임을 위해 저장하는 유효한 이유는 부동 소수점 변환이 어렵고 값을 정확하게 표현할 수없는 경우 예기치 않은 부작용이 발생할 수 있으며 값을 정확하게 표현할 수 있는지 여부를 확인하는 것이 항상 쉬운 것은 아니기 때문입니다.

3.0f 기술적으로 컴파일러는 여전히 런타임에 상수를 계산할 수 있지만 (항상 그렇습니다) 여기에서는 컴파일러가 그렇게 할 수있는 이유가 전혀 없습니다.


사실, 크로스 컴파일러의 경우, 변환이 잘못된 플랫폼에서 일어나기 때문에 컴파일 타임에 수행되는 것은 매우 정확하지 않을 것입니다.
Marquis of Lorne

2

오류는 아니지만 그 자체로는 약간 엉성합니다. 플로트가 필요하다는 것을 알고 있으므로 플로트로 초기화하십시오.
다른 프로그래머가 와서 선언의 어느 부분이 올바른지, 유형 또는 이니셜 라이저인지 확신하지 못할 수 있습니다. 둘 다 정확하지 않은 이유는 무엇입니까?
부동 답변 = 42.0f;


0

변수를 정의하면 제공된 이니셜 라이저로 초기화됩니다. 이니셜 라이저의 값을 초기화되는 변수의 유형으로 변환해야 할 수 있습니다. 이것이 바로 float a = 3.0;이니셜 라이저의 값이로 float변환되고 변환의 결과가의 초기 값이 된다고 말할 때 일어나는 일 입니다 a.

그것은 일반적으로 괜찮지 만, 3.0f당신이하고있는 일을 알고 있다는 것을 보여주기 위해, 특히 당신이 auto a = 3.0f.


0

다음을 시도하는 경우 :

std::cout << sizeof(3.2f) <<":" << sizeof(3.2) << std::endl;

다음과 같이 출력됩니다.

4:8

즉, 3.2f의 크기는 32 비트 시스템에서 4 바이트로 간주되며 3.2는 32 비트 시스템에서 8 바이트를 사용하는 double 값으로 해석됩니다. 이것은 당신이 찾고있는 답을 제공 할 것입니다.


그것은 보여줍니다 doublefloat다른, 그것은 당신이를 초기화 할 수 있는지 여부를 응답하지 않는 float이중 문자에서
조나단 Wakely

물론 데이터 잘림에 따라 double 값에서 float를 초기화 할 수 있습니다 (해당하는 경우)
Dr. Debasish Jana

4
예, 알아요.하지만 그게 OP의 질문 이었기 때문에 답변을 제공한다고 주장했지만 실제로 답변하지 못했습니다!
Jonathan Wakely

0

컴파일러는 리터럴에서 가장 적합한 유형을 추론하거나 최소한 가장 적합하다고 생각하는 유형을 추론합니다. 이는 정밀도보다 효율성을 잃는 것입니다. 즉, float 대신 double을 사용합니다. 확실하지 않은 경우 brace-intializer를 사용하여 명시 적으로 만듭니다.

auto d = double{3}; // make a double
auto f = float{3}; // make a float
auto i = int{3}; // make a int

유형 변환 규칙이 적용되는 다른 변수에서 초기화하면 이야기가 더 흥미로워집니다. 이중 형식을 리터럴로 구성하는 것이 합법적이지만 가능한 축소없이 int에서 구성 할 수 없습니다.

auto xxx = double{i} // warning ! narrowing conversion of 'i' from 'int' to 'double' 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.