Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Serialize a double for a binary protocol

22 views
Skip to first unread message

Fede

unread,
Feb 27, 2006, 11:11:31 AM2/27/06
to
Hi,

I'm implementing a binary protocol and I need to serialize a C++ double
type value. I know that some compilers implement doubles with IEEE 754
format, but I need to ensure the portability of my code. The question
is if do you know any library or some other way to convert a double
into a stream of bytes encoded with the IEEE format?

Thanks,

Federico


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Harper Shelby

unread,
Feb 28, 2006, 3:58:17 AM2/28/06
to
Fede wrote:

> Hi,
>
> I'm implementing a binary protocol and I need to serialize a C++ double
> type value. I know that some compilers implement doubles with IEEE 754
> format, but I need to ensure the portability of my code. The question
> is if do you know any library or some other way to convert a double
> into a stream of bytes encoded with the IEEE format?
>
> Thanks,
>
> Federico

Seems to me that a custom stream insertion operator would be the best way to
guarantee that the bits hit the wire in the exact order needed. I had to do
something similar in emulating a structure written to a file in Visual
Basic from C++ code. It's not terribly difficult - I used simple shifting
and masking to get things to go out in the required format.

Harper

kanze

unread,
Feb 28, 2006, 9:47:07 AM2/28/06
to
Fede wrote:

> I'm implementing a binary protocol and I need to serialize a
> C++ double type value. I know that some compilers implement
> doubles with IEEE 754 format, but I need to ensure the
> portability of my code. The question is if do you know any
> library or some other way to convert a double into a stream of
> bytes encoded with the IEEE format?

It's not trivial, but you can play games with frexp() and
ldexp(). Basically, frexp() is used to obtain the exponent, and
ldexp() to convert the mantissa bits left after frexp() into an
integral value, which can be converted to an integer of
sufficient size. Having got the actual values for the three
fields (I suppose you're capable of figuring out the sign
yourself), you can or and shift to put them where ever they are
wanted in the target format. Something like the following
should work (for big-endian IEEE):

oxdrstream&
oxdrstream::operator<<(
float source )
{
BytePutter dest( *this ) ;
bool isNeg = source < 0 ;
if ( isNeg ) {
source = - source ;
}
int exp ;
if ( source == 0.0 ) {
exp = 0 ;
} else {
source = ldexp( frexp( source, &exp ), 24 ) ;
exp += 126 ;
}
uint32_t mant = source ;
dest.put( (isNeg ? 0x80 : 0x00) | exp >> 1 ) ;
dest.put( ((exp << 7) & 0x80) | ((mant >> 16) & 0x7F) ) ;
dest.put( mant >> 8 ) ;
dest.put( mant ) ;
return *this ;
}

oxdrstream&
oxdrstream::operator<<(
double source )
{
BytePutter dest( *this ) ;
bool isNeg = source < 0 ;
if ( isNeg ) {
source = - source ;
}
int exp ;
if ( source == 0.0 ) {
exp = 0 ;
} else {
source = ldexp( frexp( source, &exp ), 53 ) ;
exp += 1022 ;
}
uint64_t mant = static_cast< GB_uint64_t >( value )
;
dest.put( (isNeg ? 0x80 : 0x00) | exp >> 4 ) ;
dest.put( ((exp << 4) & 0xF0) | ((mant >> 48) & 0x0F) ) ;
dest.put( mant >> 40 ) ;
dest.put( mant >> 32 ) ;
dest.put( mant >> 24 ) ;
dest.put( mant >> 16 ) ;
dest.put( mant >> 8 ) ;
dest.put( mant ) ;
return *this ;
}

Note that this is something I started a while back, but have not
finished; the code has NOT been tested, and obviously, doesn't
handle special values like denormalized, Inf and NaN -- which
are very hard to handle portably, because they aren't present in
all formats. (BytePutter is a very simple class which simply
ensures that errors are set correctly when they occur, and that
no further output is attempted once an error has occured.)

Reading is the reverse -- in my case, I actually read into an
unsigned int of sufficient size first (handling byte order
there), then pick the fields a part and reassemble them using
ldexp, e.g.:

ixdrstream&
ixdrstream::operator>>(
GB_uint32_t& dest )
{
ByteGetter source( *this ) ;
GB_uint32_t tmp = source.get() << 24 ;
tmp |= source.get() << 16 ;
tmp |= source.get() << 8 ;
tmp |= source.get() ;
if ( *this ) {
dest = tmp ;
}
return *this ;
}

ixdrstream&
ixdrstream::operator>>(
GB_int64_t& dest )
{
GB_uint64_t tmp ;
operator>>( tmp ) ;
if ( *this ) {
dest = tmp ;
}
return *this ;
}

ixdrstream&
ixdrstream::operator>>(
GB_uint64_t& dest )
{
ByteGetter source( *this ) ;
GB_uint64_t tmp = source.get() << 56 ;
tmp |= source.get() << 48 ;
tmp |= source.get() << 40 ;
tmp |= source.get() << 32 ;
tmp |= source.get() << 24 ;
tmp |= source.get() << 16 ;
tmp |= source.get() << 8 ;
tmp |= source.get() ;
return *this ;
}

ixdrstream&
ixdrstream::operator>>(
float& dest )
{
GB_uint32_t tmp ;
operator>>( tmp ) ;
if ( *this ) {
float f = 0.0 ;
if ( (tmp & 0x7FFFFFFF) != 0 ) {
f = ldexp( ((tmp & 0x007FFFFF) | 0x00800000),
(int)((tmp & 0x7F800000) >> 23) - 126 - 24 )
;
}
if ( (tmp & 0x80000000) != 0 ) {
f = -f ;
}
dest = f ;
}
return *this ;
}

ixdrstream&
ixdrstream::operator>>(
double& dest )
{
GB_uint64_t tmp ;
operator>>( tmp ) ;
if ( *this ) {
double f = 0.0 ;
if ( (tmp & 0x7FFFFFFFFFFFFFFF) != 0 ) {
f = ldexp( ((tmp & 0x000FFFFFFFFFFFFF) |
0x0010000000000000),
(int)((tmp & 0x7FF0000000000000) >> 52) -
1022 - 53 ) ;
}
if ( (tmp & 0x8000000000000000) != 0 ) {
f = -f ;
}
dest = f ;
}
return *this ;
}

(It's my intent to add this component to my library, if I can
ever find the time to finish it, and write the necessary tests.
And the library will be available on the Internet as soon as my
Internet access at home starts working again.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Daniel K. O.

unread,
Feb 28, 2006, 9:51:00 AM2/28/06
to
Fede escreveu:

> I'm implementing a binary protocol and I need to serialize a C++ double
> type value.


This is the code I currently use in my code. See if it fit your needs.
Also, I would be glad if the people here could comment on the possible
flaws of the conversion functions.


(be aware that it's meant to work only on platforms with FLT_RADIX==2, I
don't know if it works on other platforms)


Here's the header (serial_fp.h):

---
#ifndef SERIAL_FP_H
#define SERIAL_FP_H


struct single_precision {
typedef long mantissa_t;

mantissa_t mantissa;
short exp;
bool negative;

static const int bias = 127;
static const int non_finite_exp = 255;
static const int mantissa_size = 23;
};

struct double_precision {
typedef long long mantissa_t; // let's pretend we have long long

mantissa_t mantissa;
short exp;
bool negative;

static const int bias = 1023;
static const int non_finite_exp = 2047;
static const int mantissa_size = 52;
};


single_precision to_ieee754_single(float);
double_precision to_ieee754_double(float);

single_precision to_ieee754_single(double);
double_precision to_ieee754_double(double);


float float_from_ieee754(const single_precision& pf);
float float_from_ieee754(const double_precision& pf);

double double_from_ieee754(const single_precision& pf);
double double_from_ieee754(const double_precision& pf);

...

#endif
---

The single_precision and double_precision classes represent the IEEE 754
single and double precision types. Note how I'm cheating, using a C99
type and macros. :P


The code to create a {single|double}_precision type, in serial_fp.cpp:


---
#include <limits>
#include <math.h>
#include "serial_fp.h"


template<class T, class PT>
PT to_ieee754(T f)
{
typedef PT precision_type;
typedef typename precision_type::mantissa_t mantissa_t;

precision_type pf;

pf.negative = signbit(f);

if (!isfinite(f)) {
if (isinf(f)) {
pf.exp = precision_type::non_finite_exp;
pf.mantissa = 0;
return pf;
}

if (isnan(f)) {
pf.exp = precision_type::non_finite_exp;
pf.mantissa = mantissa_t(1) <<
(precision_type::mantissa_size-1);
return pf;
}
}

if (f == 0) {
pf.mantissa = 0;
pf.exp = 0;
return pf;
}

if (pf.negative)
f = -f;

pf.exp = pf.bias;

while (f < 1) {
--pf.exp;
f *= 2;
}
while (f >= 2) {
++pf.exp;
f /= 2;
}

/* now we have 1 <= f < 2b */
mantissa_t big_positive_int = mantissa_t(1) <<
precision_type::mantissa_size;

pf.mantissa = static_cast<mantissa_t>(f * big_positive_int);

if (pf.exp<1) {
// number is not normalized?
pf.mantissa >>= 1-pf.exp;
pf.exp = 0;
} else {
pf.mantissa &= (mantissa_t(1) <<
precision_type::mantissa_size)-1;
}

return pf;
}


single_precision
to_ieee754_single(float f)
{
return to_ieee754<float, single_precision>(f);
}

double_precision
to_ieee754_double(float f)
{
return to_ieee754<float, double_precision>(f);
}


single_precision
to_ieee754_single(double d)
{
return to_ieee754<double, single_precision>(d);
}

double_precision
to_ieee754_double(double d)
{
return to_ieee754<double, double_precision>(d);
}


...
---

And the code to convert back to float and double:


---
...

template<class T, class PT>
T from_ieee754(const PT& pf)
{
typedef PT precision_type;
typedef typename precision_type::mantissa_t mantissa_t;

T f;
mantissa_t mantissa = pf.mantissa;

if (pf.exp==precision_type::non_finite_exp) {
if (!mantissa) {
f = std::numeric_limits<T>::infinity();
return pf.negative?-f:f;
} else {
// NaN
if (std::numeric_limits<T>::has_quiet_NaN)
return std::numeric_limits<T>::quiet_NaN();
else
return
std::numeric_limits<T>::signaling_NaN();
}
}

short texp = pf.exp;

if (pf.exp) {
// if normalized, restore the implicit '1'
mantissa |= mantissa_t(1) << precision_type::mantissa_size;
} else
++texp;

f = pf.negative ? -mantissa : mantissa;

texp -= precision_type::mantissa_size;
texp -= precision_type::bias;

while (texp > 0) {
f *= 2;
--texp;
}
while (texp < 0) {
f /= 2;
++texp;
}

return f;
}

float
float_from_ieee754(const single_precision& pf)
{
return from_ieee754<float>(pf);
}

float
float_from_ieee754(const double_precision& pf)
{
return from_ieee754<float>(pf);
}

double
double_from_ieee754(const single_precision& pf)
{
return from_ieee754<double>(pf);
}

double
double_from_ieee754(const double_precision& pf)
{
return from_ieee754<double>(pf);
}

...
---

Once you have a single_precision or double_precision type, it's easy to
serialize it, as it's just a bunch of integers. e.g.:

---
std::string
serialize_ieee754(const single_precision& pf)
{
std::string result;
char c;


// LSB

// byte 0, 7-0
c = pf.mantissa & 0xff;
result.push_back(c);

// byte 1, 15-8
c = (pf.mantissa >> 8) & 0xff;
result.push_back(c);

// byte 2, 23-16
c = (pf.mantissa >> 16) & 0x7f;
c |= (pf.exp & 1) << 7;
result.push_back(c);

// byte 3, 31-24
c = pf.exp >> 1 & 0x7f;
c |= pf.negative ? 0x80 : 0;
result.push_back(c);

return result;
}


std::string
serialize_ieee754(const double_precision& pf)
{
std::string result;
char c;


// LSB

// byte 0, 7-0
c = pf.mantissa & 0xff;
result.push_back(c);

// byte 1, 15-8
c = (pf.mantissa >> 8) & 0xff;
result.push_back(c);

// byte 2, 23-16
c = (pf.mantissa >> 16) & 0xff;
result.push_back(c);

// byte 3, 31-24
c = (pf.mantissa >> 24) & 0xff;
result.push_back(c);

// byte 4, 39-32
c = (pf.mantissa >> 32) & 0xff;
result.push_back(c);

// byte 5, 47-40
c = (pf.mantissa >> 40) & 0xff;
result.push_back(c);

// byte 6, 55-48
c = (pf.exp << 4) & 0xf0;
c |= (pf.mantissa >> 48) & 0x0f;
result.push_back(c);

// byte 7, 63-56
c = pf.negative ? 0x80 : 0;
c |= (pf.exp >> 4) & 0x7f;
result.push_back(c);

return result;
}
---
(de-serializing is omitted for brevity, but it's pretty straightforward)

---
Daniel K. O.

Greg Herlihy

unread,
Mar 1, 2006, 8:13:30 AM3/1/06
to

Fede wrote:
> Hi,
>
> I'm implementing a binary protocol and I need to serialize a C++ double
> type value. I know that some compilers implement doubles with IEEE 754
> format, but I need to ensure the portability of my code. The question
> is if do you know any library or some other way to convert a double
> into a stream of bytes encoded with the IEEE format?

Yes, use an ASN.1 compiler to generate the code needed to serialize or
unserialize the fields of a standard C struct. There are any number of
open source ASN.1 compilers around (here's one for example:
http://lionet.info/asn1c/ ). Not only will you probably save yourself a
lot of work, your portability goals will also have been met: after all,
it can easily be argued that given two programs, the more portable
program is the one that can communicate with programs other than
itself.

Greg

kanze

unread,
Mar 1, 2006, 8:21:48 AM3/1/06
to

> single_precision to_ieee754_single(float);
> double_precision to_ieee754_double(float);

Just curious, but shouldn't the types single_precision and
double_precision be internal. If serialising, I need a sequence
of bytes, not a structure. I'd imagine an interface more along
the lines of:

template< typename OutIter >
void to_ieee754_single( OutIter dest, float ) ;
template< typename OutIter >
void to_ieee754_double( OutIter dest, double ) ;

(I presume that the float parameter for the second function is a
typo, and that you meant double:-).)

> single_precision to_ieee754_single(double);
> double_precision to_ieee754_double(double);

Or was it? I'm not sure why you need four functions here.
Given the way floating point works in C++, the last two are
largely sufficient.

> float float_from_ieee754(const single_precision& pf);
> float float_from_ieee754(const double_precision& pf);

> double double_from_ieee754(const single_precision& pf);
> double double_from_ieee754(const double_precision& pf);

Ditto.

> ...

> #endif
> ---

> The single_precision and double_precision classes represent
> the IEEE 754 single and double precision types.

Except that they don't. And they aren't serialized; you still
have to serialize them. If the protocol requires IEEE in a
specific encoding scheme (to use Unicode terminology), then you
still have to insert the information where it belongs -- IEEE
float says that the mantissa will occupy exacty 23 bits, for
example, and not whatever long happens to be on your machine.

Getting the information in your structures is a necessary first
step in encoding, but it is only the first step -- you still
have to combine it into an IEEE value, according to the encoding
scheme used in the protocol. (XDR, for example, can be thought
of as a 32 bit unsigned int, written out high byte first, with
the fields of the IEEE value arranged in the order sign,
exponant, mantissa, from high bit to low.)

> Note how I'm cheating, using a C99 type and macros. :P

It's possible to convert to a floating point representation
without having an integral type large enough to hold the
mantissa, but it's a bit more complicated. I took the same
short cut in my own code (which uses the C99 types uint32_t and
uint64_t).

> The code to create a {single|double}_precision type, in serial_fp.cpp:

> ---
> #include <limits>
> #include <math.h>
> #include "serial_fp.h"

> template<class T, class PT>
> PT to_ieee754(T f)
> {
> typedef PT precision_type;
> typedef typename precision_type::mantissa_t mantissa_t;

> precision_type pf;

> pf.negative = signbit(f);

No need for a special function here:
pf.negative = f < 0.0 ;
does the trick.

> if (!isfinite(f)) {
> if (isinf(f)) {
> pf.exp = precision_type::non_finite_exp;
> pf.mantissa = 0;
> return pf;
> }

> if (isnan(f)) {
> pf.exp = precision_type::non_finite_exp;
> pf.mantissa = mantissa_t(1) <<
> (precision_type::mantissa_size-1);
> return pf;
> }
> }

I've not tried to handle the special values yet. A priori, I
think I'd go for something more on the lines of
switch ( fpclassify( f ) ) {...}
With a default for the unlike case that the implementation
supports something more than the five cases defined in C90:§7.12

The reason I've not tried to handle NaN yet is because I'm not
sure what it should do. If my host architecture is also IEEE,
it seems wrong to loose the information in the mantissa field,
but I can't begin to imagine a portable way of extracting it.
Your solution is probably the best one can do.

Is it guaranteed that this representation is a non-trapping NaN
on every implementation. I seem to recall having heard that
implementations vary in this respect. (According to RFC 1014,
"According to IEEE specifications, the 'NaN' (not a number) is
system dependent and should not be used externally.")

> if (f == 0) {
> pf.mantissa = 0;
> pf.exp = 0;
> return pf;
> }

> if (pf.negative)
> f = -f;

> pf.exp = pf.bias;

> while (f < 1) {
> --pf.exp;
> f *= 2;
> }
> while (f >= 2) {
> ++pf.exp;
> f /= 2;
> }

This is what frexp is for. frexp should be written in an
implementation dependant manner, so as to be significantly
faster, especially for larger exponent values, and even more
importantly, significantly more accurate if the base is not a
power of two. If your internal format is actually IEEE, you can
loop over 1000 times in one of the loops, and if the base isn't
a power of two, you loose bits of accuracy each time through the
loop -- even in the case of IBM floating point (16 bits), you
can loose up to three bits precision here (where as frexp should
be exact).

> /* now we have 1 <= f < 2b */
> mantissa_t big_positive_int = mantissa_t(1) <<
> precision_type::mantissa_size;

> pf.mantissa = static_cast<mantissa_t>(f * big_positive_int);

I suspect that ldexp() would be preferrable to the
multiplication.

> if (pf.exp<1) {
> // number is not normalized?
> pf.mantissa >>= 1-pf.exp;
> pf.exp = 0;
> } else {
> pf.mantissa &= (mantissa_t(1) <<
> precision_type::mantissa_size)-1;
> }
> return pf;
> }

[...]


> Once you have a single_precision or double_precision type,
> it's easy to serialize it, as it's just a bunch of integers.

Of course, you have to know the encoding scheme which is
required (unless you are defining your own format), and respect
that.

For the rest: the same basic comments apply to from_ieee -- you
should be using ldexp, rather than a loop multiplication or
division.

And I question the necessity of exposing the intermediate
structure in the interface -- IMHO, it should be an
implementation detail.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Nicola Musatti

unread,
Mar 1, 2006, 9:30:25 AM3/1/06
to

Fede wrote:
> Hi,
>
> I'm implementing a binary protocol and I need to serialize a C++ double
> type value. I know that some compilers implement doubles with IEEE 754
> format, but I need to ensure the portability of my code. The question
> is if do you know any library or some other way to convert a double
> into a stream of bytes encoded with the IEEE format?

Consider using the Boost.Serialization library, which you can find at
www.boost.org .

Cheers,
Nicola Musatti

Drew Hall

unread,
Mar 1, 2006, 9:37:54 AM3/1/06
to
Fede wrote:
>I'm implementing a binary protocol and I need to serialize a C++ double
>type value. I know that some compilers implement doubles with IEEE 754
>format, but I need to ensure the portability of my code. The question
>is if do you know any library or some other way to convert a double
>into a stream of bytes encoded with the IEEE format?

XDR (External Data Representation) is a popular solution. It defines a
common binary format for the net (fp types based on IEEE 754 iirc), and
each
platform has a lib that converts to & from the host format to the
external format in a fairly painless way. See:

http://www.faqs.org/rfcs/rfc1014.html

or Google "XDR spec".

HTH,

Drew

Daniel K. O.

unread,
Mar 2, 2006, 3:27:52 AM3/2/06
to
kanze escreveu:

> Just curious, but shouldn't the types single_precision and
> double_precision be internal.

Well, I think the main problem is to break down the bits correctly. I
agree, it's much cleaner to export just 2 functions, one to serialize
32-bit single-precision and another for 64-bit double-precision. My
original idea was to construct an object of type single_precision /
double_precision and then overload operator<< and operator>> - but
somehow along the coding I just forgot to do that and it just got that
ugly. :)


>> The single_precision and double_precision classes represent
>> the IEEE 754 single and double precision types.
>
> Except that they don't. And they aren't serialized; you still
> have to serialize them.

I think they do represent the fp types. There are some extra bits (I
somehow didn't want to use a bit-field... but I can't remember why), but
the values are fully representable by those classes.

And indeed, the serialization isn't done by the class itself, I should
have made it clear that I used a clumsy 2-step approach: break the
native floating point into pieces, and then serialize. Obviously not the
most intelligent solution - but it was easy to debug it this way. :)

>> pf.negative = signbit(f);
>
> No need for a special function here:
> pf.negative = f < 0.0 ;
> does the trick.

I did try the plain comparison, but on my system -0 isn't less than 0;
so -0 becomes 0. OK, it may be irrelevant, but signbit() can preserve 1
bit of information for free. :)


> This is what frexp is for. frexp should be written in an
> implementation dependant manner, so as to be significantly
> faster, especially for larger exponent values, and even more
> importantly, significantly more accurate if the base is not a
> power of two.

I was a bit worried about using frexp()/ldexp() at first, because the
standard doesn't make it clear what happens when the argument isn't a
normalized finite value. You are right, I should be using it instead of
the explicit loop.


Thank you for the helpful comments.


---
Daniel K. O.

John Nagle

unread,
Mar 2, 2006, 3:30:38 AM3/2/06
to
Fede wrote:
> Hi,
>
> I'm implementing a binary protocol and I need to serialize a C++ double
> type value.

"htond" and "htonf" are widely used for that.

John Nagle
Animats

kanze

unread,
Mar 2, 2006, 8:51:08 AM3/2/06
to
John Nagle wrote:
> Fede wrote:

> > I'm implementing a binary protocol and I need to serialize a
> > C++ double type value.

> "htond" and "htonf" are widely used for that.

When they're present, and work. (They're not part of Posix, nor
of Open Systems Unix. And I can't find them under Solaris.)

And how do you use them when they exist? The integer forms
(which are present in Posix) suppose that the result of the
conversion is non-trapping on the host -- which is pretty safe
for ints, but not at all for floating point, and that the values
are always aligned -- guaranteed for types with four bytes or
less (but not for doubles) in XDR, but not universally.

All in all, I'd say that these functions are a good example of
how NOT to solve the problem.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

kanze

unread,
Mar 2, 2006, 10:14:47 AM3/2/06
to
Daniel K. O. wrote:
> kanze escreveu:
> > Just curious, but shouldn't the types single_precision and
> > double_precision be internal.

> Well, I think the main problem is to break down the bits
> correctly.

It's certainly the hardest part to do portably.

> I agree, it's much cleaner to export just 2 functions, one to
> serialize 32-bit single-precision and another for 64-bit
> double-precision. My original idea was to construct an object
> of type single_precision / double_precision and then overload
> operator<< and operator>> - but somehow along the coding I
> just forgot to do that and it just got that ugly. :)

> >> The single_precision and double_precision classes represent
> >> the IEEE 754 single and double precision types.

> > Except that they don't. And they aren't serialized; you
> > still have to serialize them.

> I think they do represent the fp types.

I guess it depends on what you mean by "represent". IEEE is
more than just a set of values. But I wrote that before I'd
gotten to the end of your posting, and seen how you handle the
serialization. As it is, I'd still object that they only
encompass part of the information, since you need to add
information at the point of serialization; if they truly
represented IEEE types, you could serialize from them without
any additional knowledge.

> There are some extra bits (I somehow didn't want to use a
> bit-field... but I can't remember why), but the values are
> fully representable by those classes.

> And indeed, the serialization isn't done by the class itself,
> I should have made it clear that I used a clumsy 2-step
> approach: break the native floating point into pieces, and
> then serialize. Obviously not the most intelligent solution -
> but it was easy to debug it this way. :)

Well, at some point you almost have to do this. It's really
just a question of whether you expose it or not.

> >> pf.negative = signbit(f);

> > No need for a special function here:
> > pf.negative = f < 0.0 ;
> > does the trick.

> I did try the plain comparison, but on my system -0 isn't less
> than 0; so -0 becomes 0. OK, it may be irrelevant, but
> signbit() can preserve 1 bit of information for free. :)

Good point. I hadn't thought of that.

> > This is what frexp is for. frexp should be written in an
> > implementation dependant manner, so as to be significantly
> > faster, especially for larger exponent values, and even more
> > importantly, significantly more accurate if the base is not
> > a power of two.

> I was a bit worried about using frexp()/ldexp() at first,
> because the standard doesn't make it clear what happens when
> the argument isn't a normalized finite value. You are right,
> I should be using it instead of the explicit loop.

Especially as you only use them after checking for the special
values anyway.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

0 new messages