c - 一个简单的 float 序列化示例的问题

标签 c

我正在阅读http://beej.us/guide/bgnet/html/#serialization教程的序列化部分。

我正在审查将数字编码为可移植的二进制形式的代码。

#include <stdint.h>

uint32_t htonf(float f)
{
    uint32_t p;
    uint32_t sign;

    if (f < 0) { sign = 1; f = -f; }
    else { sign = 0; }

    p = ((((uint32_t)f)&0x7fff)<<16) | (sign<<31); // whole part and sign
    p |= (uint32_t)(((f - (int)f) * 65536.0f))&0xffff; // fraction

    return p;
}

float ntohf(uint32_t p)
{
    float f = ((p>>16)&0x7fff); // whole part
    f += (p&0xffff) / 65536.0f; // fraction

    if (((p>>31)&0x1) == 0x1) { f = -f; } // sign bit set

    return f;
}

我在p = ((((uint32_t)f)&0x7fff)<<16) | (sign<<31); // whole part and sign这一行遇到了问题。

根据原始代码注释,此行提取整个部分符号,下一行处理分数部分。

然后,我找到了一张有关float如何在内存中表示的图像,并手动开始了计算。

从维基百科Single-precision floating-point format:

因此,我假设整个部分 == 指数部分

但是,如果基于上面的图像,则此(uint32_t)f)&0x7fff)<<16)将获取分数部分的最后

现在我很困惑,哪里出了错?

最佳答案

重要的是要意识到这段代码不是什么。此代码对float值的各个位不执行任何操作。 (如果确实如此,它将不会像它声称的那样是可移植的并且与机器无关。)并且它创建的“便携式”字符串表示形式是固定点,而不是浮点。

例如,如果使用此代码转换数字-123.125,我们将获得二进制结果

10000000011110110010000000000000

或十六进制
807b2000

现在,这个数字10000000011110110010000000000000是从哪里来的?让我们将其分解为符号,整数和小数部分:
1 000000001111011 0010000000000000

符号位为1,因为我们的原始数字为负。 000000001111011是123的15位二进制表示。0010000000000000是8192。8192来自何处?好吧,8192÷65536是0.125,这是我们的小数部分。 (更多有关此内容。)

代码是如何做到的?让我们逐步介绍它。

(1)提取符号。这很容易:这是普通的测试if(f < 0)

(2)提取整数部分。这也很容易:我们将浮点数f转换为unint32_t。当您将浮点数转换为C语言中的整数时,其行为非常明显:它将丢弃小数部分,并为您提供整数。因此,如果f为123.125,则(uint32_t)f为123。

(3)提取分数。由于我们已经有了整数部分,因此可以通过从原始浮点数f开始并减去整数部分来隔离分数。即123.125-123 = 0.125。然后我们将小数部分乘以65536,即216。

我们乘以65536而不乘以其他数字可能并不明显。从某种意义上说,使用什么数字都没有关系。这里的目标是取一个小数点f并将其转换为两个整数ab,以便我们稍后可以再次恢复该小数点f(尽管可能近似)。我们稍后将再次恢复分数f的方法是通过计算
a + b / x
x在哪里,还有其他一些数字。如果我们为x选择1000,则将123.125分为ab值123和125。我们为x选择65536或216,因为这可以最大程度地利用分配给我们的16位我们表示中的小数部分。由于x是65536,因此b必须为某个数字,我们可以将其除以65536,以获得0.125。因此,从b / 65536 = 0.125开始,通过简单的代数,我们有了b = 0.125 * 65536。有道理?

无论如何,现在让我们看一下执行步骤1、2和3的实际代码。
if (f < 0) { sign = 1; f = -f; }

十分简单。如果f为负,则我们的符号位将为1,并且我们希望其余代码在f的正版本上运行。
p = ((((uint32_t)f)&0x7fff)<<16) | (sign<<31);

如前所述,此处的重要部分是(uint32_t)f,它只是获取f的整数(整数)部分。位掩码& 0x7fff提取其中的低15位,并丢弃其他任何内容。 (这是因为我们的“便携式表示形式”仅为整数部分分配了15位,这意味着不能表示大于215-1或32767的数字)。移位<< 16将其移至最终unint32_t结果的上半部分,即它所属的位置。然后| (sign<<31)接受符号位并将其放在它所属的高位位置。
p |= (uint32_t)(((f - (int)f) * 65536.0f))&0xffff; // fraction

在这里,(int)f重新计算f的整数(整数)部分,然后f - (int)f提取分数。如上所述,我们将其乘以65536。可能仍然存在一个小数部分(即使在乘法之后也是如此),因此我们再次转换为(uint32_t)将其丢弃,仅保留整数部分。我们只能处理16位小数,因此我们可以使用& 0xffff提取这些位(丢弃其他任何内容),尽管这是不必要的,因为我们从小于1的正小数开始并将其乘以65536,所以我们应该得出结果正数小于65536,即我们不应该有一个不完全适合16位的数字。最后,p |=操作将我们刚刚计算出的这16位填充到p的低位一半中,我们已经完成了。

关于c - 一个简单的 float 序列化示例的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58938528/

相关文章:

c - char 逐字符读取文件,numbers 设置数字

c - 如何判断哪个用户启动了这个程序?

c - 以下 MPI 程序通过矩形面积求和法计算曲线下面积的错误是什么

c - 您如何理解 mlockall 手册页?

c - 创建共享库时,如何告诉链接器可执行文件中存在符号?

对 Makefile (C/unix) 感到困惑

C 程序 : Can anyone explain the program ? [循环变量概念]

c - 实现通用排序功能时遇到问题

C 在循环中将输入读入 char 数组

c - 如何检查数据类型是否达到最大容量?