IT story

C ++에서 float의 round ()

hot-time 2020. 4. 17. 08:25
반응형

C ++에서 float의 round ()


간단한 부동 소수점 반올림 함수가 필요합니다.

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1

내가 찾을 수 ceil()floor()math.h에 -하지만 round().

표준 C ++ 라이브러리에 다른 이름으로 존재합니까, 아니면 없습니까?


C ++ 98 표준 라이브러리에는 round ()가 없습니다. 그래도 직접 쓸 수 있습니다. 다음은 반감기 구현입니다 .

double round(double d)
{
  return floor(d + 0.5);
}

C ++ 98 표준 라이브러리에 라운드 함수가없는 가능한 이유는 실제로 다른 방식으로 구현 될 수 있기 때문입니다. 위의 한 가지 일반적인 방법이지만 round-to-even 과 같은 다른 방법이 있습니다. 편향이 적고 많은 라운딩을 할 경우 일반적으로 더 좋습니다. 그래도 구현하기가 조금 더 복잡합니다.


Boost는 간단한 반올림 함수를 제공합니다.

#include <boost/math/special_functions/round.hpp>

double a = boost::math::round(1.5); // Yields 2.0
int b = boost::math::iround(1.5); // Yields 2 as an integer

자세한 정보는 Boost 문서를 참조하십시오 .

편집 : C ++ 11가 있기 때문에 std::round, std::lround하고std::llround .


는 C ++ 03 표준에 대한 C90 표준에 의존하는 것을 표준 통화량 표준 C 라이브러리 초안 C ++ 03 표준에 덮여 ( C ++ 03 N1804에 가장 가까운 공개 초안 표준 섹션) 1.2 참조 규격 :

ISO / IEC 9899 : 1990의 7 절과 ISO / IEC 9899 / Amd.1 : 1995의 7 절에 설명 된 라이브러리를 이하 표준 C 라이브러리라고합니다. 1)

우리가에 가면 라운드, lround에 대한 C 문서, cppreference에 llround은 우리가 볼 수있는 및 관련 기능의 일부 C99 때문에 03 또는 이전 ++ C에서 사용할 수 없습니다.

C ++ 11에서는 C ++ 11이 C 표준 라이브러리 에 대한 C99 표준 초안을 사용 하므로 std :: round 및 정수 리턴 유형 std :: lround, std :: llround를 제공하므로 변경됩니다 .

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::round( 0.4 ) << " " << std::lround( 0.4 ) << " " << std::llround( 0.4 ) << std::endl ;
    std::cout << std::round( 0.5 ) << " " << std::lround( 0.5 ) << " " << std::llround( 0.5 ) << std::endl ;
    std::cout << std::round( 0.6 ) << " " << std::lround( 0.6 ) << " " << std::llround( 0.6 ) << std::endl ;
}

C99의 또 다른 옵션은 std :: trunc 입니다.

arg보다 크지 않은 가장 가까운 정수를 계산합니다.

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::trunc( 0.4 ) << std::endl ;
    std::cout << std::trunc( 0.9 ) << std::endl ;
    std::cout << std::trunc( 1.1 ) << std::endl ;

}

비 C ++ 11 응용 프로그램을 지원 해야하는 경우 가장 좋은 방법은 boost round, iround, lround, llround 또는 boost trunc를 사용하는 것 입니다.

나만의 라운드 버전을 구르는 것은 어렵습니다

당신은 자신의 롤링 아마 노력이 가치가 없어 보이는 것보다 세게 : 가까운 정수, 1 부에 부동 소수점을 반올림 , 가장 가까운 정수, 2 부에 부동 소수점 반올림가장 가까운 정수로 반올림 플로트를, 3 부 설명 :

예를 들어 구현을 사용 std::floor하고 추가 하는 공통 롤 0.5이 모든 입력에 대해 작동하지는 않습니다.

double myround(double d)
{
  return std::floor(d + 0.5);
}

이것이 실패하는 입력은입니다 0.49999999999999994( 실제 참조 ).

다른 일반적인 구현에는 부동 소수점 유형을 정수 유형으로 캐스트하는 것이 포함되는데, 이는 정수 부분을 대상 유형으로 표시 할 수없는 경우 정의되지 않은 동작을 호출 할 수 있습니다. C ++ 표준 섹션 인 4.9 부동 적분 변환 초안 ( 강조 표시 )에서이를 볼 수 있습니다.

부동 소수점 유형의 prvalue는 정수 유형의 prvalue로 변환 될 수 있습니다. 변환이 잘립니다. 즉, 분수 부분은 폐기됩니다. 잘린 값을 대상 유형으로 표시 할 수없는 경우 동작이 정의되지 않습니다. [...]

예를 들면 다음과 같습니다.

float myround(float f)
{
  return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}

주어 std::numeric_limits<unsigned int>::max()4294967295다음 호출은 :

myround( 4294967296.5f ) 

오버플로가 발생합니다 ( 살아보기 ).

C에서 round ()를 구현 하는 간결한 방법에 대한이 답변을 보면 이것이 실제로 얼마나 어려운지 알 수 있습니까? 단정도 플로트 라운드의 newlibs 버전 을 참조 합니다. 단순 해 보이는 것으로 매우 긴 기능입니다. 부동 소수점 구현에 대한 친밀한 지식이없는 사람이라면 누구나이 함수를 올바르게 구현할 수 없을 것 같습니다.

float roundf(x)
{
  int signbit;
  __uint32_t w;
  /* Most significant word, least significant word. */
  int exponent_less_127;

  GET_FLOAT_WORD(w, x);

  /* Extract sign bit. */
  signbit = w & 0x80000000;

  /* Extract exponent field. */
  exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;

  if (exponent_less_127 < 23)
    {
      if (exponent_less_127 < 0)
        {
          w &= 0x80000000;
          if (exponent_less_127 == -1)
            /* Result is +1.0 or -1.0. */
            w |= ((__uint32_t)127 << 23);
        }
      else
        {
          unsigned int exponent_mask = 0x007fffff >> exponent_less_127;
          if ((w & exponent_mask) == 0)
            /* x has an integral value. */
            return x;

          w += 0x00400000 >> exponent_less_127;
          w &= ~exponent_mask;
        }
    }
  else
    {
      if (exponent_less_127 == 128)
        /* x is NaN or infinite. */
        return x + x;
      else
        return x;
    }
  SET_FLOAT_WORD(x, w);
  return x;
}

반면에 다른 솔루션을 사용할 수없는 경우 newlib 은 잘 테스트 된 구현이므로 옵션이 될 수 있습니다.


반올림에서 정수 결과를 원한다면 ceil 또는 floor를 통해 전달할 필요가 없습니다. 즉,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}

cmath에서 C ++ 11부터 사용할 수 있습니다 ( http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf 에 따라 )

#include <cmath>
#include <iostream>

int main(int argc, char** argv) {
  std::cout << "round(0.5):\t" << round(0.5) << std::endl;
  std::cout << "round(-0.5):\t" << round(-0.5) << std::endl;
  std::cout << "round(1.4):\t" << round(1.4) << std::endl;
  std::cout << "round(-1.4):\t" << round(-1.4) << std::endl;
  std::cout << "round(1.6):\t" << round(1.6) << std::endl;
  std::cout << "round(-1.6):\t" << round(-1.6) << std::endl;
  return 0;
}

산출:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2

일반적으로로 구현됩니다 floor(value + 0.5).

편집 : 내가 알고있는 적어도 3 개의 반올림 알고리즘이 있기 때문에 아마도 라운드라고 부르지 않을 것입니다 : 0으로 반올림, 가장 가까운 정수로 반올림 및 은행가 반올림. 가장 가까운 정수에 반올림을 요청합니다.


우리 가보고있는 두 가지 문제가 있습니다 :

  1. 반올림 변환
  2. 유형 변환.

반올림 변환은 반올림 ± float / double을 가장 가까운 floor / ceil float / double로 반올림하는 것을 의미합니다. 문제가 여기서 끝날 수 있습니다. 그러나 Int / Long을 반환해야하는 경우 형식 변환을 수행해야하므로 "오버플로"문제가 솔루션에 영향을 줄 수 있습니다. 따라서 함수에서 오류를 확인하십시오.

long round(double x) {
   assert(x >= LONG_MIN-0.5);
   assert(x <= LONG_MAX+0.5);
   if (x >= 0)
      return (long) (x+0.5);
   return (long) (x-0.5);
}

#define round(x) ((x) < LONG_MIN-0.5 || (x) > LONG_MAX+0.5 ?\
      error() : ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

부터 : http://www.cs.tut.fi/~jkorpela/round.html


특정 유형의 반올림도 Boost에서 구현됩니다.

#include <iostream>

#include <boost/numeric/conversion/converter.hpp>

template<typename T, typename S> T round2(const S& x) {
  typedef boost::numeric::conversion_traits<T, S> Traits;
  typedef boost::numeric::def_overflow_handler OverflowHandler;
  typedef boost::numeric::RoundEven<typename Traits::source_type> Rounder;
  typedef boost::numeric::converter<T, S, Traits, OverflowHandler, Rounder> Converter;
  return Converter::convert(x);
}

int main() {
  std::cout << round2<int, double>(0.1) << ' ' << round2<int, double>(-0.1) << ' ' << round2<int, double>(-0.9) << std::endl;
}

정수로 변환하는 경우에만 작동합니다.


다음을 사용하여 n 자리 정밀도로 반올림 할 수 있습니다.

double round( double x )
{
const double sd = 1000; //for accuracy to 3 decimal places
return int(x*sd + (x<0? -0.5 : 0.5))/sd;
}

궁극적으로 함수 double출력 을로 변환하려면 이 질문에 허용되는 솔루션은 다음과 같습니다.round()int

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}

이것은 균일하게 임의의 값으로 전달 될 때 내 컴퓨터 에서 약 8.88ns로 작동 합니다.

아래는 기능적으로 동일하지만 기계 에서 2.48ns로 클럭 하면 성능이 크게 향상됩니다.

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}

더 나은 성능의 이유 중 하나는 건너 뛴 분기입니다.


요즘 C99 / C ++ 11 수학 라이브러리를 포함하는 C ++ 11 컴파일러를 사용하는 것은 문제가되지 않습니다. 그러나 문제는 다음과 같습니다. 어떤 반올림 함수를 선택합니까?

C99 / C ++ 11 round()은 종종 실제로 원하는 반올림 함수가 아닙니다 . 중간 경우 ( +-xxx.5000) 의 타이 브레이크로 0에서 반올림하는 펑키 한 반올림 모드를 사용합니다 . 반올림 모드를 구체적으로 원하거나 round()보다 빠른 C ++ 구현을 대상으로하는 rint()경우이를 사용하십시오 (또는이 질문에 대한 다른 답변 중 하나를 사용하여 동작을 에뮬레이션하여 액면가에서 취하고 구체적으로 재현했습니다) 반올림 동작.)

round()반올림은 IEEE 브레이크 기본 반올림과 가장 가까운 모드로 의 반올림과 다릅니다 . 가장 가까운 경우에도 숫자의 평균 크기에서 통계적 편향을 피하지만 짝수로 편향됩니다.

현재 기본 반올림 모드를 사용하는 두 개의 수학 라이브러리 반올림 함수가 있습니다. std::nearbyint()std::rint()C99 / C ++ 11에 모두 추가되어 언제든지 사용할 수 있습니다 std::round(). 유일한 차이점은 nearbyintFE_INEXACT 를 발생 시키지 않는다는 것입니다.

선호 rint()성능상의 이유로 GCC를보다 쉽게 모두 인라인에게 그것을 그 소리, 그러나 GCC 결코 인라인 : nearbyint()(심지어와 -ffast-math)


x86-64 및 AArch64 용 gcc / clang

좀 넣어 매트 Godbolt의 컴파일러 탐색기에서 테스트 기능을 사용하면 (여러 컴파일러) 소스 +의 ASM 출력을 볼 수 있습니다. 컴파일러 출력을 읽는 방법에 대한 자세한 내용은 이 Q & A 및 Matt의 CppCon2017 강연을 참조하십시오 . “나의 컴파일러가 최근에 무엇을 했습니까? 컴파일러 뚜껑을 벗기다” ,

FP 코드에서는 일반적으로 작은 함수를 인라인하는 것이 큰 승리입니다. 특히 표준 호출 규칙에 호출 보존 레지스터가없는 Windows 이외의 경우 컴파일러는에서 XMM 레지스터의 FP 값을 유지할 수 없습니다 call. 따라서 실제로 asm을 모르더라도 라이브러리 함수에 대한 꼬리 호출인지 또는 하나 또는 두 개의 수학 명령에 인라인되었는지 쉽게 확인할 수 있습니다. 하나 또는 두 개의 명령어에 인라인하는 것은 함수 호출 (x86 또는 ARM의 특정 작업)보다 낫습니다.

x86에서 SSE4.1에 인라인하는 것은 roundsdSSE4.1 roundpd(또는 AVX vroundpd)로 자동 벡터화 할 수 있습니다 . (FP-> 정수 변환은 AVX512가 필요한 FP-> 64 비트 정수를 제외하고 팩형 SIMD 형식으로도 제공됩니다.)

  • std::nearbyint():

    • x86 clang :을 사용하여 단일 insn에 인라인합니다 -msse4.1.
    • 86 GCC : 만에 하나 INSN에 인라인 -msse4.1 -ffast-math, 오직 GCC 5.4에 이전 . 이 (어쩌면 그들이 정확하지 않은 예외를 억제 할 수 즉시 비트의 하나를 몰랐어요의 어떤 그 소리 사용하지만, 나이가 GCC가 같은 즉각적인에 관해서는 사용? 인라인 나중에 결코 gcc를하지 rint가 인라인 수행 할 때)
    • AArch64 gcc6.3 : 기본적으로 단일 인라인으로 인라인합니다.
  • std::rint:

    • x86 clang : 단일 인라인으로 인라인 -msse4.1
    • x86 gcc7 :을 (를) 사용하여 단일 인라인에 인라인합니다 -msse4.1. (SSE4.1없이 여러 지침에 대한 인라인)
    • x86 gcc6.x 및 이전 버전 :을 사용하여 단일 인라인으로 인라인합니다 -ffast-math -msse4.1.
    • AArch64 gcc : 기본적으로 단일 인라인으로 인라인
  • std::round:

    • x86 clang : 인라인하지 않습니다
    • x86 gcc :을 사용하여 여러 명령어에 인라인하며 -ffast-math -msse4.1두 개의 벡터 상수가 필요합니다.
    • AArch64 gcc : 단일 명령어에 대한 인라인 (이 반올림 모드 및 IEEE 기본값 및 대부분의 기타에 대한 하드웨어 지원)
  • std::floor/ std::ceil/std::trunc

    • x86 clang : 단일 인라인으로 인라인 -msse4.1
    • x86 gcc7.x : 단일 인라인으로 인라인 -msse4.1
    • x86 gcc6.x 및 이전 : 단일 인라인에 대한 인라인 -ffast-math -msse4.1
    • AArch64 gcc : 기본적으로 단일 명령에 대한 인라인

반올림 int/ long/ long long:

여기에 두 가지 옵션이 있습니다 : lrint(like rintbut returns long또는 long longfor llrint) 또는 FP-> FP 반올림 함수를 사용한 다음 일반적인 방식으로 정수 유형으로 변환하십시오 (절단 사용). 일부 컴파일러는 다른 방법보다 더 나은 방식으로 최적화합니다.

long l = lrint(x);

int  i = (int)rint(x);

참고 int i = lrint(x)변환 float또는 double> - long첫째, 다음의 정수를 자릅니다 int. 범위를 벗어난 정수의 경우 차이가 있습니다 .C ++의 정의되지 않은 동작이지만 x86 FP-> int 명령어에 대해 잘 정의되어 있습니다 (정확한 전파를 수행하는 동안 컴파일 타임에 UB를 보지 않으면 컴파일러가 방출합니다) 코드가 실행될 경우 중단되는 코드를 만들 수 있음).

86에서 정수 오버플 FP-> 정수 변환을 생성 INT_MIN하거나 LLONG_MIN(의 비트 패턴 0x8000000단지 부호 비트 세트로 또는 64 비트 상당). 인텔은이를 "정수 무한정"값이라고합니다. (참조 수동 입력 , 그것은 사용할 수 있다고 절단과 변환 () 부호있는 정수에 스칼라 배. SSE2 명령어를 32 비트 또는 64 비트 정수 대상 (64 비트 모드에서만).도 있습니다 현재 라운딩 (변환이 mode), 이것은 컴파일러가 방출하고 싶지만 불행히도 gcc와 clang은 없이는 그렇게하지 않습니다 .cvttsd2sicvtsd2si-ffast-math

또한 unsignedint / long으로의 FP 는 x86에서 (AVX512없이) 효율성이 떨어집니다. 64 비트 컴퓨터에서 부호없는 32 비트로의 변환은 매우 저렴합니다. 64 비트 부호로 변환하고 자르기 만하면됩니다. 그러나 그렇지 않으면 상당히 느려집니다.

  • 86 그 소리와 함께 / 않고 -ffast-math -msse4.1: (int/long)rint에 인라인 roundsd/ cvttsd2si. (에 최적화를 놓쳤습니다 cvtsd2si). lrint전혀 인라인하지 않습니다.

  • x86 gcc6.x 및 이전 버전 -ffast-math: 인라인 방식

  • 86 gcc7없이 -ffast-math: (int/long)rint별도로 라운드와 회심은 (SSE4.1 2 개 총 지침을 사용하여, 다른 코드의 무리와 함께 대한 인라인 rint없이 roundsd). lrint인라인하지 않습니다.
  • 86 GCC 와 함께 -ffast-math : 모든 방법에 인라인 cvtsd2si(최적) , SSE4.1에 대한 필요가 없습니다.

  • AArch64 gcc6.3 미포함 -ffast-math: (int/long)rint지시 사항 2 개에 대한 인라인. lrint인라인하지 않습니다

  • AArch64 gcc6.3 with -ffast-math: (int/long)rint에 대한 호출을 컴파일합니다 lrint. lrint인라인하지 않습니다. 우리가 얻지 못한 두 가지 명령 -ffast-math이 매우 느리지 않으면 이것은 놓친 최적화 일 수 있습니다 .

주의하십시오 floor(x+0.5). [2 ^ 52,2 ^ 53] 범위의 홀수에서 발생할 수있는 사항은 다음과 같습니다.

-bash-3.2$ cat >test-round.c <<END

#include <math.h>
#include <stdio.h>

int main() {
    double x=5000000000000001.0;
    double y=round(x);
    double z=floor(x+0.5);
    printf("      x     =%f\n",x);
    printf("round(x)    =%f\n",y);
    printf("floor(x+0.5)=%f\n",z);
    return 0;
}
END

-bash-3.2$ gcc test-round.c
-bash-3.2$ ./a.out
      x     =5000000000000001.000000
round(x)    =5000000000000001.000000
floor(x+0.5)=5000000000000002.000000

이다 http://bugs.squeak.org/view.php?id=7134 . @konik과 같은 솔루션을 사용하십시오.

내 강력한 버전은 다음과 같습니다.

double round(double x)
{
    double truncated,roundedFraction;
    double fraction = modf(x, &truncated);
    modf(2.0*fraction, &roundedFraction);
    return truncated + roundedFraction;
}

floor (x + 0.5)를 피하는 또 다른 이유는 여기에 있습니다 .


아무것도 구현할 필요가 없으므로 많은 답변이 정의, 함수 또는 메소드를 포함하는 이유를 모르겠습니다.

C99에서

형식 일반 매크로에 대한 다음과 헤더 <tgmath.h>가 있습니다.

#include <math.h>
double round (double x);
float roundf (float x);
long double roundl (long double x);

이것을 컴파일 할 수 없다면 아마도 수학 라이브러리를 생략했을 것입니다. 이와 비슷한 명령은 내가 가진 모든 C 컴파일러에서 작동합니다 (여러 개).

gcc -lm -std=c99 ...

C ++ 11에서

IEEE 배정 밀도 부동 소수점에 의존하는 #include <cmath>에는 다음과 같은 추가 과부하가 있습니다.

#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);

있다 성병 네임 스페이스의 등가물 도는.

이를 컴파일 할 수 없으면 C ++ 대신 C 컴파일을 사용하고있을 수 있습니다. 다음 기본 명령은 g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 및 Visual C ++ 2015 커뮤니티에서 오류나 경고를 생성하지 않습니다.

g++ -std=c++11 -Wall

서 수부

T가 short, int, long 또는 다른 서수 인 두 서수를 나눌 때 반올림 식은 다음과 같습니다.

T roundedQuotient = (2 * integerNumerator + 1)
    / (2 * integerDenominator);

정확성

부동 소수점 연산에서 이상한 모양의 부정확성이 나타나는 것은 의심의 여지가 없지만 숫자가 나타날 때만 발생하며 반올림과 관련이 거의 없습니다.

소스는 부동 소수점 숫자의 IEEE 표현 가수에서 유효 숫자의 숫자 일뿐만 아니라 인간으로서의 십진수 사고와 관련이 있습니다.

10은 5와 2의 곱이고 5와 2는 비교적 소수입니다. 따라서 IEEE 부동 소수점 표준을 모든 이진 디지털 표현에 대해 십진수로 완벽하게 표현할 수는 없습니다.

반올림 알고리즘에는 문제가 없습니다. 유형 선택 및 계산 설계, 데이터 입력 및 숫자 표시 중에 고려해야 할 수학적 현실입니다. 응용 프로그램에 이러한 10 진수 이진 변환 문제를 나타내는 숫자가 표시되면 응용 프로그램에서 디지털 현실에는 존재하지 않는 정확도를 시각적으로 나타내는 것이므로 변경해야합니다.


기능 double round(double)을 사용하여 modf기능 :

double round(double x)
{
    using namespace std;

    if ((numeric_limits<double>::max() - 0.5) <= x)
        return numeric_limits<double>::max();

    if ((-1*std::numeric_limits<double>::max() + 0.5) > x)
        return (-1*std::numeric_limits<double>::max());

    double intpart;
    double fractpart = modf(x, &intpart);

    if (fractpart >= 0.5)
        return (intpart + 1);
    else if (fractpart >= -0.5)
        return intpart;
    else
        return (intpart - 1);
    }

깨끗하게 컴파일하려면 "math.h"와 "limits"가 포함되어야합니다. 이 함수는 다음 반올림 스키마에 따라 작동합니다.

  • 5.0 라운드는 5.0
  • 3.8의 라운드는 4.0입니다
  • 2.3의 라운드는 2.0입니다
  • 1.5 라운드는 2.0
  • 0.501의 라운드는 1.0
  • 0.5의 라운드는 1.0
  • 0.499의 라운드는 0.0
  • 0.01의 라운드는 0.0
  • 0.0의 라운드는 0.0입니다
  • -0.01의 라운드는 -0.0입니다.
  • -0.499의 라운드는 -0.0입니다
  • -0.5의 라운드는 -0.0입니다
  • -0.501의 라운드는 -1.0입니다
  • -1.5의 라운드는 -1.0입니다
  • -2.3의 라운드는 -2.0입니다
  • -3.8의 라운드는 -4.0입니다
  • -5.0의 라운드는 -5.0입니다

C ++ 11 표준을 지원하는 환경에서 코드를 컴파일 할 수 있어야하지만이를 지원하지 않는 환경에서 동일한 코드를 컴파일 할 수 있어야하는 경우 함수 매크로를 사용하여 std 중에서 선택할 수 있습니다. :: round () 및 각 시스템에 대한 사용자 정의 함수. C ++ 11 호환 컴파일러를 전달 -DCPP11하거나 /DCPP11내장 버전 매크로를 사용하여 다음과 같이 헤더를 만드십시오.

// File: rounding.h
#include <cmath>

#ifdef CPP11
    #define ROUND(x) std::round(x)
#else    /* CPP11 */
    inline double myRound(double x) {
        return (x >= 0.0 ? std::floor(x + 0.5) : std::ceil(x - 0.5));
    }

    #define ROUND(x) myRound(x)
#endif   /* CPP11 */

간단한 예는 http://ideone.com/zal709를 참조하십시오 .

이는 -0.0에 대한 부호 비트 보존을 포함하여 C ++ 11과 호환되지 않는 환경에서 std :: round ()와 비슷합니다. 그러나 약간의 성능 저하가 발생할 수 있으며 0.49999999999999994 또는 이와 유사한 값과 같이 알려진 특정 "문제점"부동 소수점 값을 반올림하는 데 문제가있을 수 있습니다.

또는 C ++ 11 호환 컴파일러에 액세스 할 수 있다면 <cmath>헤더 에서 std :: round ()를 가져 와서 아직 정의되지 않은 경우 함수를 정의하는 자체 헤더를 만들 수 있습니다. 그러나 특히 다중 플랫폼 용으로 컴파일해야하는 경우에는 최적의 솔루션이 아닐 수 있습니다.


Kalaxy의 응답을 바탕으로 다음은 부동 소수점 숫자를 자연 반올림을 기준으로 가장 가까운 정수 유형으로 반올림하는 템플릿 솔루션입니다. 또한 값이 정수 유형의 범위를 벗어나면 디버그 모드에서 오류가 발생하여 대략적인 라이브러리 함수로 사용됩니다.

    // round a floating point number to the nearest integer
    template <typename Arg>
    int Round(Arg arg)
    {
#ifndef NDEBUG
        // check that the argument can be rounded given the return type:
        if (
            (Arg)std::numeric_limits<int>::max() < arg + (Arg) 0.5) ||
            (Arg)std::numeric_limits<int>::lowest() > arg - (Arg) 0.5)
            )
        {
            throw std::overflow_error("out of bounds");
        }
#endif

        return (arg > (Arg) 0.0) ? (int)(r + (Arg) 0.5) : (int)(r - (Arg) 0.5);
    }

주석 및 기타 답변에서 지적했듯이 round()ISO C99 표준 수학 라이브러리를 참조하여이 함수를 가져 왔을 때 ISO C ++ 표준 라이브러리는 ISO C ++ 11까지 추가되지 않았습니다 .

[½, ub ]의 양수 피연산자의 round(x) == floor (x + 0.5)경우, ubIEEE-754 (2008)에 매핑 된 경우 2 23 , floatIEEE-754 (2008) binary32매핑 된 경우 2 52 입니다 . 숫자 23과 52 는이 두 부동 소수점 형식 저장된 가수 비트 수에 해당합니다 . [+0, ½)의 양의 피연산자 및 ( ub , + ∞]의 양의 피연산자에 대한 함수가 x 축에 대해 대칭이므로 음수 인수 는에 따라 처리 될 수 있습니다 .doublebinary64round(x) == 0round(x) == xxround(-x) == -round(x)

이것은 아래의 컴팩트 코드로 이어집니다. 다양한 플랫폼에서 합리적인 수의 기계 명령어로 컴파일됩니다. my_roundf()약 12 개의 명령이 필요한 GPU에서 가장 컴팩트 한 코드를 관찰했습니다 . 프로세서 아키텍처 및 툴체인에 따라이 부동 소수점 기반 접근 방식은 다른 답변 에서 참조 된 newlib의 정수 기반 구현보다 빠르거나 느릴 수 있습니다 .

나는 시험 my_roundf()newlib에 대해 철저하게 roundf()모두 인텔 컴파일러 버전 (13)를 사용하여 구현 /fp:strict하고 /fp:fast. 나는 또한 newlib에 버전이 일치하는지 확인 roundf()에서 mathimf인텔 컴파일러의 라이브러리입니다. 배정도에 대한 철저한 테스트는 불가능 round()하지만 코드는 단 정밀도 구현과 구조적으로 동일합니다.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

float my_roundf (float x)
{
    const float half = 0.5f;
    const float one = 2 * half;
    const float lbound = half;
    const float ubound = 1L << 23;
    float a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floorf (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

double my_round (double x)
{
    const double half = 0.5;
    const double one = 2 * half;
    const double lbound = half;
    const double ubound = 1ULL << 52;
    double a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floor (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

uint32_t float_as_uint (float a)
{
    uint32_t r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float uint_as_float (uint32_t a)
{
    float r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float newlib_roundf (float x)
{
    uint32_t w;
    int exponent_less_127;

    w = float_as_uint(x);
    /* Extract exponent field. */
    exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
    if (exponent_less_127 < 23) {
        if (exponent_less_127 < 0) {
            /* Extract sign bit. */
            w &= 0x80000000;
            if (exponent_less_127 == -1) {
                /* Result is +1.0 or -1.0. */
                w |= ((uint32_t)127 << 23);
            }
        } else {
            uint32_t exponent_mask = 0x007fffff >> exponent_less_127;
            if ((w & exponent_mask) == 0) {
                /* x has an integral value. */
                return x;
            }
            w += 0x00400000 >> exponent_less_127;
            w &= ~exponent_mask;
        }
    } else {
        if (exponent_less_127 == 128) {
            /* x is NaN or infinite so raise FE_INVALID by adding */
            return x + x;
        } else {
            return x;
        }
    }
    x = uint_as_float (w);
    return x;
}

int main (void)
{
    uint32_t argi, resi, refi;
    float arg, res, ref;

    argi = 0;
    do {
        arg = uint_as_float (argi);
        ref = newlib_roundf (arg);
        res = my_roundf (arg);
        resi = float_as_uint (res);
        refi = float_as_uint (ref);
        if (resi != refi) { // check for identical bit pattern
            printf ("!!!! arg=%08x  res=%08x  ref=%08x\n", argi, resi, refi);
            return EXIT_FAILURE;
        }
        argi++;
    } while (argi);
    return EXIT_SUCCESS;
}

x86 아키텍처 및 MS VS 특정 C ++에 대해 다음과 같은 라운드 인 asm 구현을 사용합니다.

__forceinline int Round(const double v)
{
    int r;
    __asm
    {
        FLD     v
        FISTP   r
        FWAIT
    };
    return r;
}

UPD : 이중 값을 반환

__forceinline double dround(const double v)
{
    double r;
    __asm
    {
        FLD     v
        FRNDINT
        FSTP    r
        FWAIT
    };
    return r;
}

산출:

dround(0.1): 0.000000000000000
dround(-0.1): -0.000000000000000
dround(0.9): 1.000000000000000
dround(-0.9): -1.000000000000000
dround(1.1): 1.000000000000000
dround(-1.1): -1.000000000000000
dround(0.49999999999999994): 0.000000000000000
dround(-0.49999999999999994): -0.000000000000000
dround(0.5): 0.000000000000000
dround(-0.5): -0.000000000000000

부동 소수점 값을 "n"소수점 이하 자릿수로 반올림하는 가장 좋은 방법은 O (1) 시간과 다음과 같습니다.

값을 3 자리로 반올림해야합니다 (예 : n = 3).

float a=47.8732355;
printf("%.3f",a);

// Convert the float to a string
// We might use stringstream, but it looks like it truncates the float to only
//5 decimal points (maybe that's what you want anyway =P)

float MyFloat = 5.11133333311111333;
float NewConvertedFloat = 0.0;
string FirstString = " ";
string SecondString = " ";
stringstream ss (stringstream::in | stringstream::out);
ss << MyFloat;
FirstString = ss.str();

// Take out how ever many decimal places you want
// (this is a string it includes the point)
SecondString = FirstString.substr(0,5);
//whatever precision decimal place you want

// Convert it back to a float
stringstream(SecondString) >> NewConvertedFloat;
cout << NewConvertedFloat;
system("pause");

비효율적 인 더러운 변환 방법 일지 모르지만 도대체 작동합니다. 실제 플로트에 적용되기 때문에 좋습니다. 시각적으로 출력에 영향을 미치지 않습니다.


나는 이걸했다:

#include <cmath.h>

using namespace std;

double roundh(double number, int place){

    /* place = decimal point. Putting in 0 will make it round to whole
                              number. putting in 1 will round to the
                              tenths digit.
    */

    number *= 10^place;
    int istack = (int)floor(number);
    int out = number-istack;
    if (out < 0.5){
        floor(number);
        number /= 10^place;
        return number;
    }
    if (out > 0.4) {
        ceil(number);
        number /= 10^place;
        return number;
    }
}

참고 URL : https://stackoverflow.com/questions/485525/round-for-float-in-c

반응형