float to string to float, with first float == second float

C

Carsten Fuchs

Hi Pascal,
I assume that knowing your specific machine representation of floating
point, you could do some bit mungling in C++ to implement similar
functions.

Well, sure, and thanks for your post.
"Problem" is that this is somewhere in the middle between a decimal number representation and
printing the bytes of a float as a hex value (e.g. by interpreting the bytes of the float as the
bytes of an unsigned int of the same size, then writing that).

Best regards,
Carsten
 
R

Rune Allnor

Dear Rune,

thank you very much for your reply!






I understand this, but all I'm looking for is a serialization of "a" that, when converted back to a
float, yields the same bits for "a" again.

That is, I don't care about the fact that
        float a=0.3;
doesn't assign the exact decimal value 0.3 to "a". Due to the inherent limits of floating point
representations, exactly as you pointed out, the "true" value of "a" will *not* be 0.3.

The problem is to come up with a pattern that is guaranteed
to reproduce the original binary pattern:

1) Approximations might occur when the value is first loaded
2) Approximations might occur when the value is serialized
3) Approximations might occur when the value is de-serialized

The problem is steps 2) and 3): Unless you can guarantee
that either

a) No approximations occur in steps 2) and 3)
b) The approximation in 3) exactly cancels the
approximation introduced in 2)

you can not guarantee that you end up with the same bit
pattern as you started out with.
But again, this is not what I'm after.
Instead, I'm looking for
        float a2 = unserialize(serialize(a));
such that a2==a (fully intentionally using the == comparison with floats).

Understanding the term 'serialize' as 'store binary data
on text-based format', I would have dropped the requirement
for human readability and stored the HEX pattern of the float.
Something like

#include<iomanip>

float a = 0.3;
ss << std::hex() << a << std::endl;
ss >> std::hex() >> a;

should go a long way to meet your requirements - EXCEPT
for the 'human readability' issue.

Rune
 
C

Carsten Fuchs

Rune said:
As I understand the question, the OP wants to break out
of those limitations

Not at all!
Please see my other post for details.

I just want to (or "hope to") exploit that fact(?) that

std::stringstream ss;

ss << "0.3";

float a=0.3;
float b;
ss >> b;

yields a==b.

:)

Best regards,
Carsten
 
S

SG

As I understand the question, the OP wants to break out
of those limitations: Exact conversions between base-10
and base-2 numbers, eliminating approximation errors etc.

The OP wants a float->string->float or double->string->double
roundtrip to be "perfect" where "string" refers to a decimal ASCII
representation.

The fact that you can't exactly represent every decimal number in
binary with a finite string is of little interest. You CAN represent
every floating point number exactly with a finite string of decimal
digits -- assuming your computer uses base-2, base-10 or base-16.

Even if you deal with different representations where the bases have
no common factors (suppose you want an ASCII representation in base
13) you can make the float->string mapping surjective by using enough
digits.

Cheers,
SG
 
C

Carsten Fuchs

Rune said:
1) Approximations might occur when the value is first loaded
2) Approximations might occur when the value is serialized
3) Approximations might occur when the value is de-serialized

The problem is steps 2) and 3): Unless you can guarantee
that either

a) No approximations occur in steps 2) and 3)
b) The approximation in 3) exactly cancels the
approximation introduced in 2)

Yes, agreed, one or both of a) and b is/are the implied assumption.

I also should have mentioned in my original post where I got the idea for all this from:
http://www.open-std.org/JTC1/sc22/wg21/docs/papers/2006/n2005.pdf

So if I understand this correctly, for step 2) there is a certain number of digits (max_digits10 in
the paper) that express the original value in "equal or higher precision" than was in the original
float.
This is turn gives argument for the correctness of step 3), because the number of digits was big
enough for yielding more precision than can be stored in the float.

When rigorously checked mathematically and wrt. the implementation details, I agree that there is
probably *still* room for problems with steps 2) and 3), but principally, both goals of human
readability and data integrity should be available based on the arguments in the above paper.
you can not guarantee that you end up with the same bit
pattern as you started out with.
Yes...

Understanding the term 'serialize' as 'store binary data
on text-based format', I would have dropped the requirement
for human readability and stored the HEX pattern of the float.

Yes, I'm aware of that, but unfortunately, human readability is a core requirement.

Thanks and best regards,
Carsten
 
C

Carsten Fuchs

Hi Fred,

thanks for your reply!

Fred said:
I think in principle your approach should work.
The limitation to prec<10 may be to small.
In the first place it depends on the platform you run the program on.
Secondly, can you prove that with 10 decimal digits you
are always close enough to the binary value of the float, so that
it can be converted back correctly?

Ah, I'm very sorry to you and everyone else that I didn't mention in my original post where I got
the idea from:
http://www.open-std.org/JTC1/sc22/wg21/docs/papers/2006/n2005.pdf

So indeed, the prec<10 is specific to IEEE single precision floats and should have been
prec<std::numeric_limits<float>::max_digits10

Best regards,
Carsten
 
P

Pascal J. Bourguignon

Rune Allnor said:
...so you just reproduce the floating-point represntation
usually implemented on the binary level, as ASCII?

What would be the point?

True, you can get an exact representation PROVIDED you use
a base-2 representation. As I demonstrated elsewhere, exactness
is lost due to incompatibilities of decimal and binary
representations of numbers.

Not with the right representation.

The base-2 numbers would not make much sense to the human
reader, though. Once that advantage has been lost, one can just
as well store the HEX representation of the binary pattern.

Read again the above formula.
There's no representation here.
There are only integer numbers and products thereof.

If you can represent integer numbers without loss of precision (I
don't care what representation system you use for these integers),
then you can represent your floating point numbers in a readable form
without loss of precision.

The only little inconvenient is that you will have to compute a
multiplication to get the more usual *approximate* value of those
floating point and to conver them back into internal floating point
numbers, but that you always have to work to archieve.

The point of this representation is that it fulfills all the
requirements of the OP: the naive human reader can easily compute the
product if he needs an approximate value. Naive human readers would
have more problems with an hexadecimal representation, than with this
product form.
 
P

Pascal J. Bourguignon

Carsten Fuchs said:
Hi Pascal,


Well, sure, and thanks for your post.
"Problem" is that this is somewhere in the middle between a decimal
number representation and printing the bytes of a float as a hex value
(e.g. by interpreting the bytes of the float as the bytes of an
unsigned int of the same size, then writing that).

I see a big difference: a naive human reader can compute the product
and get the real value easily. (The notation embeds the algorithm to
decode it, understandable by everybody).

Compare:

My height is (1*15938355*2^-23) meters.
vs.
My height is 3FF33333 meters.
 
V

Victor Bazarov

Carsten said:
Not at all!
Please see my other post for details.

I just want to (or "hope to") exploit that fact(?) that

std::stringstream ss;

ss << "0.3";

float a=0.3;
float b;
ss >> b;

yields a==b.

:)

"Don't try to bend the spoon. It's impossible."

There is no such "fact". The conversion from the literal "0.3" in the
definition of 'a' is not necessarily going through the same algorithm as
the conversion when reading 'b' from 'ss'. Besides, the floating point
literal in the code you have here (0.3) has the type "double", so some
adjustment will likely happen when converting it to 'float a'...

Just so you know :)

V
 
F

Francesco S. Carta

Dear Francesco,

thanks for your reply!


Why?
I'm not trying to deal with round-off errors here, but to serialize a float with enough digits to be
able to restore the exact same value from the string later.
Can you suggest a working alternative?
Also please seehttp://www.open-std.org/JTC1/sc22/wg21/docs/papers/2006/n2005.pdffor some background.


Sure, the check for failure is missing, but besides that, what is the problem?
What's your suggested better alternative?


Sorry, but the thread you're referring to is about file formats and parsing.
My question is about serializing float numbers in a way so that unserializing them yields the
original value.


Yes, thanks, I even own (and have read) the books paper edition.

As I have anticipated in my reply to Fred, please accept my apologies
for having made such assumptions about your knowledge of the issue.

Although your post was open to different interpretations under that
aspect, I was wrong picking one of them over the others - I just
followed the first impression without verifying it, not exactly my
custom, but I happen to.

I'll try to be more careful for the times to come :)

Cheers,
Francesco
 
V

Victor Bazarov

Pascal said:
I see a big difference: a naive human reader can compute the product
and get the real value easily. (The notation embeds the algorithm to
decode it, understandable by everybody).

Compare:

My height is (1*15938355*2^-23) meters.
vs.
My height is 3FF33333 meters.

Are you being facetious? It is advisable to add a smiley in that case.

"Humanly readable" never meant the possibility of using a calculator to
understand what's being read, nor does it presume superhuman ability to
perform calculations in one's head.

V
 
C

Carsten Fuchs

Francesco,

thanks for the clarification!
I should have made my point clearer in my initial post as well. :)

Thanks and best,
Carsten
 
R

Rune Allnor

Yes, agreed, one or both of a) and b is/are the implied assumption.

I also should have mentioned in my original post where I got the idea for all this from:http://www.open-std.org/JTC1/sc22/wg21/docs/papers/2006/n2005.pdf

So if I understand this correctly, for step 2) there is a certain number of digits (max_digits10 in
the paper) that express the original value in "equal or higher precision" than was in the original
float.

OK, so let's play with numbers:

The number D of digits needed to represent a number N in
base 10 notation is

D = ceil(log10(N)).

For a single-precision floating-point number, there are some
20 bits in the mantissa, so the number of digits D to represent
the mantissa becomes

D = ceil(log10(2^20))
= ceil(20*log10(2))
> 20*floor(log10(2))
> 20*3
> 60.

So one needs *at*least* 60 digits to represent the number.
Of course, a similar argument is used for the exponent.

Compare these >60 digits to the 6-8 significant digits of
the floating-point number: 10% of the digits in the exact
representation represent everything except the last bit,
while the remaining 90% of the digits are spent on getting
that last bit right.

Of course, most parsers stop parsing after the remainder
of the parsed literal becomes less than the relative
accuracy of the number, so you would have to write a
dedicated parser for the stream.

So to summarize:

1) You expanded the required space by at least a factor
10, compared to the binary represnetation.
2) 90% of the stored information pertains the last
bit of the binary format
3) You need to write a dedicated parser for the result

....and all of this to avoid that some person would need
to write something like

stringstream ss;
float a = 1.23;
ss << std::hex() << a;
ss >> std::hex() >> a;
std::cout << std::dec() << a;

to inspect the contents of the stream.

Rune
 
P

Pascal J. Bourguignon

Victor Bazarov said:
Are you being facetious? It is advisable to add a smiley in that case.

"Humanly readable" never meant the possibility of using a calculator
to understand what's being read, nor does it presume superhuman
ability to perform calculations in one's head.

That must be my math and programmer minds that prevent me to see the
what's wrong with:

15938355*2^-23
vs.
1912*10^-3
 
V

Victor Bazarov

Pascal said:
That must be my math and programmer minds

How many have you got? I always thought that a human has only one mind.
Keep that in minds when developing something that needs to be humanly
readable. said:
> that prevent me to see the
what's wrong with:

15938355*2^-23
vs.
1912*10^-3

For one, it's smaller.

V
 
V

Victor Bazarov

Rune said:
OK, so let's play with numbers:

The number D of digits needed to represent a number N in
base 10 notation is

D = ceil(log10(N)).

For a single-precision floating-point number, there are some
20 bits in the mantissa, so the number of digits D to represent
the mantissa becomes

D = ceil(log10(2^20))
= ceil(20*log10(2))

log10(2) == 0.3

Should be 6
So one needs *at*least* 60 digits to represent the number.

Six, actually. Check your math.
Of course, a similar argument is used for the exponent.

[..snip..]

V
 
S

SG

Rune said:
D = ceil(log10(2^20))
  = ceil(20*log10(2))
  > 20*floor(log10(2))
  > 20*3
  > 60.

So one needs *at*least* 60 digits to represent the number.

log10(2) is not above 3, it's about 0.3 ==> about 7 digits.
So to summarize:

1) You expanded the required space by at least a factor
   10, compared to the binary represnetation.

More like 2.4 (log2(10)/8) if you compare "binary binary" to "decimal
ASCII" (assuming 8 bit characters)

Rune, please pay a little more attention to what has been written here
already.

Cheers,
SG
 
C

Carsten Fuchs

Pete said:
You can have both. The seminal papers on this subject are:

Steele, Guy M. & Jon L. White, "How to Print Floating-point
Numbers Accurately", Proceedings of the ACM SIGPLAN '90
Conference on Programming Language Design and Implementation.
White Plains, New York, June 20-22, 1990.

Clinger, William D., "How to Read Floating-point Numbers
Accurately", Proceedings of the ACM SIGPLAN '90 Conference on
Programming Language Design and Implementation. White Plains,
New York, June 20-22, 1990.

And there's a followup:

Gay, David M., "Correctly Rounded Binary-Decimal and
Decimal-Binary Conversions", AT&T Bell Laboratories
Numerical Analysis Manuscript 90-10, November 30, 1990.

with an implementation that's available from netlib.

Many thanks, Pete!
I'll check these out.

Best regards,
Carsten
 
H

Hans Bos

"Pete Becker" <[email protected]> schreef in bericht
....
with an implementation that's available from netlib.

But the cost is sometimes high: it's not easy to implement, and you
occasionally have to break into unlimited precision integer math to get
the right answer.

The basic strategy is to write enough decimal digits to distinguish the
value being written from its two (binary) neighbors.

I vaguely recall that the C standard requires this round-trip behavior,
but I don't have a reference handy.


I thought it was only required since IEEE 754-2008. In the previous standard
IEEE 754-1985 it was only required for a limited range of values.

I also thought that IEEE 754 support is optional in C99.

At least microsoft visual c++ 2008 doesn't round all values correctly.
E.g. 99999999999999983222784 = 5960464477539061.5 * 2**24
is converted to 5960464477539061 * 2**24.
According to the IEEE rounding rules it should round to even,
so to 5960464477539062 * 2**24.

Hans.
 
J

James Kanze

I've seen algorithms for this in the past. (Perhaps "How to
print floating point numbers accurately", by Guy Steele, but I'm
not sure.)

You don't always need six digits---for the value 1.0, one digit
is enough. And if the floating point format is IEEE, seven
digits are always enough.

There are certainly more efficient means, but the ones I know
require reimplementing the conversion function itself, rather
than using the one present in the library.
There are a couple of problems with your code.
First, you should never compare floats or doubles for equality
that way.

Why not? And what should he do instead? He wants to know if
the two values are exactly equal.
Secondarily, extracting (>>) non-character data (integers,
doubles and so on) from streams can hang.

Since when? A correct implementation of stringstream will never
hang. (A correct implementation of an fstream can hang if the
"file" is actually a device which can hang, but that's beyond
the power of the library to control.)
 

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,767
Messages
2,569,570
Members
45,045
Latest member
DRCM

Latest Threads

Top