Strange behaviour of tiny double numbers

S

soeren

Hello,

two days ago I stumbled across a very strange problem that came up when
we were printing tiny double numbers as strings and trying to read them
on another place. This is part of an object serialisation framework
that cannot be done in binary format currently, so please no comments
about this ,-))

It took quite some time to shrink down the problem but it looks like
that C++ does not behave well in regards to very tiny numbers.
The <limits> header of the STL defines various templates that should
return amonst other things the tiniest negative double number that C++
can handle. The line

cout << numeric_limits<double>::min() << endl;

returns "2.22507e-308". So I would expect that every number closer to
zero (but still negative) would be treated as zero?

Please have a look at the following code snippet (insert it into a
"main" to run it on your compiler):


ostringstream output;
istringstream input;

// Regarding the limits this should assign zero to foobar, but it
doesn't look like that ?!?
double foobar = -2.345e-315;

// Simply put three numbers on the stream
output << 10 << " ";
output << 2.12345 << " ";
output << foobar << " ";
output << 10 << endl;

// Print out the resulting stream, you should see the above three
numbers
cout << output.str() << endl;

// Generate input stream from the output stream
input.str(output.str());

int int1=0, int2=0;
double double1=0, double2=0;

// For every read action print out the stream state
input >> int1;
cout << input.good() << endl;
input >> double1;
cout << input.good() << endl;

// Here comes the read of the "wrong" double
input >> double2;
cout << input.good() << endl;
input >> int2;
cout << input.good() << endl << endl;

// Now print out the numbers from the stream
cout << int1 << endl;
cout << double1 << endl;
cout << double2 << endl;
cout << int2 << endl;

Although I'm doing this example with three numbers only the 3rd number
is of interest as this number is a double value that should be zero
after the assignment from my understanding.

Various things come up when running the code that I cannot explain
currently:

1) The assignment obviously does not assign zero. Proof: Printing the
resulting stream yields "-2.345e-315" as the third number (checked on
..NET 2003 C++ and g++ 4.0/Linux). Why that?

2) The number is printed to the stream as "-2.345e-315". But regarding
<limits> this number should impossible to handle?

3) The accompanying read action results in different behavious
.NET 2003: input.good() toggles to "false" after read, the read
number "double2" is not read, subsequent reads fail.
g++ 4.0/Linux: The number is read, the stream state is "true",
subsequent numbers are read.

Why does this code work on g++? This should not run as the numeric
limit produces exactly the same number as with .NET2003

#3 currently produces the biggest headaches as in our current framework
this very tiny numbers are produces on linux servers but must be read
on windows clients. Following the above tests this cannot work.

So beside the basic question "What is the real limit of a double number
and why does this not work in c++?" there seems to be some platform
dependante implementations; what is the reason for this?


Thanks a lot for shedding some light in this issue,
Soeren Gerlach
 
M

Markus Schoder

It took quite some time to shrink down the problem but it looks like
that C++ does not behave well in regards to very tiny numbers.
The <limits> header of the STL defines various templates that should
return amonst other things the tiniest negative double number that C++
can handle. The line

cout << numeric_limits<double>::min() << endl;

returns "2.22507e-308". So I would expect that every number closer to
zero (but still negative) would be treated as zero?

No. numeric_limits<double>::min() returns the minimum _normalized_ value.
There can be denormalized values which are closer to zero. This is very
likely the case here. You should try denorm_min() instead (gives me
4.94066e-324).

Even then smaller values may still be rounded up to denorm_min() (e.g.
2.5e-324 is rounded to denorm_min() but 2.4e-324 is rounded to 0).
 
K

Kaz Kylheku

cout << numeric_limits<double>::min() << endl;

returns "2.22507e-308". So I would expect that every number closer to
zero (but still negative) would be treated as zero?

Note that this is (a printed approximation) to the smallest /normal/
number.

The IEEE 754 double precision format allows for smaller magnitudes than
that, in the form of denormal numbers.

Note that the numeric_limits system has a denorm_min() function.
double foobar = -2.345e-315;

So here, you are in the territory of denormals. Denormals have the
smallest possible exponent, but the mantissa is interpreted as not
having the implicit leading 1.

You can think of zero as being a special case of the the denormals.

Or, conversely, as denormals being extensions to zero where the
mantissa bits are used to encode tiny values near to zero.

The smallest positive denormal (or, in alternate terminology,
subnormal) is approximately 4.94E-324. As you can see, this is quite a
bit smaller still than the denormal you are representing in your
program.
 
D

Duane Hebert

It took quite some time to shrink down the problem but it looks like
that C++ does not behave well in regards to very tiny numbers.
The <limits> header of the STL defines various templates that should
return amonst other things the tiniest negative double number that C++
can handle. The line

cout << numeric_limits<double>::min() << endl;

returns "2.22507e-308". So I would expect that every number closer to
zero (but still negative) would be treated as zero?

numeric_limits<double>::min() returns the smallest
positive number representable. Maybe I don't understand
what you mean by "tiniest" but at any rate, min() returns
a positive number. albeit a very small one <g>
 
R

Robert Mabee

3) The accompanying read action results in different behavious
.NET 2003: input.good() toggles to "false" after read, the read
number "double2" is not read, subsequent reads fail.
g++ 4.0/Linux: The number is read, the stream state is "true",
subsequent numbers are read.

#3 currently produces the biggest headaches as in our current framework
this very tiny numbers are produces on linux servers but must be read
on windows clients. Following the above tests this cannot work.

Sounds like you need an immediate work-around for the Windows bug.
(This is unquestionably a bug. A number that it can write should not
kill a read.)

I suggest reading the number as a string, then converting to double in
a separate step so even if that has a bug, you can continue with a
close substitute value (such as zero). It's fairly unlikely that you
need to pass these values accurately since they are in the range of
denormalized numbers, where gradual underflow causes loss of precision.
 
S

soeren

Hi all,

okay, with this mailthread I learned a lot about the denormal numbers
,-))

I tested it today on two other environments (linux 2.4 + icc 8.0 /
Solaris 5.8 + gcc 2.95) and the test program doesn't fail on these
environmets as it did not fail on Linux 2.4 + gcc 4). Obviously this
seems to be MSVC related so nothing for this newsgroup.


Thanks a lot for your help,
Soeren Gerlach
 

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,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top