• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 140
  • Last Modified:

C++ rounding half-even

Hi,
Is there a C/C++ equivalent function for rounding half-even? I need identical function to Java's BigDecimal.setScale(int scale, BigDecimal.ROUND_HALF_EVEN);
So 20.225 = 20.22
20.235 = 20.24
Thanks.
0
Vakils
Asked:
Vakils
  • 9
  • 7
  • 3
  • +1
1 Solution
 
David Johnson, CD, MVPOwnerCommented:
// bankers-round.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <iomanip>
#include <cstring>
#include <math.h>
using namespace std;

float bround(float f)
{
	if (!(f > -8388608.0f && f < 8388608.0f)) // Return true for NaN
		return f;
	else if (f > 0)
		return (float)(f + 8388608.0f) - 8388608.0f;
	else
		return (float)(f - 8388608.0f) + 8388608.0f;
}
/**
* Main entry to the program.
*/
int main(void) {
	float test1 = 20.225;
	float test2 = 20.235;

	cout << "Testing " << test1 << endl;
	cout << (bround(test1 * 100)/100) << endl;
	cout << "Testing " << test2  << endl;
	cout << (bround(test2*100)/100) << endl;
	return 0;
}

Open in new window


Testing 20.225
20.22
Testing 20.235
20.24

https://stackoverflow.com/questions/32746523/ieee-754-compliant-round-half-to-even
0
 
VakilsDeveloperAuthor Commented:
David,
This works! Whoever thought of this method  is genius. Thanks for picking the answer (there were many). Can you explain the method? I did not fully understand. Also, I get compiler warning assigning double to float.
0
 
sarabandeCommented:
it only works with float.

for double you may use this:

double bround2dec(double d)
{
    // save sign
    int sgn = (d < 0.)? -1 : 1;
    double dabs = d * sgn;
    // convert to 64 bit integer and factor 1000
    long long ll1000 = (long long)(dabs * 1000);

    // get last digit 
    int rc = (int) ll1000%10;
    // if  the first decimal digit which decides whether to round up or down is not 5 
    // we simply can round by adding 5 and cut by doing integer rounding
    if (rc != 5)
        return ((ll1000 + 5)/1000.) * sgn;

    // if coming here we extract the digit which was subject of being rounded 
    int rr = (int) ((ll1000-rc)/10)%10;
    // if the digit is odd we round up else we round down 
    return  ((rr+1)%2 == 0)? ((ll1000 + 5)/1000.) * sgn : ((ll1000-5)/1000.) * sgn;
}

// you can use it like 

#include <sstream>
#include <iostream>
#include <string>
      ....

    double d1 =  20.225;//  225.885;
    double d2 =  20.235;//  -0.065; 

    std::ostringstream oss;
    // std::ios::fixed means that the number of decimal digits in the fraction given by the setprecison was fixed
    // otherwise the precision means the number of significant digits of the decimal number
    oss << d1 << " was rounded to " << std::setiosflags( std::ios::fixed ) << std::setprecision(2) << bround2dec(d1) << "  " 
        << d2 << " was rounded to " << std::setiosflags( std::ios::fixed ) << std::setprecision(2) << bround2dec(d2);
    std::string s = oss.str();

Open in new window


Sara
1
Cloud Class® Course: CompTIA Healthcare IT Tech

This course will help prep you to earn the CompTIA Healthcare IT Technician certification showing that you have the knowledge and skills needed to succeed in installing, managing, and troubleshooting IT systems in medical and clinical settings.

 
sarabandeCommented:
the compiler warning will arise if you were using a double where a float is expected. for example test1*100 is a double for most compilers as the 100 has to be converted to a floating point number and double is the default. if you would use test1*100. the compiler should handle the result to be a float because the test1 explicitly was defined as a float and the 100. easily can be initialized as a float as well as it has only 3 significant digits what is within the precision range of a float.

Can you explain the method?
the explanation was given in the link phoffric has posted:

The float data type can represent all whole numbers, but no fractions, within the range 8388608.0f to 16777216.0f. Any float numbers which are larger than 8388607.5f are whole numbers, and no rounding will be necessary. Adding 8388608.0f to any non-negative float which is smaller than that will yield a whole number which will be rounded according to the current rounding mode (typically round-half-to-even). Subtracting 8388608.0f will then yield a properly-rounded version of the original (assuming it was in a suitable range).

in other words: (positive) integers stored in a float variable are in a special range of the float internal coding. if you add 8388608.0f to any arbitrary float that you want to round properly at the integer boundary (note: test1 was multiplied with 100) you would move the float number into the integer range area and therefore it was rounded correctly by using the standard half-to-even round algorithm. if you finally subtract the 8388608.0f you will get the original float back but rounded.

Sara
0
 
phoffricCommented:
>> in the link phoffric has posted:
Could you provide a link for the quote.
0
 
sarabandeCommented:
it is stackoverflow.com/questions/32746523/ieee-754-compliant-round-half-to-even

the quoted text is in the final part of this lengthy thread posted by a member called 'supercat'.

you can find it easily by searching for "8388608.0f"

Sara
0
 
VakilsDeveloperAuthor Commented:
Thanks, Sara. I will try it out and post results.
0
 
VakilsDeveloperAuthor Commented:
Sara, I got results below:
David's code:
Testing -20.125
-20.12
Testing -701.252
-701.25

Your code:
-20.125 was rounded to -20.12  -701.25 was rounded to -701.26 should be -701.25
Is it because my system is 32 bit?
0
 
sarabandeCommented:
Is it because my system is 32 bit?

no, bitness shouldn't make any difference (beside that the 8388608.0f is valid for 32-bit float only).

-701.252 must be rounded to .701.25 because the 3rd digit after decimal point is not a '5'.

the return of my function is

((ll1000 + 5)/1000.) * sgn;

what is (701252 + 5)/1000. * (-1)

what is (701257/1000. * (-1)

what is 701.257 * (-1)

what is - 701.26 rounded to 2 decimals.

so, there is a bug in my code which is the division by 1000. (and not 1000) what makes the result a double instead of an integer.

if you change

return ((ll1000 + 5)/1000.) * sgn;

Open in new window


by

return ((ll1000 + 5)/1000) * sgn;

Open in new window


it should work correctly.

Sara
0
 
VakilsDeveloperAuthor Commented:
OK. Thanks. I will inform you of results.
0
 
phoffricCommented:
@Sara,

>> the explanation was given in the link phoffric has posted:
Please provide the EE link where you are quoting me.
0
 
VakilsDeveloperAuthor Commented:
@phorric
 Sara most likely meant: explanation is given in link posted to you. Go thru the thread.
0
 
sarabandeCommented:
sorry, phoffric.

the link i mentioned was from david at #a42521142

Sara
0
 
VakilsDeveloperAuthor Commented:
Sara,
That does not work either. The output is 701.00.
-20.125 was rounded to -20.12   -701.252 was rounded to -701.00
and compiler warning:
warning C4244: 'return' : conversion from '__int64' to 'double', possible loss of data
because: return ((ll1000 + 5)/1000) * sgn; is _int64.
I tweaked to: return ((ll1000-5)/1000.) * sgn; //Comments?
The output is: -20.125 was rounded to -20.12   -701.252 was rounded to -701.25.
With +ve value: -20.125 was rounded to -20.12   701.252 was rounded to 701.25
The true test of rounding half-even:
20.145 was rounded to 20.14   20.135 was rounded to 20.14
But this does not hit above code.
Final test with David's output:
Testing -701.252
-701.25
Testing 701.252
701.25

-701.252 was rounded to -701.25   701.252 was rounded to 701.25
which matches.
Finally, instead of cout, how do you store your output in a double variable with precision 2?
0
 
VakilsDeveloperAuthor Commented:
Thanks all. I find David's solution more robust and fits my need.
0
 
phoffricCommented:
>> sorry, phoffric.   the link i mentioned was from david

I was trying so hard remembering where/when I may have made the statement.

Aha, OK, well in the future, try not to take my name in vain. :)
0
 
sarabandeCommented:
That does not work either.

the corrected code is

#include <limits.h>

double bround(double d)
{
    // save the sign info
    int sgn = (d < 0.)? -1 : 1;
    // make it positive
    double dabs = d * sgn;
    // convert to 64-bit integer  

    // note a 64-bit integer has a range from -2^63 to 2^63 
    // while a double is from approximately -1.8 × 10^308 to 1.8 × 10^308
    // if that is an issue you may not use the bround function
    if (d < (double)(LLONG_MIN/1000) || d > (double)(LLONG_MAX/1000))
    {
          return d;
    }

    // convert to 64-bit integer with 3 fractional digits
    long long ll1000 = (long long)(dabs * 1000);

    // extract last digit
    int rc = (int) ll1000%10;
    // if not 5 we can do a 'normal rounding' ...
    if (rc != 5)
    {
        // ... by adding 5 to the long long and do an integer division by 10
        // so for 0 to 4 we get a maximum of 9 and therefore round down and 
        // for 6 to 9 we pass the next 10 boundary and round up 
        // by dividing with 100. we get a double with 2 decimals 

        return ((ll1000 + 5)/10)/100. * sgn;
    }
    // extract the forelast digit
    int rr = (int) ((ll1000-rc)/10)%10;
    // check if even or odd by using modulo 2
    // and either add 5 for odd digits or subtract 5 for even digits
    // what both means to round to the nearest even integer
    return  ((rr+1)%2 == 0)? ((ll1000 + 5)/10)/100. * sgn : ((ll1000-5)/10)/100. * sgn;
}

Open in new window



Finally, instead of cout, how do you store your output in a double variable with precision 2?

decimal precision is an output feature. a double has two binary components the mantissa and the exponent. with that you can't fix it to 2 decimal places. for nearly all cases (beside of multiples of 2 numbers)  a double is either a small amount less or a small amount greater than the a decimal number rounded to 2 decimal places. if you look at the doubles returned by the bround function with the debugger, you will see that the doubles either display like 20.23500000000000... or like -225.229999999999... . the ... digits displayed after 16 significant digits in general are different to the '0' or '9' sequence left from it.  that is because a double only has a precision of 16 decimals (approximately 15.95 decimal digits (log10(2^53)) and 53 bits is the length of the mantissa in bits, see https://en.wikipedia.org/wiki/Double-precision_floating-point_format).

so, the only ways to store exactly with precision 2 is either to store strings or to store integers by multiplying the double with 100.

note, a double has 8 bytes, a 32-bit integer 4 bytes and a char buffer would take number of digits + 1 for the decimal point + 1 for the terminating zero character, e. g. "20.24" would need a buffer of 6 bytes.

Sara
1
 
VakilsDeveloperAuthor Commented:
Thanks for clarification. That is exactly what i found.
0
 
VakilsDeveloperAuthor Commented:
@sara
I modified your code and now it works for me for large values (which did not work in float method)
double bround2dec(double d)
{
    // save sign
    int sgn = (d < 0.)? -1 : 1;
    double dabs = d * sgn;
	char buffer[32];
	double retVal =0.00;
    // convert to 64 bit integer and factor 1000 - This could be an in parameter depending on scale
    long long ll1000 = (long long)(dabs * 1000);

    // get last digit 
    long long rc = (long long) ll1000%10;
    // if  last digit is 5 and before it is even,  subtract 5, else add 5
    // so 20.905 = 20.90 and 20.915 = 20.92
    if (rc == 5)
	{
		if((ll1000-5)/10%10%2 == 0)
			retVal = (ll1000 -5)/1000.* sgn;
		else
			retVal = (ll1000+5)/1000.* sgn;
	}
	else
		retVal = dabs* sgn;

	sprintf(buffer, "%0.2f", retVal);
	return  atof(buffer);
}

Open in new window

I tested out in my app and seems to work for large numbers
Do you see any issues?
0
 
sarabandeCommented:
no there are no issues with the solution as it is straight forward and is checking directly the 2nd and 3rd digit after period.

Sara
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

  • 9
  • 7
  • 3
  • +1
Tackle projects and never again get stuck behind a technical roadblock.
Join Now