C:如何在不使用强制类型转换的情况下更改变量类型

3
我正在将一个uint32_t转换为float,但不改变实际位。
只是为了确保:我不想强制转换它。因此 float f = (float) i 是我想做的完全相反的操作,因为它会改变位。
我将使用这个方法将我的(伪)随机数转换为浮点数,而不需要进行不必要的计算。
目前我正在做的事情已经可以运行,就是:
float random_float( uint64_t seed ) {

    // Generate random and change bit format to ieee
    uint32_t asInt = (random_int( seed ) & 0x7FFFFF) | (0x7E000000>>1);

    // Make it a float
    return *(float*)(void*)&asInt; // <-- pretty ugly and nees a variable
}                                                                      

问题: 现在我想摆脱asInt变量,并且想知道是否有更好/不那么丑陋的方法,而不是获取此变量的地址,两次进行转换并再次取消引用?


有趣的问题,你能说明一下为什么你需要这个微优化吗? - ckruczek
1
浮点数结果;memcpy(&result, &asInt, sizeof(uint32_t));返回结果;你可能还想有某种静态断言,即sizeof(float) == sizeof(uint32_t),即使这在今天是相当普遍的。 - Michael Burr
进一步解释@MichaelBurr的观点:您目前正在违反严格别名规则,这是未定义行为。 - EOF
1
请注意,浮点数的二进制表示中有很多_未定义_代码。此外,还有特殊条件的信号,如NAN、+INF、-INF等。还有两个0的表示形式(+0、-0)。你真的考虑过所有的影响吗? - too honest for this site
@Olaf:你说得对 :) 我目前正在阅读这篇文章:http://www.cprogramming.com/tutorial/floating_point/understanding_floating_point_representation.html 但是还没有看完。 - Scheintod
显示剩余11条评论
3个回答

6

您可以尝试使用联合(union)- 只要确保内存大小的类型是相同的:

union convertor {
    int asInt;
    float asFloat;
};

然后你可以将int赋值给asFloat(或者反过来,如果你愿意)。当我需要进行位操作并仍然在另一侧获取uint32_t表示的数字时,我经常使用它。[编辑]像许多评论员所正确指出的那样,您必须考虑不能用整数表示的值,如NAN,+ INF,-INF,+ 0,-0。

这看起来是个不错的想法。但它还需要一个额外的变量,对吗? - Scheintod
不对,sizeof(union convertor) == 4,联合体的大小,与结构体不同,它只使用保持其最大组件所需的最大内存。 - Ishay Peled
这就是正确的方法,我会更明确地说明我使用的类型,以使其更易读:c.asInt = ((random_int(seed) & 0x7FFFFF) | (0x7E000000>>1)); return c.asFloat。 - Ishay Peled
一个 int 可以有超过 15+1 位的任何大小。实际上确实如此。此外,浮点数对于特殊/非值条件(NAN、+/-INF、+/-0)具有未定义/特殊编码。此外,值取决于浮点数和整数的字节序,而不仅仅是整数的大小。真正的建议是:忘记这些细节! - too honest for this site
我看了你的第一个评论。我的评论也不仅仅是关于内存大小的问题。而且我忘了提到对齐问题。 - too honest for this site
显示剩余2条评论

1

根据你的代码,似乎你想生成介于0.5和1.0之间的浮点数。

假设你的微控制器具有带有浮点支持的标准C库,你可以在不涉及任何浮点运算的情况下符合所有标准地完成此操作,只需使用ldexp函数即可,该函数本身并不执行任何浮点数学运算。

代码如下:

return ldexpf((1 << 23) + random_thing_smaller_than_23_bits(), -24);

这里的诀窍在于我们知道IEEE754二进制32位浮点数的整数精度在2^23到2^24之间(我可能有一处错误,请仔细检查,我是从一些关于双精度浮点数的工作中翻译过来的)。因此编译器应该知道如何将该数字轻松地转换为浮点数。然后ldexp通过改变指数中的位数将该数字乘以2 ^ -24。没有实际的浮点运算和未定义的行为,该代码完全可移植到具有IEEE754数字的任何标准C实现。请检查生成的代码,但是一个好的编译器和C库不应该在这里使用任何浮点指令。
如果您想查看一些我做的关于生成随机浮点数的实验您可以查看这个github repo。这全部都是关于双精度浮点数的,但应该很容易转换为浮点数。

看起来很有趣。 实际上,我正在尝试在0..1范围内生成随机数,但我还没有成功。 - Scheintod
Hmm.. ldexpf(C11,7.12.6.6)已经需要一个浮点参数。我猜 TO 想从整数构造一个浮点数。 - too honest for this site
不确定ldexpf是否比rand24b() * 0x1p-24)更有效率。它的第一个参数是浮点数,因此仍然需要进行转换,而明智的编译器应该能够识别乘以二的幂并在那里执行最有效的操作。 - sh1
@sh1 这实际上是有意为之的。在注释中有记录。选择是在所有可能生成的数字之间具有统一的距离(这会减少尾数中的位数),还是使范围内的所有数字都可能,但某些数字比其他数字更有可能被返回。我选择了第一种选项。 - Art
那么为什么要这么复杂呢?为什么不直接使用 rand52b() * 0x1p-52 或者使用 ldexp() 来完成相同的操作呢?在转换过程中,位扫描将会在硬件中自动完成,无需手动重复。 - sh1
显示剩余2条评论

0

重新解释int的二进制表示为float会导致严重问题:

  • 在float的二进制表示中有很多未定义的代码。
  • 其他代码代表特殊条件,如NAN、+INF、-INF、+0、-0等。

此外,如果这是一个随机值,即使捕获所有非值表示,也会产生非常糟糕的随机分布。

如果您正在使用没有FPU的MCU上工作,最好考虑完全避免使用float。一种替代方法可能是分数或缩放整数。有许多实现使用float的算法,但可以轻松转换为固定点类型,精度损失可接受(甚至根本没有)。有些甚至可能比float更精确(请注意,单精度float只有23位尾数,int32将具有31位(+ 1个符号),分数或固定缩放int同样如此)。

请注意,C11添加了对_Frac的(可选)支持。您可能需要进行研究。

编辑:

根据您的评论,您似乎要将int转换为0..<1范围内的float。为此,您可以使用uint32_t(例如原始值)上的位操作组装浮点数。您只需要遵循IEEE格式(假定您的工具链符合C标准!请参见wikipedia)。
然后,可以通过联合或指针由其他人描述的方式重新解释结果(仍为uint32_t)。将其打包在一个系统相关的、有良好注释的库中,并深入挖掘。不要忘记检查字节序和对齐方式(浮点数和uint32_t可能都相同,但对于位运算很重要)。

有一些黑客技巧需要在浮点数的原始数据上进行整数运算,比如著名的“快速反平方根”。 - D-side
@D-side:任何软件FPU都必须具备这一点(即isqrt实际上是Soft-PFU的一个方面。但是,这是深层次的实现细节。上面的问题并没有暗示这一点。由于TO显然不知道保留代码,我不认为这是他真正想要的。然而,我主要展示了缺点;他仍然可以忽略我的建议。 - too honest for this site

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