如何将浮点数转换为4字节长度的字节数组(char*数组)?

8

如何将长度为4的浮点数转换为字节数组(char*数组)?我需要通过网络发送一些数据,使用tcp协议,并且需要将浮点数作为字节数组发送。(我知道精度到小数点后两位,所以目前在客户端上乘以100,在服务器上除以100 - 基本上转换为整数,然后用 & 0xff 和 << 操作找到字节)。但这很丑陋,而且可能会在时间上失去精度。


我认为你需要8个字节来保持精度。 - Henno Brandsma
2
@HennoBrandsma 一般来说,那是一个double。在大多数现代系统上,float是4字节的IEEE-754格式。 - user529758
1
除非您将客户端/服务器限制为一个格式(大端或小端),否则不要在最终解决方案中忽略字节顺序的计算。 - WhozCraig
3个回答

18

任何类型作为字节序列阅读非常简单:

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

从网络流写入到浮点数的操作与之类似,只需省略const即可。

任何对象都可以被重新解释为字节序列(任何char类型均可),这不会违反别名规则。注意,任何类型的二进制表示当然是与平台相关的,因此如果接收方平台不同,你应该仅在进行序列化时使用它。


但是,如果他必须向网络写入字节,这当然对他没有什么帮助。 - James Kanze
@JamesKanze:当然,你可以使用write(fd, p, sizeof(float))...问题是谁能读取它 :-) - Kerrek SB
几乎没有情况下 write 是正确的。但问题是要在网络上输出。因此,他所写的必须符合他使用的协议定义的格式。 - James Kanze

6
你需要做的第一件事是确定网络协议中浮点数的格式。仅知道它是4个字节并不能告诉你太多信息:IBM大型机、Oracle Sparc和通常的PC都有四个字节的浮点数,但它们有三种不同的格式。一旦你知道了格式,根据它和你的可移植性要求,可以使用两种不同的策略:
如果协议中的格式是IEEE(最常见的情况),并且你不需要在非IEEE的机器上进行可移植性操作(Windows和大多数Unix都是IEEE - 大多数大型机不是),那么你可以使用类型转换将浮点数转换为uint32_t,并输出它,使用以下任一方法:
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 );
}

对于大端字节序(通常是网络字节序),使用:

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

针对一些协议使用小端模式。具体用哪种模式取决于协议定义的格式。

要将float转换为uint32_t,您需要检查您的编译器。在标准中,只有使用memcpy方法能够完全保证;使用reinterpret_cast<uint32_t&>将float强制转换为uint32_t也是可行的,大多数(或者全部?)编译器也支持使用union

如果需要在大型机上跨平台或者格式不是IEEE,则需要从浮点数中提取指数、符号和尾数,并以目标格式输出。以下代码可以在任何机器上(包括不使用IEEE的大型机)输出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 ;
}

(BytePutter 是一个简单的类,它处理通常的样板并进行错误检查。) 当然,如果输出格式不是 IEEE,输出的各种操作将是不同的,但这应该展示了基本原则。(如果你需要在一些不支持 uint32_t 的异端主机上进行移植性,可以用任何大于 23 位的无符号整数类型来替换它.)


6
只需将数据在一个区域中叠加,使用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....  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接