gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 19:35:56 25 4
gpt4 key购买 nike

我正在阅读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/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com