如何在 C 中将浮点数转换为 16 位有符号整数分数?

How do I convert a float to a 16 bit signed integer fraction in C?

我正在使用 NXP 为其 Arm M4F 提供的库中的宏,将浮点类型变量转换为名为 frac16_t 的库数据类型,当浮点值为小于-1。 frac16_t 被定义为带符号的短格式。

宏代码为:

#define FRAC16(x) ((frac16_t)((x) < 0.999969482421875 ? ((x) >= -1 ? (x)*0x8000 : 0x8000) : 0x7fff))

此宏的预期行为是将 -1 和 +1 之间的任何浮点值转换为 16 位有符号整数,表示从 -1 到 (1 - 1/2^15) 的范围,其中 -1 为 0x8000值和 0x7fff 以及接近 1 的值。如果浮点值大于(接近)1,则结果在 0x7fff 处饱和,当浮点值小于 -1 时,结果应该为 0x8000。

实际发生的情况是,对于任何小于 -1 的输入,结果都是 0x7fff(即接近 1),对于任何其他值,它都像宣传的那样工作。

我确实发现将 0x8000 常量转换为 frac16_t 类型可以使宏正常工作,但我不明白为什么原始库宏不起作用。将常量更改为 -32768 也有效,并且这两个修复都会导致常量被编码为 32 位长,这需要从闪存中靠近加载指令的某处间接加载值,而不是作为 16 位文字加载,这是说明。

both of those fixes result in the constant being coded as 32 bits long which requires that the value be loaded indirectly from somewhere in flash

不完全是。十六进制常量转换为 double,然后转换为 signed short.

先用test ? some_type_A : some_type_B,结果是普通类型。在这种情况下,double.

(x)*0x8000是一个double(或者float),然后: 0x8000然后: 0x7fff也变成了同样的浮点型。

0x8000 变成了 32768.0。将超出范围的 double 分配给 signed short 是 UB。
一个常见的 UB 是超出范围的值接受 min/max 限制。
在 OP 的例子中 double 32768.0 变成了 signed short 32767.

#define FRAC16(x) \
    ((frac16_t)((x) < 0.999969482421875 ? ((x) >= -1 ? (x)*0x8000 : 0x8000) : 0x7fff))
//                                                                  ^^^^^^    
//                                                                  32768.0

不是将 32768.0 分配给 signed short 并调用 未定义的行为,而是将 -32768.0 分配给已定义的行为。

#define FRAC16(x)
    ((frac16_t)((x) < 0.999969482421875 ? ((x) >= -1 ? (x)*0x8000 : -32768 : 0x7fff))
//                                                                  ^^^^^^^
//                                                                  -32768.0

如果想用 SHRT_MIN 编码,请不要使用 0x8000,请使用 SHRT_MIN(-0x7fff - 1)