Rounding error when converting from double to int

M

mark

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
 
F

Flash Gordon

Richard said:
mark said:


The binary representation of 9.495 is bound to be a smidgen under that
- 9.494995... or so. 100 times this is 949.4995..., add 0.5 to get
949.9995, convert to int, hey presto, 949. Solution: add a smidgen
before multiplying. 0.0001 is probably about right.

Or may be wrong (in the more general case). I've seen people get rather
upset about discrepancies of a penny! Well, if they only need to worry
down to a 10th of a cent it should be OK. However, I would look to see
if C++ has floor/ceil functions or other methods of converting from
double to int that do what is required.

Oh for decimal floating point... and also fixed-point arithmetic all
built in the the language. <sigh>

(Note that this is a C newsgroup, not a C++ newsgroup.)

Indeed, and C++ may or may not have floor/ceil and/or other methods of
converting between double and int. Only C++ people will no for sure...
 
F

Flash Gordon

Richard said:
Gordon Burditt said:


You don't have to put money amounts into doubles to print them. If
you're storing, say, pennies, you can print like this:

fprintf(fp, "%c%lu.%02lu\n", currency_symbol,
pennies / 100,
pennies % 100);

Agreed.

Furthermore, sometimes you have to put money amounts in a double because
you only have 32 bit integer types, but 64 bit doubles, so you can use
double as a larger integer type then long! Of course, it requires a
little care, and you would still work in pennies (or cents, or tenths of
a cent, or whatever) rather than dollars (or pounds).
My text in this article (from "You don't have to..." to the end of
this paragraph) is protected by copyright law, and I do not give
permission for it to be copied without proper attribution.

Similar rules apply to all my posts :)
 
U

user923005

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?

Get decnumber from IBM.
http://www.alphaworks.ibm.com/tech/decnumber

Examples 2,3,6 show calculation of interest.
decNumber\example2.c:8:// example2.c -- calculate compound interest
decNumber\example3.c:8:// example3.c -- calculate compound interest,
passive checking
decNumber\example6.c:8:// example6.c -- calculate compound interest,
using Packed Decimal
 
P

Phil Carmody

Richard Heathfield said:
My text in this article (from "You don't have to..." to the end of
this paragraph) is protected by copyright law, and I do not give
permission for it to be copied without proper attribution.

Stick that in your headers - that way you're always covered.

And Gordon - please learn to attribute correctly, it's
annoying not knowing who is being responded to.

Phil
 
K

Keith Thompson

Phil Carmody said:
Stick that in your headers - that way you're always covered.

Hardly anybody reads headers. I think Richard wants Gordon to be
aware of his preferences.
And Gordon - please learn to attribute correctly, it's
annoying not knowing who is being responded to.

It's not a matter of knowing. Gordon knows perfectly well how to
attribute correctly. He deliberately removes attribution lines.
Repeated attempts to ask him to stop have failed, and I don't expect
that to change.
 
B

bartc

Flash Gordon said:
Or may be wrong (in the more general case). I've seen people get rather
upset about discrepancies of a penny!

So they should. A penny error shows something is seriously wrong. And a
customer would instantly pick up on it.

Well, if they only need to worry
down to a 10th of a cent it should be OK. However, I would look to see if
C++ has floor/ceil functions or other methods of converting from double to
int that do what is required.

Oh for decimal floating point... and also fixed-point arithmetic all built
in the the language. <sigh>

That won't always help. Many calculations involved fractions of a penny and
at some point (maybe several points, which is the problem), it has to be
rounded to an exact penny.
 
U

user923005


The package MIRACL employs this approach, actually storing all
rational numbers as a ratio of two integers.

You can still get problems there.

Consider the above number raised to the 40th power. The denominator
will have lots of bits in it. So if the value gets complex enough,
you will still have precision failures if you want exact mathematics.
 
K

Keith Thompson

user923005 said:
The package MIRACL employs this approach, actually storing all
rational numbers as a ratio of two integers.

You can still get problems there.

Consider the above number raised to the 40th power. The denominator
will have lots of bits in it. So if the value gets complex enough,
you will still have precision failures if you want exact mathematics.

A properly written rational arithmetic package will never have
"precision failures" in the sense of generating an imprecise result.
It can easily run out of storage, though.
 
K

Kaz Kylheku

The package MIRACL employs this approach, actually storing all
rational numbers as a ratio of two integers.

You can still get problems there.

Consider the above number raised to the 40th power. The denominator
will have lots of bits in it. So if the value gets complex enough,
you will still have precision failures if you want exact mathematics.

CLISP session:

[1]> (expt 1/10 40)
1/10000000000000000000000000000000000000000

The powers have to be a lot larger before we run into trouble. :)
 
R

Richard Bos

Kaz Kylheku said:

Now take the square root of that.

Unlikely in a financial package, I know. But rationals solve some of the
problem - in finances they probably solve _most_ of the problem - but
they can't solve all of the problem.

Richard
 
K

Keith Thompson

Eric Sosman said:
io_x wrote: [...]
{unsigned i;
double m05=(0.5), m2, m10=(1.0), m1=(1.0), mr;
double parteIntera;

if(cifra>=15)
{ *res=a; return; }
F(i=0; i<cifra; ++i)

Oh, sweet Jesus, spare me from macro abusers! I guess you've
got `#define F for' somewhere so this will compile? It's not at
all helpful, it's not at all clever, it is purely silly. Don't do
it any more, not even once more, or I for one will not offer any
help for any questions you may ever ask again.
[...]

He's been using those silly macros for years, and repeated requests
that he stop using them, at least when posting here, have been
completely ineffective. I've already stated that I'll never help
him with any code that uses them.

He's also repeatedly changed the name under which he posts (possibly
to evade killfiles). I've seen him post as RoSsIaCrIiLoIA,
av <[email protected]>, and "rio" <[email protected]>.
 
K

Kaz Kylheku

Now take the square root of that.

We just need a suitable base. I pick base root-ten. For instance the
root-ten number 123 means 1*10 + 2*sqrt(10) + 3. 3 units,
2 root-tens, and 3 tens. In this base we can represent anything that
base 10 can represent. Just expand the digits leaving the root parts to vanish.
For instance decimal 12345 becomes 102030405 in root-ten. 5 units, zero
root-tens, 4 tens, zero ten-root-tens. 3 hundreds, zero 100-root-tens, etc.

So, in this base, 1/sqrt(10) can be written as 1/10 or 0.1.
Unlikely in a financial package, I know. But rationals solve some of the
problem - in finances they probably solve _most_ of the problem - but
they can't solve all of the problem.

Also, you can keep a square root result wrapped as a sqrt form. If at any point
in the computation such a quantity is squared, then just hoist the original
value out. If you need to print it, then approximate.

:)
 
P

Paul Hsieh

Steve McConnel, in his book "Code Complete", suggests converting money
amounts from double to integer in order to avoid rounding errors
during calculations.

I assume he meant a *LONG* integer. I declined a free copy of this
book, so I don't have the actual text of it available to me.
[...] 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.

The problem is that the compiler makes 1.115 into 1.1149999...
internally, so that it does indeed get correctly rounded. It just
doesn't match your "visual expectation". The way that the C language
typically interacts with IEEE-754 numbers you probably want the
following:

if (value < 0)
myCurrencyx100 = (__int64) (100.0 * INCR_VAR_BY_ULP(value,1) +
0.5);
else
myCurrencyx100 = (__int64) (100.0 * INCR_VAR_BY_ULP(value,1) -
0.5);

You can practically implement INCR_ONE_ULP as:

double INCR_VAR_BY_ULP (double p, int inc) {
__int64 v = *((__int64 *)&(p)) + inc;
return *((double *)&v);
}

Its a bit gross, and doesn't work for INFs or NANs but its not grosser
than your use of __int64 in the first place. It also stops working as
you lose resolution when the values go higher then 1e53, but that
probably won't be an issue.
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));

}

This second one is closer, as you want to do the shift first, before
the scale, since that's the point at which you can correct IEEE-754
round-off effects. If you scale first, then you have a harder time
finding the ULP. For example, replace 0.005 above with 0.0050001 and
0.00499999 say, and you will see that it fixes pretty much all the
problems you will encounter. The ULP fix I show above is a little
more exacting with the representational anomolies but is clearly not
portable (neither is the use of __int64, BTW).
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?

You could use strings as your representational type instead of
doubles, and avoid the round off from doubles in the first place.
 
U

user923005

A properly written rational arithmetic package will never have
"precision failures" in the sense of generating an imprecise result.
It can easily run out of storage, though.

I have seen rational packages where you set a limit on the number of
significant digits (e.g. 1000, or 1000000) and they will report
precision failures.
I am not sure if this is better than running out of memory, but I
would argue that they are properly written if the behavior is
documented.
 
K

Keith Thompson

Paul Hsieh said:
I assume he meant a *LONG* integer. I declined a free copy of this
book, so I don't have the actual text of it available to me.
[...]

mark said "integer", not "int". long int is an integer type.

I don't have the book in front of me either, but it may well have been
referring to integers in general rather than to some particular C
types.

(Of course you need to choose an integer type wide enough to hold
whatever values you'll need to store.)
 
E

Eric Sosman

io_x said:
[...]
have you run my routines?

No; the compiler wouldn't let me:

foo.c:11: error: parse error before "u32"
foo.c: In function `Round05':
foo.c:16: error: `cifra' undeclared (first use in this function)
foo.c:16: error: (Each undeclared identifier is reported only once
foo.c:16: error: for each function it appears in.)
foo.c:17: error: `res' undeclared (first use in this function)
foo.c:17: error: `a' undeclared (first use in this function)
foo.c:18: warning: implicit declaration of function `F'
foo.c:18: error: parse error before ';' token
foo.c:18: error: parse error before ')' token
foo.c:13: warning: unused variable `m2'
foo.c:13: warning: unused variable `m10'
foo.c:13: warning: unused variable `m1'
foo.c:13: warning: unused variable `mr'
foo.c:14: warning: unused variable `parteIntera'
foo.c: At top level:
foo.c:20: warning: type defaults to `int' in declaration of `m2'
foo.c:20: error: `a' undeclared here (not in a function)
foo.c:20: error: `m10' undeclared here (not in a function)
foo.c:20: error: ISO C forbids data definition with no type or storage class
foo.c:21: error: parse error before '&' token
foo.c:21: warning: type defaults to `int' in declaration of `modf'
foo.c:21: error: conflicting types for `modf'
/dev/env/DJDIR/include/math.h:35: error: previous declaration of `modf'
foo.c:21: error: ISO C forbids data definition with no type or storage class
foo.c:24: warning: type defaults to `int' in declaration of `m2'
foo.c:24: error: redefinition of `m2'
foo.c:20: error: `m2' previously defined here
foo.c:24: error: `parteIntera' undeclared here (not in a function)
foo.c:24: error: ISO C forbids data definition with no type or storage class
foo.c:25: warning: type defaults to `int' in declaration of `m2'
foo.c:25: error: redefinition of `m2'
foo.c:24: error: `m2' previously defined here
foo.c:25: error: `m10' undeclared here (not in a function)
foo.c:25: error: ISO C forbids data definition with no type or storage class
foo.c:25: warning: type defaults to `int' in declaration of `mr'
foo.c:25: error: initializer element is not constant
foo.c:25: error: ISO C forbids data definition with no type or storage class
foo.c:25: warning: type defaults to `int' in declaration of `m2'
foo.c:25: error: redefinition of `m2'
foo.c:25: error: `m2' previously defined here
foo.c:25: error: `a' undeclared here (not in a function)
foo.c:25: error: ISO C forbids data definition with no type or storage class
foo.c:26: warning: ISO C does not allow extra `;' outside of a function
foo.c:26: error: parse error before '<<' token
foo.c:27: error: parse error before string constant
foo.c:27: warning: type defaults to `int' in declaration of `printf'
foo.c:27: warning: conflicting types for built-in function `printf'
foo.c:27: error: ISO C forbids data definition with no type or storage class
foo.c:29: warning: type defaults to `int' in declaration of `res'
foo.c:29: error: `res' used prior to declaration
foo.c:29: warning: initialization makes pointer from integer without a cast
foo.c:29: error: initializer element is not constant
foo.c:29: error: ISO C forbids data definition with no type or storage class
foo.c:30: error: parse error before "return"

Fifty-six lines of compiler complaints from thirty-one
lines of source. Not the worst I've seen, but not good.
 
E

Eric Sosman

io_x said:
Eric Sosman said:
io_x said:
[...]
have you run my routines?
No; the compiler wouldn't let me:

foo.c:11: error: parse error before "u32"
foo.c: In function `Round05':
foo.c:16: error: `cifra' undeclared (first use in this function) ...
foo.c:30: error: parse error before "return"

Fifty-six lines of compiler complaints from thirty-one
lines of source. Not the worst I've seen, but not good.

hope this compile, this is for a 64bit double don't know
if 0.00000000000001 is ok for some other compiler-cpu-os
[... code snipped ...]

Much better! The compiler now emits just one warning,
and it's harmless.
this is result

Inserisci un numero> 9.495
m2=0.00499999999999900524 m05=0.00500000000000000010
Round05(9.49499999999999921800)=9.50000000000000000000
[...]

This looks like fun! May I play, too?

Inserisci un numero> 1.23
m2=-0.00000000000000001778 m05=0.00500000000000000010
Round05(1.22999999999999998224)=1.22999999999999998224

Oh, gee, that's not too promising, it is? We begin with
a number having only two decimal places, which ought to be
really easy to round to two decimal places, and we get a number
a bunch of trailing nines and other garbage. We also that the
value of m05 is not exactly five thousandths, but a slightly
different number. Omigosh! Is my computer broken?
 
N

Nick Keighley

"Eric Sosman" <[email protected]> ha scritto nel messaggio




Mr Sosman have right in all!

yes the computer is broken, ieee standard for float bumber is broken.
If someone find one use for that broken float i'm interstted to hear it.

For the lurkers: io_x doesn't generally know what he's talking about.

The computer is not broken. The IEEE floating point standard is not
broken.
Floating point is inherently approximate (don't let this spark another
thread, I'm being approximate as well!). Well written floating point
programs
can yield good approximations to the real (the type) value. In the
real
(as in real) world of bridges and space shuttles this is also the
correct answer. If someone hasn't mentioned already, read this:

http://docs.sun.com/source/806-3568/ncg_goldberg.html

There is an entire body of computer science knowledge called
"Numerical Analysis". IMHO you should have at least have sniffed
around it before you start pontificating on the subject.

because if the number grow from one side of the point , it reduce
in the other side of the point...

i not will use C double type for anithing;

goody. So you won't be posting about them either.
better fixed point
better to have  "nbits.nbits"
or write float number like integer, all is better than this!

fixed point has it place (no pun) but there are problems with
fixed point as well. You can loose accuracy there as well.
You can have scaling and normalisation problems.

<snip>

The fundamental problem is that the universe won't fit in your
computer.
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,060
Latest member
BuyKetozenseACV

Latest Threads

Top