Weird arithmetic

P

Pascal Gallois

Hi newsgroup,

I am new to C and wonder about the following code:

#include <stdio.h>

int main(void)
{
double m = 100.45;
double n = 200.45;
double o = 200.45;

int i = m * 100;
int j = n * 100;

o = n * 100;
int k = o;

printf("%lf = %i\n"
"%lf = %i\n"
"%lf = %i\n", m * 100, i, n * 100, j, o, k);

return 0;
}


Compiled with GCC-3.4.3 on a Linux box (Fedora Core 3 x86) the output
looks like this:

$ gcc -Wall -pedantic -std=c99 test.c -o test
$ ./test
10045.000000 = 10045
20045.000000 = 20044
20045.000000 = 20045

How could this be?

Thanks in advance!
- Pascal
 
M

Mike Wahler

Pascal Gallois said:
Hi newsgroup,

I am new to C and wonder about the following code:

#include <stdio.h>

int main(void)
{
double m = 100.45;
double n = 200.45;
double o = 200.45;

int i = m * 100;
int j = n * 100;

o = n * 100;
int k = o;

printf("%lf = %i\n"
"%lf = %i\n"
"%lf = %i\n", m * 100, i, n * 100, j, o, k);

return 0;
}


Compiled with GCC-3.4.3 on a Linux box (Fedora Core 3 x86) the output
looks like this:

$ gcc -Wall -pedantic -std=c99 test.c -o test
$ ./test
10045.000000 = 10045
20045.000000 = 20044
20045.000000 = 20045

How could this be?

http://tinyurl.com/b5ms6

-Mike
 
K

Kenneth Brody

Pascal said:
Hi newsgroup,

I am new to C and wonder about the following code:

#include <stdio.h>

int main(void)
{
double m = 100.45;
double n = 200.45;
double o = 200.45;

int i = m * 100;
int j = n * 100;

o = n * 100;
int k = o;

printf("%lf = %i\n"
"%lf = %i\n"
"%lf = %i\n", m * 100, i, n * 100, j, o, k);

return 0;
}

Compiled with GCC-3.4.3 on a Linux box (Fedora Core 3 x86) the output
looks like this:

$ gcc -Wall -pedantic -std=c99 test.c -o test
$ ./test
10045.000000 = 10045
20045.000000 = 20044
20045.000000 = 20045

How could this be?

Rounding. Remember, not all decimal numbers can be stored exactly as
binary numbers.

Change the printf as follows to see more details:

printf("%30.20lf %.20lf = %i\n"
"%30.20lf %.20lf = %i\n"
"%30.20lf %.20lf = %i\n",
m, m * 100, i,
n, n * 100, j,
n, o, k);

On my system, "100.45" is "100.45000000000000284217", whereas "200.45"
is "200.44999999999998863132". With the multiplication and conversion
to integer, your system may cause the second to truncate to "20044".

Also, note that my system gives "20045" for the second line of output.

--
+-------------------------+--------------------+-----------------------------+
| Kenneth J. Brody | www.hvcomputer.com | |
| kenbrody/at\spamcop.net | www.fptech.com | #include <std_disclaimer.h> |
+-------------------------+--------------------+-----------------------------+
Don't e-mail me at: <mailto:[email protected]>
 
P

Pascal Gallois

Rounding. Remember, not all decimal numbers can be stored exactly as
binary numbers.

Change the printf as follows to see more details:

printf("%30.20lf %.20lf = %i\n"
"%30.20lf %.20lf = %i\n"
"%30.20lf %.20lf = %i\n",
m, m * 100, i,
n, n * 100, j,
n, o, k);

On my system, "100.45" is "100.45000000000000284217", whereas "200.45"
is "200.44999999999998863132". With the multiplication and conversion
to integer, your system may cause the second to truncate to "20044".

Also, note that my system gives "20045" for the second line of output.

Thanks Kenneth,

I applied your suggested version and as you said, the initial values of
m, n and o are not exact. But after the multiplication the output
indicates that they are (or at least I interpret the fractional 0s this
way):

$ ./test
100.45000000000000284217 10045.00000000000000000000 = 10045
200.44999999999998863132 20045.00000000000000000000 = 20044
200.44999999999998863132 20045.00000000000000000000 = 20045

Shouldn't the conversion from double to int just discard the fractional
part? I guess I misinterpret something here but I still don't get it
completely. Do you care to explain to me?

- Pascal
 
W

Walter Roberson

Shouldn't the conversion from double to int just discard the fractional
part?

No, there are elaborate rules about what should be rounded in what
direction. If you follow that tinyurl that was pointed, have a look
at the "round to even" portion.
 
P

Pascal Gallois

No, there are elaborate rules about what should be rounded in what
direction. If you follow that tinyurl that was pointed, have a look
at the "round to even" portion.

Hhhmm. Quoting from ISO/IEC 9899:1999:

"The conversions from floating to integer types provide IEC 60559-like conversions
but always round toward zero." (F.3)

and:

"When a finite value of real floating type is converted to an integer type other than _Bool,
the fractional part is discarded (i.e., the value is truncated toward
zero)." (6.3.1.4)

So I understand that the computation involving floating point values might
produce rounding errors. Lets say after double m = 200.45; m is really
200.44999999999998863132. But why does printf("%.30lf\n", m * 100);
print 20045.000000000000000000000000000000 which would be 20045 if
assigned to an int, but gets 20044 when assigned? Shouldn't printf show
something like 20044.9999999999988... in this case?

Thank you!
- Pascal
 
K

Keith Thompson

Pascal Gallois said:
Hi newsgroup,

I am new to C and wonder about the following code:
[snip]

Compiled with GCC-3.4.3 on a Linux box (Fedora Core 3 x86) the output
looks like this:

$ gcc -Wall -pedantic -std=c99 test.c -o test
$ ./test
10045.000000 = 10045
20045.000000 = 20044
20045.000000 = 20045

How could this be?

Here's another program that exhibits the problem:

#include <stdio.h>

int main(void)
{
double x = 200.45;
double x_times_100 = x * 100;

printf("x = %46.40f --> %d\n", x, (int)x);
printf("x * 100 = %46.40f --> %d\n", x * 100, (int)(x * 100));
printf("x_times_100 = %46.40f --> %d\n", x_times_100, (int)x_times_100);

return 0;
}

On x86 systems, I get the following output:

x = 200.4499999999999886313162278383970260620117 --> 200
x * 100 = 20045.0000000000000000000000000000000000000000 --> 20044
x_times_100 = 20045.0000000000000000000000000000000000000000 --> 20045

On a SPARC system, I get:

x = 200.4499999999999886313162278383970260620117 --> 200
x * 100 = 20045.0000000000000000000000000000000000000000 --> 20045
x_times_100 = 20045.0000000000000000000000000000000000000000 --> 20045

There's no guarantee that 200.45 * 100 >= 20045.0 -- but if it is, the
result of converting 20045.0 to int should be 20045, not 20044.

I think there's an issue on x86 systems with intermediate results
being stored in extended-precision registers. It looks like the
result of x*100 is being stored with extra precision when it's passed
directly to printf(), but not when it's the operand of a cast to int.
Or something like that.

The "-ffloat-store" option didn't affect the behavior.
 
J

Joe Wright

Pascal said:
Hi newsgroup,

I am new to C and wonder about the following code:

#include <stdio.h>

int main(void)
{
double m = 100.45;
double n = 200.45;
double o = 200.45;

int i = m * 100;
int j = n * 100;

o = n * 100;
int k = o;

printf("%lf = %i\n"
"%lf = %i\n"
"%lf = %i\n", m * 100, i, n * 100, j, o, k);

return 0;
}


Compiled with GCC-3.4.3 on a Linux box (Fedora Core 3 x86) the output
looks like this:

$ gcc -Wall -pedantic -std=c99 test.c -o test
$ ./test
10045.000000 = 10045
20045.000000 = 20044
20045.000000 = 20045

How could this be?

Thanks in advance!
- Pascal

What output did you expect? What do you expect from "%lf" format?
 
K

Keith Thompson

Joe Wright said:
What output did you expect? What do you expect from "%lf" format?

Given a C99 conforming printf, I'd expect "%lf" to be equivalent to
"%f", expecting a double argument. (In C90, it's undefined behavior,
but I don't think it's causing the symptoms the OP is seeing.)
 
K

Kevin Bracey

In message <[email protected]>
Keith Thompson said:
I think there's an issue on x86 systems with intermediate results
being stored in extended-precision registers. It looks like the
result of x*100 is being stored with extra precision when it's passed
directly to printf(), but not when it's the operand of a cast to int.
Or something like that.

Which is the reason I asserted the other day that x86 gcc is not C90
conformant - these problems keep coming up with its floating point support.

Someone lept to its defence and claimed that this wasn't true, and these
floating point problems only applied to some obsolete code generation
options, not when using MMX (or whatever it is). Perhaps a gcc person could
clarify how to change this option to get C90 conformance?
 
B

Ben Pfaff

Kevin Bracey said:
In message <[email protected]>


Which is the reason I asserted the other day that x86 gcc is not C90
conformant - these problems keep coming up with its floating point support.

Is this true even with -fno-fast-math? (That should be the default.)
Someone lept to its defence and claimed that this wasn't true, and these
floating point problems only applied to some obsolete code generation
options, not when using MMX (or whatever it is). Perhaps a gcc person could
clarify how to change this option to get C90 conformance?

This probably refers to -mfpmath=sse.

Followups set.
 
T

tedu

Keith said:
On x86 systems, I get the following output:

x = 200.4499999999999886313162278383970260620117 --> 200
x * 100 = 20045.0000000000000000000000000000000000000000 --> 20044
x_times_100 = 20045.0000000000000000000000000000000000000000 --> 20045

On a SPARC system, I get:

x = 200.4499999999999886313162278383970260620117 --> 200
x * 100 = 20045.0000000000000000000000000000000000000000 --> 20045
x_times_100 = 20045.0000000000000000000000000000000000000000 --> 20045

There's no guarantee that 200.45 * 100 >= 20045.0 -- but if it is, the
result of converting 20045.0 to int should be 20045, not 20044.

I think there's an issue on x86 systems with intermediate results
being stored in extended-precision registers. It looks like the
result of x*100 is being stored with extra precision when it's passed
directly to printf(), but not when it's the operand of a cast to int.
Or something like that.

There's more to it than that; gcc on any x86 BSD machine produces the
correct result.
 
R

Randy Howard

Here's another program that exhibits the problem:

#include <stdio.h>

int main(void)
{
double x = 200.45;
double x_times_100 = x * 100;

printf("x = %46.40f --> %d\n", x, (int)x);
printf("x * 100 = %46.40f --> %d\n", x * 100, (int)(x * 100));
printf("x_times_100 = %46.40f --> %d\n", x_times_100, (int)x_times_100);

return 0;
}

On x86 systems, I get the following output:

x = 200.4499999999999886313162278383970260620117 --> 200
x * 100 = 20045.0000000000000000000000000000000000000000 --> 20044
x_times_100 = 20045.0000000000000000000000000000000000000000 --> 20045

On a SPARC system, I get:

x = 200.4499999999999886313162278383970260620117 --> 200
x * 100 = 20045.0000000000000000000000000000000000000000 --> 20045
x_times_100 = 20045.0000000000000000000000000000000000000000 --> 20045

I get exactly the same results using gcc 4.0 on Mac OS X 10.4.1 and
a G5, compiling for either 32-bit or 64-bit binary.

--
Randy Howard (2reply remove FOOBAR)
Life should not be a journey to the grave with the intention of arriving
safely in an attractive and well preserved body, but rather to skid in
sideways, chocolate in one hand, martini in the other, body thoroughly
used up, totally worn out and screaming "WOO HOO what a ride!!"
 
P

Pascal Gallois

I get exactly the same results using gcc 4.0 on Mac OS X 10.4.1 and
a G5, compiling for either 32-bit or 64-bit binary.

On the other hand compiling with Microsoft's VS.NET C/C++ compiler
produces correct results. I tend to consider this a GCC related bug.

- Pascal
 
T

Tim Prince

Pascal said:
On the other hand compiling with Microsoft's VS.NET C/C++ compiler
produces correct results. I tend to consider this a GCC related bug.
You haven't yet revealed which options and rounding modes you use. I guess
you're saying that the defaults of gcc are buggy compared with the defaults
of an unspecified Microsoft compiler. gcc defaults were determined by
relatively open consensus among developers who want support for long
double, while your Microsoft compiler is unlikely to support long double.
You don't take any responsibility for setting equivalent options; for
example gcc -march=pentium4 -mfpmath=sse
or setting 53-bit precision mode when running the gcc x87 version, when that
is the default run-time setting for 32-bit MSVC.
 
C

Chris Torek

On x86 systems, I get [result A] ...
On a SPARC system, I get [different result B].

I get exactly the same results using gcc 4.0 on Mac OS X 10.4.1 and
a G5, compiling for either 32-bit or 64-bit binary.

Exactly the same as Result A, or exactly the same as Result B?

(I do not really need to know; I merely note that your post was
ambiguous, so that the other follow-ups to it, which made claims
about gcc, are inconclusive at best.)
 
D

Dik T. Winter

>
> On the other hand compiling with Microsoft's VS.NET C/C++ compiler
> produces correct results. I tend to consider this a GCC related bug.

What is the correct result? Assuming IEEE double, 200.45 is represented
as:
11001000.011100110011001100110011001100110011001100110
multiplying by 100 gives:
100111001001100.111111111111111111111111111111111111111011
and rounding that to double again yields:
100111001001101.00000000000000000000000000000000000000
or exactly 2045.

A SPARC has no extended precision, but an x86 has. The standard allows
for excess precision in expressions, and that is why int(x * 100) == 2044
on some systems but not on other. Note that when x * 100 is passed as
an argument to printf the excess precision is lost because the argument
is rounded to double.
 
R

Randy Howard

On x86 systems, I get [result A] ...
On a SPARC system, I get [different result B].

I get exactly the same results using gcc 4.0 on Mac OS X 10.4.1 and
a G5, compiling for either 32-bit or 64-bit binary.

Exactly the same as Result A, or exactly the same as Result B?

I'm sorry, you're exactly right. Not sure why I wasn't more clear.
I get the same results that were reported earlier on the Sparc.

--
Randy Howard (2reply remove FOOBAR)
Life should not be a journey to the grave with the intention of arriving
safely in an attractive and well preserved body, but rather to skid in
sideways, chocolate in one hand, martini in the other, body thoroughly
used up, totally worn out and screaming "WOO HOO what a ride!!"
 
P

Pascal Gallois

You haven't yet revealed which options and rounding modes you use. I guess
you're saying that the defaults of gcc are buggy compared with the defaults
of an unspecified Microsoft compiler. gcc defaults were determined by
relatively open consensus among developers who want support for long
double, while your Microsoft compiler is unlikely to support long double.
You don't take any responsibility for setting equivalent options; for
example gcc -march=pentium4 -mfpmath=sse
or setting 53-bit precision mode when running the gcc x87 version, when that
is the default run-time setting for 32-bit MSVC.

My conclusions were premature and I apologize for that. I posted my
compiler switches in my original post (-Wall -pedantic -std=c99). The
suggested -fno-fast-math from Ben Pfaff did not change the result and my
response went to gnu.gcc.help since that was set as the followup. AFAIK it
was never suggested to use -march=pentium4 -mfpmath=sse. With those
two switches I indeed get the expected result of 20045.

I understand from Dik T. Winter's reply that excess precision in
expressions is platform dependent. However I find this specification
unfortunate since it produces different results for i and j (if -march !=
pentium4 for example) in:

double m = 200.45;
double n = m * 100;
int i = m * 100;
int j = n;

- Pascal
 
T

Tim Prince

Pascal said:
The
suggested -fno-fast-math from Ben Pfaff did not change the result and my
response went to gnu.gcc.help since that was set as the followup. AFAIK it
was never suggested to use -march=pentium4 -mfpmath=sse. With those
two switches I indeed get the expected result of 20045.

I understand from Dik T. Winter's reply that excess precision in
expressions is platform dependent. However I find this specification
unfortunate since it produces different results for i and j (if -march !=
pentium4 for example) in:

double m = 200.45;
double n = m * 100;
int i = m * 100;
int j = n;

- Pascal
gcc never sets -ffast-math unless given explicitly on the command line. As
you found out, it should not have any effect in this example.
In the long run, however, you can easily cause yourself and your colleagues
a lot of grief by ignoring the distinction between decimal and binary
arithmetic.
 

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

Similar Threads


Members online

Forum statistics

Threads
474,164
Messages
2,570,898
Members
47,439
Latest member
shasuze

Latest Threads

Top