Rounding error when converting from double to int

C

clintonb

Steve McConnel, in his book "Code Complete", suggests converting money
amounts from double to integer in order to avoid rounding errors
during calculations. So we made a money class where the money amount
is stored as an integer instead of a double. The cents are stored in
the 10's and 1's position of the integer.

For example: $1.23 is stored as the integer 123. The cents portion
is 23, and the rest is the dollar portion.

However we are having trouble initializing this integer amount when
the original double amount contains fractions of a cent (due to some
interest calculations). We want to round any fractional cents up to
the nearest cent. We use a rounding scheme where values greater than
or equal to 5 are rounded up.

Below is our initialization function. Note that we use the type
__int64 so we can hold larger numbers but the same problems happen
with type int also. myCurrencyx100 is of the type __int64.

The code multiplies the double by 100 first, then it rounds to the
nearest 1, then it converts to int.
So if value = 9.495 we want myCurrencyx100 to be initialized to 950.
But the code sets it to 949.

void GCSMoney::init( const double& value )
{
if( value < 0 )
myCurrencyx100 = static_cast<__int64>(100.0 * value - 0.5);
else
myCurrencyx100 = static_cast<__int64>(100.0 * value + 0.5);
}


I experimented with the order of the calculations. I changed it to
round to the nearest hundredth (cent), then multiply by 100, then
convert to int. This will correctly set myCurrencyx100 to 950 when
the double value is 9.495. But when I tried a double value of 1.115,
it rounded it to 1.11 when it should have rounded it to 1.12. However
the first init method worked for 1.115.

void GCSMoney::init( const double& value )
{
if( value < 0 )
myCurrencyx100 = static_cast<__int64>(100.0 * (value -
0.005));
else
myCurrencyx100 = static_cast<__int64>(100.0 * (value +
0.005));
}

So I have two different methods, but neither works with all values.
For each of these methods, I noticed if I recoded the steps as
individual lines of code, the error happens during the step when
converting the double to int.


Is there any commonly accepted practice for handling these types of
errors?

Thanks in advance,
Clint
 
E

Erik Wikström

Steve McConnel, in his book "Code Complete", suggests converting money
amounts from double to integer in order to avoid rounding errors
during calculations. So we made a money class where the money amount
is stored as an integer instead of a double. The cents are stored in
the 10's and 1's position of the integer.

For example: $1.23 is stored as the integer 123. The cents portion
is 23, and the rest is the dollar portion.

However we are having trouble initializing this integer amount when
the original double amount contains fractions of a cent (due to some
interest calculations). We want to round any fractional cents up to
the nearest cent. We use a rounding scheme where values greater than
or equal to 5 are rounded up.

Below is our initialization function. Note that we use the type
__int64 so we can hold larger numbers but the same problems happen
with type int also. myCurrencyx100 is of the type __int64.

The code multiplies the double by 100 first, then it rounds to the
nearest 1, then it converts to int.
So if value = 9.495 we want myCurrencyx100 to be initialized to 950.
But the code sets it to 949.

void GCSMoney::init( const double& value )
{
if( value < 0 )
myCurrencyx100 = static_cast<__int64>(100.0 * value - 0.5);
else
myCurrencyx100 = static_cast<__int64>(100.0 * value + 0.5);
}


I experimented with the order of the calculations. I changed it to
round to the nearest hundredth (cent), then multiply by 100, then
convert to int. This will correctly set myCurrencyx100 to 950 when
the double value is 9.495. But when I tried a double value of 1.115,
it rounded it to 1.11 when it should have rounded it to 1.12. However
the first init method worked for 1.115.

void GCSMoney::init( const double& value )
{
if( value < 0 )
myCurrencyx100 = static_cast<__int64>(100.0 * (value -
0.005));
else
myCurrencyx100 = static_cast<__int64>(100.0 * (value +
0.005));
}

So I have two different methods, but neither works with all values.
For each of these methods, I noticed if I recoded the steps as
individual lines of code, the error happens during the step when
converting the double to int.


Is there any commonly accepted practice for handling these types of
errors?

Convert the value to int first and then compare with the double to see
the size of the fraction and decide what to do from there:

void GCSMoney::init(const double& d)
{
int val = d * 100;
if ((d * 100 - val) < 0.5)
myCurrencyx100 = val;
else
myCurrencyx100 = val + 1;
}
 
C

clintonb

Convert the value to int first and then compare with the double to see
the size of the fraction and decide what to do from there:

void GCSMoney::init(const double& d)
{
int val = d * 100;
if ((d * 100 - val) < 0.5)
myCurrencyx100 = val;
else
myCurrencyx100 = val + 1;

}

Thanks for the quick response Erik. Your suggestion seemed like a
good idea. Unfortunately, it didn't solve the problem. At least for
the value 9.495.

int val = d * 100 = 9.495 * 100 = 949

d * 100 - val = 9.495 * 100 - 949 = 0.499999999999866

So that expression is less that 0.5.
 
C

clintonb

I doubt the reason is to "avoid rounding errors" but I'll take your word
for it.



The correct way to say it would probably be that the amount is in cents.
No "10's and 1's", no "positions". Just the number of cents, and simply
because there are no fractional cents in circulation.


It's not "the dollar portion". It's just 123 (one hundred and
twenty-three) cents. If you want to represent it in dollars and cents,
then you get 1 dollar and 23 cents.


You have no values "equal to 5". You have 5 one-thousandths of a dollar
and those should be rounded to the next integer cent. But read on.



It doesn't, really. It adds or subtracts 0.5. You *think* it rounds,
but you're incorrect.



How do you know that the value is 9.495(0) exactly? What if the actual
value is 9.494999999999999? In that case it *should* be rounded down,
shouldn't it? I would bet that that is actually the case.





Here you go again... How do you "round to the nearest hundredth"? You
don't really round anything *until* you involve an integer. Storing a
floating point value in an integer involves rounding. Some CPUs have
instructions to change the value in their FP register as if they were
stored in an integer and then loaded back in, but C++ does not have such
tools or means.



Again, how do you know that the value is 1.115(0)? It's most likely not
1.115 at all, but 1.1149999999999999.



I believe you misinterpret the results as well. They work fine with all
the values, you are just blissfully unaware of what the actual values are.



Errors? I believe you need to read more about FP and how it's
represented and how to handle arithmetic imprecision. Also, consider
that *all calculations* should be performed in *cents* and not in
dollars. You should never have to "round" anything like '1.115'. In
that case the value should be 111.5 and it would work like a charm in
your "add 0.5 and assign to the integer" algorithm.

V

Thanks Victor for the response. I read some articles on how floating
point numbers are stored, but I must admit, I got confused. I believe
your assumptions about the real amounts are true.

Clint
 
K

kwikius

clintonb said:
Steve McConnel, in his book "Code Complete", suggests converting money
amounts from double to integer in order to avoid rounding errors
during calculations. So we made a money class where the money amount
is stored as an integer instead of a double. The cents are stored in
the 10's and 1's position of the integer.

Is there any commonly accepted practice for handling these types of
errors?

There are various methods for dealing with bits and pieces of floating point
types in <cmath> header.

Heres one suggestion:

#include <iostream>
#include <cmath>

int main()
{
//some input value in dollars
double dollars_unrounded = 15.12499999999;

//integer part of dollars
double dolls = floor(dollars_unrounded);
//fraction * 100 (unrounded cents)
double cents_unrounded = fmod(dollars_unrounded,1)*100;

//integer part of cents
double cents = floor(cents_unrounded);
//fraction
double cents_rem = fmod(cents_unrounded,1);
// do rounding
if ( cents_rem >= 0.5){
++ cents;
}

std::cout << "$" << dolls << " " << cents << "c\n";


}

regards
Andy Little
 
K

kwikius

kwikius said:
There are various methods for dealing with bits and pieces of floating
point types in <cmath> header.

Heres one suggestion:

<...>
oops ... technically the cmath functions should be preceeded by std:: as are
theoretically in std namespace

regards
Andy Little
 
C

clintonb

There are various methods for dealing with bits and pieces of floating point
types in <cmath> header.

Heres one suggestion:

#include <iostream>
#include <cmath>

int main()
{
//some input value in dollars
double dollars_unrounded = 15.12499999999;

//integer part of dollars
double dolls = floor(dollars_unrounded);
//fraction * 100 (unrounded cents)
double cents_unrounded = fmod(dollars_unrounded,1)*100;

//integer part of cents
double cents = floor(cents_unrounded);
//fraction
double cents_rem = fmod(cents_unrounded,1);
// do rounding
if ( cents_rem >= 0.5){
++ cents;
}

std::cout << "$" << dolls << " " << cents << "c\n";

}

regards
Andy Little

Thanks Andy. Unfortunately your solution didn't work for me either.

Using your code, if I set dollars_unrounded = 9.495
Here are my results:
dolls = 9;
cents_unrounded = 49.4999999999999
cents = 49
cents_rem = 0.499999999999922

Since cents_rem is not greater than or equal to 0.5, cents is not
incremented from 49 to 50.


Clint
 
C

clintonb

Victor said:
Also, consider that *all calculations* should be performed in *cents* and not in
dollars. You should never have to "round" anything like '1.115'.

The double value that I'm trying to convert to GCSMoney (which is
implemented as cents) was produced by multiplying a dollar amount by
an interest rate to get interest.

double amount = 126.60;
double interestRate = .075;
double interest = amount * interestRate;
int interestAsCents = interest * 100.0 + 0.5;

The debugger says interest = 9.495.
But when I convert it to cents, we see that interest really wasn't
9.495 since the cents rounds to 949 instead of 950.

Below is code where I do my calculations as cents. I can convert
amount to cents by multiplying by 100 since I'm dealing with a dollar
amount. But interest rate is not a dollar amount. I can't convert it
to cents. In this particular case, I can convert it to an integer by
multiplying by 1000 since it has three decimal places.

double amount = 126.60;
double interestRate = .075;
int amountAsCents = amount * 100; // = 12660
int interestRateAsInt = interestRate * 1000; // = 75
int interestTemp = amountAsCents * interestRateAsInt; // = 949500
interestTemp += 500; // round it. = 950000
int interestAsCents = interestTemp / 1000; // undo interestRate
* 10000. = 950

So this works. But what if I have an interest rate that has more
decimal places? For example, 0.1234? Then I would have to convert
it to an int by multiplying by 10000 and later divide by 10000.

In general, if I had a money class that stored money amounts as cents
and I needed a multiplication operator that could multiply a money
type by a double, how would I implement that? Would I just
arbitrarily choose the amount of double precision like this?:

Clint
int precision = 3;
double amount = 126.60;
double interestRate = .075;
int amountAsCents = amount * 100; // = 12660
int interestRateAsInt = interestRate * pow( 10.0, precision ); //
= 75
int interestTemp = amountAsCents * interestRateAsInt; // = 949500
interestTemp += 5 * pow(10.0, precision - 1 ); // round it. = 950000
int interestAsCents = interestTemp / pow(10.0, precision); //
undo interestRate * 10000. = 950

Or instead of hardcoding the precision in the code above, could I
detect how many decimal places there are and use that as my precision?
 
M

Marcel Müller

clintonb said:
However we are having trouble initializing this integer amount when
the original double amount contains fractions of a cent (due to some
interest calculations).

There is nothing like an original double amount. If you care about
rounding errors you must not store any currency value in an approximate
number. You must not do this even once. Least of all at something like
an original value.
If you take account of that the question to do a correct back-conversion
is obsolete. Of course, if you make some assumptions about the expected
values and the domain of the values a conversion is possible.

We want to round any fractional cents up to
the nearest cent. We use a rounding scheme where values greater than
or equal to 5 are rounded up.

int cents = (int)floor(float_dollars*100. +.5);

But be warned. This may fail for larges values.

Is there any commonly accepted practice for handling these types of
errors?

Yes: no approximate numbers for currency values.

Furthermore, it is a common practice in commercial applications to store
currency values with 4 fractional digits. This is important if you apply
relative conditions like rebates.

If the language supports it, decimal data types are preferred for
currency values. C++ does not support this out of the box. But using
long long with a scalefactor of 100 or 10000 by convention should be
fine too.


Marcel
 
M

Marcel Müller

clintonb said:
Thanks Andy. Unfortunately your solution didn't work for me either.

Using your code, if I set dollars_unrounded = 9.495
Here are my results:
dolls = 9;
cents_unrounded = 49.4999999999999
cents = 49
cents_rem = 0.499999999999922

Since cents_rem is not greater than or equal to 0.5, cents is not
incremented from 49 to 50.

:)

You did not catch what is going on here, isn't it?

The compiler cannot even store the fractional number 9.495 in a IEEE
float storage, because that number is not an element of the domain of
the double data type.
So your application was never compiled with 9.495. Beyond this it
behaves as expected.

That is the difference between approximate numbers like double and
rational numbers. Non periodic decimal numbers are a subset of rational
numbers.
But the approximate numbers (usually) use a base 2 representation. In
this representation rational numbers with a denominator that has prime
factors other than 2 cannot be mapped with finite storage.


Marcel
 
K

kwikius

Thanks Andy.  Unfortunately your solution didn't work for me either.

Using your code, if I set dollars_unrounded = 9.495
Here are my results:
dolls = 9;
cents_unrounded = 49.4999999999999
cents = 49
cents_rem = 0.499999999999922

Since cents_rem is not greater than or equal to 0.5, cents is not
incremented from 49 to 50.

Try the following:

int main()
{
//show lots of decimal digits
std::cout.precision(32);
// when we initialise the variable
// the compiler converts the decimal representation
// to binary

double dollars_unrounded = 9.495;

// lets see what we really have...
std::cout << dollars_unrounded <<'\n';

}
On my systemm I get :

9.4949999999999992



The problem is that the number that is input as decimal must be
converted to binary format, so its hopeless from the start!

The binary representation is actually held as an integer (mantissa)
part and then as a power of 2 part (exponent), but at the end of the
day it is actually stored as a rational number. If you think of
dividing integer (say) 10 / 3 you get a remainder, which floating
point solves by shifting the mantissa left and bumping down the
exponent. The problem is that the mantissa is only (say) 4 bytes and
if the actual mantissa is longer it just drops the lsb thus losing
accuracy(but still bumps the exponent).

The rounding mode should be specifed so that for a range of similar
situations the unsymmetry cancels out (using nearest neighbour
rounding), but the details are implementation defined AFAIK.

In some systems you can specify to change the rounding mode so that in
this situation the result would always be rounded up, but whether you
can do this is implementation defined.

AFAIK the only way out of this one is to write your own decimal type
and input to that via text strings.

regards
Andy Little
 
K

kwikius

Try the following:

int main()
{
 //show lots of decimal digits
 std::cout.precision(32);
 // when we initialise the variable
 // the compiler converts the decimal representation
 // to binary

 double dollars_unrounded = 9.495;

// lets see what we really have...
 std::cout << dollars_unrounded <<'\n';

}

On my systemm I get :

9.4949999999999992

The problem is that the number that is input as decimal must be
converted to binary format, so its hopeless from the start!

 The binary representation is actually held as an integer (mantissa)
part and then as a power of 2 part (exponent), but at the end of the
day it is actually stored as a rational number. If you think of
dividing integer (say) 10 / 3  you get a remainder, which floating
point solves by shifting the mantissa left and bumping down the
exponent. The problem is that the mantissa is only (say) 4 bytes and
if the actual mantissa is longer it just drops the lsb thus losing
accuracy(but still bumps the exponent).

The rounding mode should be specifed so that for a range of similar
situations the unsymmetry cancels out (using nearest neighbour
rounding), but the details are implementation defined AFAIK.

In some systems you can specify to change the rounding mode so that in
this situation the result would always be rounded up, but whether you
can do this is implementation defined.

AFAIK the only way out of this one is to write your own decimal type
and input to that via text strings.

OTOH you could use a type that keeps the dollars and cents separate

dollars_and_cents mymoney(9, 495);
//9 dollars and 495 cents

You can then do the math on dollars and cents separately and do your
own rounding.

regards
Andy Little
 
K

kwikius

if the actual mantissa is longer it just drops the lsb thus losing
accuracy(but still bumps the exponent).

The rounding mode should be specifed so that for a range of similar
situations the unsymmetry cancels out (using nearest neighbour
rounding), but the details are implementation defined AFAIK.

Actually the last 2 para are in conflict. In reality what happens to any
underflow is implementation defined, but there are various rounding schemes
that deal with it.

regards
Andy Little
 
J

James Kanze

Victor said:
The double value that I'm trying to convert to GCSMoney (which is
implemented as cents) was produced by multiplying a dollar amount by
an interest rate to get interest.
double amount = 126.60;
double interestRate = .075;
double interest = amount * interestRate;
int interestAsCents = interest * 100.0 + 0.5;
The debugger says interest = 9.495.
But when I convert it to cents, we see that interest really
wasn't 9.495 since the cents rounds to 949 instead of 950.

Well, the first error in the above: interestRate is NOT .075,
because .075 can't be represented as a double. Since you start
with an incorrect value, obviously, all of the remaining values
will be incorrect as well.
Below is code where I do my calculations as cents. I can
convert amount to cents by multiplying by 100 since I'm
dealing with a dollar amount. But interest rate is not a
dollar amount. I can't convert it to cents.

No, but it has been specified as an exact decimal fraction, so
you must use some representation which preserves its exact
value, and double isn't it.

(The actually depends on what you're using the monetary values
for. But if it's any bookkeeping, the law generally says
exactly how you have to round, and it it almost always specifies
the rules in terms of decimal values.)
In this particular case, I can convert it to an integer by
multiplying by 1000 since it has three decimal places.

For example. More generally, regardless of the number of
decimal places, you can convert by multiplying by a power of
ten.

This is usually handled with some sort of class, which stores
the integral value and the number of digits after the decimal.
The values are determined directly when converting the input.
double amount = 126.60;
double interestRate = .075;
int amountAsCents = amount * 100; // = 12660
int interestRateAsInt = interestRate * 1000; // = 75
int interestTemp = amountAsCents * interestRateAsInt; // = 949500
interestTemp += 500; // round it. = 950000
int interestAsCents = interestTemp / 1000; // undo interestRate
* 10000. = 950
So this works. But what if I have an interest rate that has
more decimal places? For example, 0.1234? Then I would have
to convert it to an int by multiplying by 10000 and later
divide by 10000.

Given something like:

class Decimal
{
// ...
long long value ;
int scalingFactor ;
} ;

you multiply by multiplying the value, and adding teh
scalingFactor.

In addition to the usual arithmetic operations, you probably
want some functions to round to a specific position, etc.
In general, if I had a money class that stored money amounts
as cents and I needed a multiplication operator that could
multiply a money type by a double, how would I implement that?
Would I just arbitrarily choose the amount of double precision
like this?:

No. Not arbitrarily, but from the input. If the input is 1.23,
then value is 123, and scalingFactor 2. If it is 0.12345, then
value is 12345, and scalingFactor 5.
Clint
int precision = 3;
double amount = 126.60;
double interestRate = .075;
int amountAsCents = amount * 100; // = 12660
int interestRateAsInt = interestRate * pow( 10.0, precision ); //
= 75
int interestTemp = amountAsCents * interestRateAsInt; // = 949500
interestTemp += 5 * pow(10.0, precision - 1 ); // round it. = 950000
int interestAsCents = interestTemp / pow(10.0, precision); //
undo interestRate * 10000. = 950
Or instead of hardcoding the precision in the code above,
could I detect how many decimal places there are and use that
as my precision?

Exactly. And since the only powers you need are those of 10
(and you can probably restrict those, e.g. by restricting the
scalingFactor to +/- 13, like Cobol does), you can easily put
those in a table. (Don't forget that pow(10.0, precision) will
return a possibly inexact double.)

Better yet, I'm sure that there are packages on the market which
do this already. Use one of them.
 
E

Erik Wikström

Victor said:


The double value that I'm trying to convert to GCSMoney (which is
implemented as cents) was produced by multiplying a dollar amount by
an interest rate to get interest.


You should perform the calculations with integers too, as soon as you
use floating point values you run the risk of getting problems. You
should consider creating (or getting from somewhere) a Decimal-class
which can represent decimal numbers and perform calculations with them.
 
K

Kai-Uwe Bux

Razii said:
Speed and size are not his problem. His problem is rounding errors.
The problem will be solved 100% if he uses a multi-precision library.
If you can calculate million of PI digits in a fraction of seconds
with a fast Bignum library, you certainly can use it to track money
with no noticeable speed penalty.

Actually, tracking money is _not_ a domain where arbitrary precision is a
good idea. The laws of the land usually will require accounting to be done
according to specified rounding rules and also determine to which precision
intermediate results are to be calculated. For money, you really need a
decimal fixed point class with many bells and whistles if you want to do it
right (i.e., so that the accountants and lawers of your company are happy).


Best

Kai-Uwe Bux
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top