Thursday, 26 February 2015

Different modes of rounding float values in C and C++

Welcome to Logically Proven blog.

This post demonstrates different modes of rounding float values in C/C++ programming.

Floating-point calculations are carried out internally with extra precision, and then rounded to fit into the destination type.

ROUNDING MODES:

IEEE 754 defines the following four possible rounding modes:

1. Round to nearest (FE_TONEAREST)
2. Round toward plus infinity (FE_UPWARD)
3. Round toward minus infinity (FE_DOWNWARD)
4. Round toward zero (FE_TOWARDZERO)

RETURN VALUES:

You can decide the following destination types when rounding the float values -

These functions are available in the header #include <math.h>
So don't forget to include this header file while working with rounding the float values.

rint() - this function returns the integral value depending on rounding mode.

Syntax:

//x is the float value
double rint(double x)

If x is +/- infinity, rint() returns x.
If x is NaN, NaN is returned.

rintf() - this function returns the single-precision value depending on rounding mode.

Syntax:

//x is the float value
float rint(float x)

#include <fenv.h>

All these rounding modes are defined in the header file #include <fenv.h> as shown below -

/* FPU control word rounding flags */
#define FE_TONEAREST 0x0000   //decimal value: 0
#define FE_DOWNWARD 0x0400   //decimal value: 1024
#define FE_UPWARD 0x0800   //decimal value: 2048
#define FE_TOWARDZERO 0x0c00   //decimal value: 3072

The macro values may change depending on compiler.
The decimal representation of hex values are useful while setting the rounding mode. This is explained in the later section.

Now we look into each rounding mode.

Round to nearest (FE_TONEAREST):

This is the default mode. In this mode results are rounded to the nearest representable value. If the result is midway between two representable values, the even representable is chosen. Even here means the lowest-order bit is zero. This rounding mode prevents statistical bias and guarantees numeric stability.

Round toward plus infinity (FE_UPWARD):

All results are rounded to the smallest representable value which is greater than the result. In other words the values are rounded towards +infinity.

Round toward minus infinity (FE_DOWNWARD):

All results are rounded to the largest representable value which is less than the result. In other words the values are rounded towards -infinity.

Round toward zero (FE_TOWARDZERO):

All results are rounded to the largest representable value which is less than the result. In other words, if the result is negative it is rounded up; if it is positive, it is rounded down.

Important note - If the result is too small to be represented, it is rounded to zero. However, the sign of the result is preserved.

E.g. FE_TOWARDZERO(-0.22) returns -0.

This is because the negative zero can also result from some operations on infinity, such as 4/-infinity.

Get and Set Rounding Mode:

int fegetround(void) - This function is used to to return the currently selected rounding mode.

int fesetround(int round) -

This function is used to set the one of the rounding modes. The argument 'round' to this function is either an decimal representation or macro name (e.g. FE_UPWARD) of rounding mode. This function returns non-zero value if it fails to set rounding mode else zero if success.

Note - Avoid changing the rounding mode if possible. It can be expensive operation which leads to run the code slower than expected. For more details see your compiler documentation.

C Example:

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

int main()
{
    float fValue;
    fValue = 10.22;
    
    //by default FE_TONEAREST
    printf("\nFE_TONEAREST:%f\n",rintf(fValue));

    //set to FE_UPWARD, returns zero if success
    //set using macro name
    printf("Set to FE_UPWARD: %d\n",fesetround(FE_UPWARD));
    printf("Currently the rounding mode is :%d\n",fegetround());
    printf("FE_TOUPWARD(10.22):%f\n",rintf(fValue));

    //set to FE_DOWNWARD, returns zero if success
    //FE_DOWNWARD integral representation is 1024
    //set using integral representation of macro name
    printf("Set to FE_DOWNWARD: %d\n",fesetround(1024));
    printf("Currently the rounding mode is :%d\n",fegetround());
    printf("FE_DOWNUPWARD(10.22):%f\n",rintf(fValue));

    //set to FE_TOWARDZERO, returns zero if success
    printf("Set to FE_TOWARD: %d\n",fesetround(FE_TOWARDZERO));
    printf("Currently the rounding mode is :%d\n",fegetround());
    printf("FE_TOWARDZERO(-0.22):%f\n",rintf(-0.22));

    printf("Currently the rounding mode is :%d\n",fegetround());
    printf("FE_TOWARDZERO(1.22):%f\n",rintf(1.52));

    return 0;
}

/* output:
FE_TONEAREST:10.000000
Set to FE_UPWARD: 0
Currently the rounding mode is :2048
FE_TOUPWARD(10.22):11.000000
Set to FE_DOWNWARD: 0
Currently the rounding mode is :1024
FE_DOWNUPWARD(10.22):10.000000
Set to FE_TOWARD: 0
Currently the rounding mode is :3072
FE_TOWARDZERO(-0.22):-0.000000
Currently the rounding mode is :3072
FE_TOWARDZERO(1.22):1.000000
*/

C++ Example: Rounding is guaranteed only if #pragma STDC FENV_ACCESS ON is set.

Include header files fenv.h and math.h if you are using older versions of C++. C++11 version and later supports cfenv.h and cmath.h header files.

#include <iostream>
#include <string>
#include <cfenv>
#include <cmath>
int main()
{
#pragma STDC FENV_ACCESS ON
    std::fesetround(FE_DOWNWARD);
    std::cout << "rounding down: \n"              
              << "  rintf(2.8) = " << std::rint(2.8) << "\n\n";
}

/*output:
rounding down:
   rint(2.8) = 2
*/

Please write your comments if you find anything is incorrect or do you want to share more information about the topic discussed above.

Logically Proven
Learn, Teach, Share

Karthik Byggari

Author & Editor

Computer Science graduate, Techie, Founder of logicallyproven, Love to Share and Read About pprogramming related things.

3 comments:

  1. So the two functions that you have used are defined by compiler..please disclose your complete header definition file...if I use the pragma in the header, will it work..?

    ReplyDelete
    Replies
    1. Hi Mayukh,

      1. All the header files specified in the code snippets are inbuilt from C++ libraries. If you want to see the complete header file definition, right-click on the included header file in your code, select open

      2. Yes, you can use #pragma in the header file. The header file definitions are added into the main file (main) at the time of compilation. So my answer is - it works.

      Please let me know if you have any questions. Thanks for your visit to the page.

      Delete
  2. karthikbyggari9 March 2015 at 08:04

    Hi Mayukh,

    1. All the header files specified in the code snippets are inbuilt from C++ libraries. If you want to see the complete header file definition, right-click on the included header file in your code, select open

    2. Yes, you can use #pragma in the header file. The header file definitions are added into the main file (main) at the time of compilation. So my answer is - it works.

    Please let me know if you have any questions. Thanks for your visit to the page.

    ReplyDelete

 
biz.