8

How to convert float to byte array of length 4 (array of char*) ? I need to send over network some data, tcp, and need to send float as a byte array. ( I know precision to two decimal digits, so at the moment I on client side multiply by 100 and on server divide by 100 - basically convert to integer and then find bytes with & 0xff << operations). But it is ugly and can lost precision during time.

3
  • I think you require 8 bytes to keep precision. Commented Dec 24, 2012 at 8:23
  • 2
    @HennoBrandsma That's a double, in general. On most modern systems, float is the 4-byte IEEE-754 format. Commented Dec 24, 2012 at 8:25
  • 1
    Don't discount endian accounting in your final solution unless you are restricting your client/server to one format (big or small). Commented Dec 24, 2012 at 14:21

3 Answers 3

19

Reading any type as a sequence of bytes is quite simple:

float f = 0.5f;

unsigned char const * p = reinterpret_cast<unsigned char const *>(&f);

for (std::size_t i = 0; i != sizeof(float); ++i)
{
    std::printf("The byte #%zu is 0x%02X\n", i, p[i]);
}

Writing to a float from a network stream works similarly, only you'd leave out the const.

It is always permitted to reinterpret any object as a sequence of bytes (any char-type is permissible), and this expressly not an aliasing violation. Note that the binary representation of any type is of course platform dependent, so you should only use this for serialization if the recipient has the same platform.

Sign up to request clarification or add additional context in comments.

3 Comments

But this, of course, doesn't really help him if he has to write bytes to the network.
@JamesKanze: Sure, you can use write(fd, p, sizeof(float))... the question is who can read that :-)
There's practically no case where write would be correct. But the question was to output over the network. So what he writes must correspond to the format the protocol he's using has defined.
6

The first thing you have to do is to determine the format of the float in the network protocol. Just knowing that it is 4 bytes doesn't tell you much: IBM mainframe, Oracle Sparc and the usual PC all have four byte floats, but they have three different formats. Once you know the format, depending on it and your portability requirements, two different strategies can be used:

If the format in the protocol is IEEE (the most frequent case), and you don't have to be portable to machines which aren't IEEE (Windows and most Unix are IEEE—most mainframes aren't), then you can use type punning to convert the float to a uint32_t, and output that, using either:

std::ostream&
output32BitUInt( std::ostream& dest, uint32_t value )
{
    dest.put( (value >> 24) & 0xFF );
    dest.put( (value >> 16) & 0xFF );
    dest.put( (value >>  8) & 0xFF );
    dest.put( (value      ) & 0xFF );
}

for big-endian (the usual network order), or:

std::ostream&
output32BitUInt( std::ostream& dest, uint32_t value )
{
    dest.put( (value      ) & 0xFF );
    dest.put( (value >>  8) & 0xFF );
    dest.put( (value >> 16) & 0xFF );
    dest.put( (value >> 24) & 0xFF );
}

for little-endian (used by some protocols). Which one you use will depend on the format defined for the protocol.

To convert from float to uint32_t, you'll have to check your compiler. Using memcpy is the only method fully guaranteed by the standard; the intent is that using a reinterpret_cast<uint32_t&> on the float work as well, and most (all?) compiler also support using a union.

If you need to be portable to mainframes as well, or the format is something other than IEEE, then you'll need to extract exponent, sign and mantissa from the float, and output each in the target format. Something like the following should work to output IEEE big-endian on any machine (including mainframes which don't use IEEE), and should give you some idea:

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 ;
}

(BytePutter is a simple class which takes care of the usual boilerplate and does error checking.) Of course, the various manipulations for the output will be different if the output format is not IEEE, but this should show the basic principles. (If you need portability to some of the more exotic mainframes, which don't support uint32_t, you can replace it with any unsigned integral type which is larger than 23 bits.)

Comments

6

Just overlay the data in one area, in C

union dataUnion {  
    float f;  
    char fBuff[sizeof(float)];  
}  
// to use:  
dataUnion myUnion;  
//  
myUnion.f = 3.24;  
for(int i=0;i<sizeof(float);i++)  
    fputc(myUnion.fbuff[i],fp); // or what ever stream output....  

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.