You must define the format and implement it. Typically, most of the network protocols that I know use IEEE float and double, output big-endian (but other formats are possible). The advantage of using IEEE formats is that it is what is used by most modern everyday machines inside; if you are on one of these machines (and portability for other machines, such as mainframes, is not a problem), you can "convert" to the format by simply using type-punning for an unsigned int of the same size and output. So, for example, you might have:
obstream& operator<<( obstream& dest, uint64_t value ) { dest.put((value >> 56) & 0xFF); dest.put((value >> 48) & 0xFF); dest.put((value >> 40) & 0xFF); dest.put((value >> 32) & 0xFF); dest.put((value >> 24) & 0xFF); dest.put((value >> 16) & 0xFF); dest.put((value >> 8) & 0xFF); dest.put((value ) & 0xFF); return dest; } obstream& operator<<( obstream& dest, double value ) { return dest << reinterpret_cast<uint64_t const&>( value ); }
If you must be portable to a machine that does not support IEEE (for example, any of the modern mainframes), you will need something more complex:
obstream& obstream::operator<<( obstream& dest, double value ) { bool isNeg = value < 0; if ( isNeg ) { value = - value; } int exp; if ( value == 0.0 ) { exp = 0; } else { value = ldexp( frexp( value, &exp ), 53 ); exp += 1022; } uint64_t mant = static_cast< 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 dest; }
(Note that this doesnβt handle NaN and infinities. Personally, I would forbid them from formatting, since not all floating-point representations support them. But then there is no floating-point format on the IBM mainframe that will support 1E306, although you can encode it in dual IEEE format above.)
Reading is, of course, the opposite. Or:
ibstream& operator>>( ibstream& source, uint64_t& results ) { uint64_t value = (source.get() & 0xFF) << 56; value |= (source.get() & 0xFF) << 48; value |= (source.get() & 0xFF) << 40; value |= (source.get() & 0xFF) << 32; value |= (source.get() & 0xFF) << 24; value |= (source.get() & 0xFF) << 16; value |= (source.get() & 0xFF) << 8; value |= (source.get() & 0xFF) ; if ( source ) results = value; return source; } ibstream& operator>>( ibstream& source, double& results) { uint64_t tmp; source >> tmp; if ( source ) results = reinterpret_cast<double const&>( tmp ); return source; }
or if you cannot rely on IEEE:
ibstream& ibstream::operator>>( ibstream& source, double& results ) { uint64_t tmp; source >> tmp; if ( source ) { double f = 0.0; if ( (tmp & 0x7FFFFFFFFFFFFFFF) != 0 ) { f = ldexp( ((tmp & 0x000FFFFFFFFFFFFF) | 0x0010000000000000), static_cast<int>( (tmp & 0x7FF0000000000000) >> 52 ) - 1022 - 53 ); } if ( (tmp & 0x8000000000000000) != 0 ) { f = -f; } dest = f; } return source; }
(This assumes the input is not NaN or infinity.)