round() for float in C++

后端 未结 22 1179
时光取名叫无心
时光取名叫无心 2020-11-22 03:01

I need a simple floating point rounding function, thus:

double round(double);

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

I can find

22条回答
  •  盖世英雄少女心
    2020-11-22 03:26

    The C++03 standard relies on the C90 standard for what the standard calls the Standard C Library which is covered in the draft C++03 standard (closest publicly available draft standard to C++03 is N1804) section 1.2 Normative references:

    The library described in clause 7 of ISO/IEC 9899:1990 and clause 7 of ISO/IEC 9899/Amd.1:1995 is hereinafter called the Standard C Library.1)

    If we go to the C documentation for round, lround, llround on cppreference we can see that round and related functions are part of C99 and thus won't be available in C++03 or prior.

    In C++11 this changes since C++11 relies on the C99 draft standard for C standard library and therefore provides std::round and for integral return types std::lround, std::llround :

    #include 
    #include 
    
    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 ;
    }
    

    Another option also from C99 would be std::trunc which:

    Computes nearest integer not greater in magnitude than arg.

    #include 
    #include 
    
    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 ;
    
    }
    

    If you need to support non C++11 applications your best bet would be to use boost round, iround, lround, llround or boost trunc.

    Rolling your own version of round is hard

    Rolling your own is probably not worth the effort as Harder than it looks: rounding float to nearest integer, part 1, Rounding float to nearest integer, part 2 and Rounding float to nearest integer, part 3 explain:

    For example a common roll your implementation using std::floor and adding 0.5 does not work for all inputs:

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

    One input this will fail for is 0.49999999999999994, (see it live).

    Another common implementation involves casting a floating point type to an integral type, which can invoke undefined behavior in the case where the integral part can not be represented in the destination type. We can see this from the draft C++ standard section 4.9 Floating-integral conversions which says (emphasis mine):

    A prvalue of a floating point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.[...]

    For example:

    float myround(float f)
    {
      return static_cast( static_cast( f ) ) ;
    }
    

    Given std::numeric_limits::max() is 4294967295 then the following call:

    myround( 4294967296.5f ) 
    

    will cause overflow, (see it live).

    We can see how difficult this really is by looking at this answer to Concise way to implement round() in C? which referencing newlibs version of single precision float round. It is a very long function for something which seems simple. It seems unlikely that anyone without intimate knowledge of floating point implementations could correctly implement this function:

    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;
    }
    

    On the other hand if none of the other solutions are usable newlib could potentially be an option since it is a well tested implementation.

提交回复
热议问题