如何在字符串中存储浮点值的字节并在之后检索值?

时间:2017-03-02 11:54:23

标签: c++ c++11 unions

我试图找出一种通过网络发送一系列浮点值的方法。我已经看到了各种答案,这是我目前的尝试:

#include <iostream>
#include <cstring>

union floatBytes
{
    float value;
    char bytes[sizeof (float)];
};

int main()
{
    floatBytes data1;
    data1.value = 3.1;
    std::string string(data1.bytes);
    floatBytes data2;
    strncpy(data2.bytes, string.c_str(), sizeof (float));

    std::cout << data2.value << std::endl; // <-- prints "3.1"

    return 0;
}

哪种方法很好用(虽然我怀疑在将此字符串发送到其他系统时可能遇到问题,请发表评论)。

但是,如果浮点值是一个整数(如3.0而不是3.1),那么这不起作用。

data1.value = 3;
std::string string(data1.bytes);
floatBytes data2;
strncpy(data2.bytes, string.c_str(), sizeof (float));

std::cout << data2.value << std::endl; // <-- prints "0"

那么存储浮点值的字节,发送它并解析它的首选方法是什么?#34;返回&#34;浮动值?

4 个答案:

答案 0 :(得分:2)

切勿以这种方式使用str*功能。这些用于处理c-string,而float的字节表示肯定不是有效的c-string。您需要的是以通用表示形式发送/接收数据。它们中有很多,但基本上有两种:文本表示或字节编码。

文本表示)几乎包括使用stringstream将浮点值转换为字符串以进行转换,然后提取字符串并通过连接发送它。

字节表示)更有问题,因为如果两台机器没有使用相同的字节顺序,浮点编码等,那么你就不能按原样发送原始字节。但是存在(至少)一种称为XDR(RFC 4506)的标准,它指定了一个标准来编码用IEEE 754原生编码的浮点/双精度值的字节。

答案 1 :(得分:2)

你可以使用相当复杂的代码移植来重新构建一个浮点数,我在IEE754 git中心站点上维护它。如果使用这些函数将float拆分为字节,并使用其他函数重新构建,则无论float编码如何,都将在接收器中获得与发送时相同的值,直至格式的精度。

https://github.com/MalcolmMcLean/ieee754

float freadieee754f(FILE *fp, int bigendian)
{
   unsigned long buff = 0;
   unsigned long buff2 = 0;
   unsigned long mask;
   int sign;
   int exponent;
   int shift;
   int i;
   int significandbits = 23;
   int expbits = 8;
   double fnorm = 0.0;
   double bitval;
   double answer;

   for(i=0;i<4;i++)
     buff = (buff << 8) | fgetc(fp);
   if(!bigendian)
   {
     for(i=0;i<4;i++)
     {
       buff2 <<= 8;
       buff2 |= (buff & 0xFF);
       buff >>= 8;
     }
     buff = buff2; 
   }

   sign = (buff & 0x80000000) ? -1 : 1;
   mask = 0x00400000;
   exponent = (buff & 0x7F800000) >> 23;
   bitval = 0.5;
   for(i=0;i<significandbits;i++)
   {
     if(buff & mask)
        fnorm += bitval;
     bitval /= 2;
     mask >>= 1;
   }
   if(exponent == 0 && fnorm == 0.0)
     return 0.0f;
   shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */

   if(shift == 128 && fnorm != 0.0)
     return (float) sqrt(-1.0);
   if(shift == 128 && fnorm == 0.0)
   {
#ifdef INFINITY
     return sign == 1 ? INFINITY : -INFINITY;
#endif
     return (sign * 1.0f)/0.0f;
   }
   if(shift > -127)
   {
     answer = ldexp(fnorm + 1.0, shift);
     return (float) answer * sign;
   }
   else
   {
     if(fnorm == 0.0)
     {
       return 0.0f;
     }
     shift = -126;
     while (fnorm < 1.0)
     {
         fnorm *= 2;
         shift--;
     }
     answer = ldexp(fnorm, shift);
     return (float) answer * sign;
   }
}


int fwriteieee754f(float x, FILE *fp, int bigendian)
{
    int shift;
    unsigned long sign, exp, hibits, buff;
    double fnorm, significand;
    int expbits = 8;
    int significandbits = 23;

    /* zero (can't handle signed zero) */
    if (x == 0)
    {
        buff = 0;
        goto writedata;
    }
    /* infinity */
    if (x > FLT_MAX)
    {
        buff = 128 + ((1 << (expbits - 1)) - 1);
        buff <<= (31 - expbits);
        goto writedata;
    }
    /* -infinity */
    if (x < -FLT_MAX)
    {
        buff = 128 + ((1 << (expbits - 1)) - 1);
        buff <<= (31 - expbits);
        buff |= (1 << 31);
        goto writedata;
    }
    /* NaN - dodgy because many compilers optimise out this test, but
    *there is no portable isnan() */
    if (x != x)
    {
        buff = 128 + ((1 << (expbits - 1)) - 1);
        buff <<= (31 - expbits);
        buff |= 1234;
        goto writedata;
    }

    /* get the sign */
    if (x < 0) { sign = 1; fnorm = -x; }
    else { sign = 0; fnorm = x; }

    /* get the normalized form of f and track the exponent */
    shift = 0;
    while (fnorm >= 2.0) { fnorm /= 2.0; shift++; }
    while (fnorm < 1.0) { fnorm *= 2.0; shift--; }

    /* check for denormalized numbers */
    if (shift < -126)
    {
        while (shift < -126) { fnorm /= 2.0; shift++; }
        shift = -1023;
    }
    /* out of range. Set to infinity */
    else if (shift > 128)
    {
        buff = 128 + ((1 << (expbits - 1)) - 1);
        buff <<= (31 - expbits);
        buff |= (sign << 31);
        goto writedata;
    }
    else
        fnorm = fnorm - 1.0; /* take the significant bit off mantissa */

    /* calculate the integer form of the significand */
    /* hold it in a  double for now */

    significand = fnorm * ((1LL << significandbits) + 0.5f);


    /* get the biased exponent */
    exp = shift + ((1 << (expbits - 1)) - 1); /* shift + bias */

    hibits = (long)(significand);
    buff = (sign << 31) | (exp << (31 - expbits)) | hibits;

writedata:
    /* write the bytes out to the stream */
    if (bigendian)
    {
        fputc((buff >> 24) & 0xFF, fp);
        fputc((buff >> 16) & 0xFF, fp);
        fputc((buff >> 8) & 0xFF, fp);
        fputc(buff & 0xFF, fp);
    }
    else
    {
        fputc(buff & 0xFF, fp);
        fputc((buff >> 8) & 0xFF, fp);
        fputc((buff >> 16) & 0xFF, fp);
        fputc((buff >> 24) & 0xFF, fp);
    }
    return ferror(fp);
}

答案 2 :(得分:1)

首先让我清楚您的代码问题。 您正在使用strncpy,它会在看到'\ 0'时停止复制。这只是意味着它没有复制你的所有数据。

因此预期为0。

使用memcpy而不是strncpy应该可以解决问题。

我刚尝试过这个C ++代码

int main(){
        float f = 3.34;
        printf("before = %f\n", f);
        char a[10];
        memcpy(a, (char*) &f, sizeof(float));
        a[sizeof(float)] = '\0'; // For sending over network
        float f1 = 1.99;
        memcpy((char*) &f1, a, sizeof(float));
        printf("after = %f\n", f1);
        return 0;
}

我按预期得到了正确的输出。

现在正确到了。我不确定这是否归类为未定义的行为。它也可以被称为类型惩罚的情况,在这种情况下它将是实现定义的(并且我假设任何理智的编译器都不会破坏它)。

只要我为同一个程序做这件事,这一切都没问题。

现在您的问题是通过网络发送它。我认为这不是正确的做法。就像@ Jean-BaptisteYunès所提到的那样,两个系统都可以使用不同的浮点表示,甚至不同的字节排序。

在这种情况下,您需要使用库将其转换为某些标准表示,如IEEE 754。

答案 3 :(得分:1)

主要问题是C ++没有强制执行IEEE754,因此浮动的表示可能在两台计算机之间起作用而在另一台计算机上失败。

问题必须分为两个:

  1. 如何将浮点数编码和解码为共享格式
  2. 如何将值序列化为char数组以进行传输。
  3. 如何将浮点数编码/解码为通用格式

    C ++没有强制使用特定的位格式,这意味着计算机可能会传输一个浮点数而另一台机器上的值会有所不同。

      

    1.0f

    的示例      

    机1:符号+ 8位指数+ 23位尾数:   0-01111111-00000000000000000000000

         

    机器2:符号+ 7位指数   + 24bit尾数:0-0111111-000000000000000000000000

         

    从机器1发送到没有共享格式的机器2,将导致机器2接收:0-0111111-100000000000000000000000 = 1.5

    这是一个复杂的主题,可能难以完全跨平台解决。 C ++包含一些便利属性,以某种方式帮助:

    bool isIeee754 = std::numeric_limits<float>::is_iec559;
    

    主要问题是编译器可能不知道其输出将在其上运行的确切CPU体系结构。所以这是可靠的一半。幸运的是,位格式在大多数情况下是正确的。此外,如果格式未知,则可能很难将其标准化。

    我们可能会设计一些代码来检测浮点格式,或者我们可能会决定将这些情况作为“不支持的平台”跳过。

    对于IEEE754 32bit,我们可以通过按位运算轻松提取尾数,符号和指数:

    float input;
    uint8_t exponent = (input>>23)&0xFF;
    uint32_t mantissa = (input&0x7FFFFF);
    bool sign = (input>>31);
    

    传输的标准格式可能是32位IEEE754,所以它在大多数情况下都可以工作,甚至没有编码:

    bool isStandard32BitIeee754( float f)
    {
        // TODO: improve for "special" targets.
        return std::numeric_limits<decltype(f)>::is_iec559 && sizeof(f)==4;
    }
    

    最后,特别是对于那些非标准平台,需要保留NaN和无限的特殊值。

    序列化浮动以进行传输

    第二个问题要简单得多,只需将标准化二进制文件转换为字符数组,但并非所有字符都可以在网络上接受,特别是如果它在HTTP协议中使用或等效。

    对于这个例子,我将流转换为十六进制编码(替代方案可以是Base64等)。

      

    注意:我知道有一些功能可能有所帮助,我故意使用简单的C ++来尽可能降低级别的步骤。

    void toHex( uint8_t &out1, uint8_t &out2, uint8_t in)
    {
        out1 = in>>4;
        out1 = out1>9? out1-10+'A' : out1+'0';
        out2 = in&0xF;
        out2 = out2>9? out2-10+'A' : out2+'0';
    }
    
    void standardFloatToHex (float in, std::string &out)
    {
        union Aux
        {
            uint8_t c[4];
            float f;
        };
        out.resize(8);
        Aux converter;
        converter.f = in;
        for (int i=0; i<4; i++)
        {
            // Could use std::stringstream as an alternative.
            uint8_t c1, c2, c = converter.c[i];
            toHex(c1, c2, c);
            out[i*2] = c1;
            out[i*2+1] = c2;
        }
    }
    

    最后,在对面需要等效解码。

    <强>结论

    已经解释了将浮点值标准化为共享位格式。可能需要一些与实现相关的转换。

    显示了大多数常见网络协议的序列化。