32位浮点数转uint32_t:向上取整宏异常问题求助
我尝试写一个“巧妙”的宏,实现32位浮点数除法后向上取整为无符号32位整数(uint32_t),初始代码如下:
#include "float.h" #include "stdint.h" #define DIV_ROUND_UP(x, y) (uint32_t)(((float)(x) / (float)(y)) + (float)(1.0f - FLT_EPSILON))
我用FLT_EPSILON是为了让相加的数值小于1,本来以为调用DIV_ROUND_UP(10u, 1u)会返回10u,结果却得到了11u。测试发现换成5 * FLT_EPSILON才能得到正确结果,我搞不懂两个点:
- 明明加的是小于1的数,转成
uint32_t时为什么没被截断? - 为什么5倍的
FLT_EPSILON就有效?
补充:最后我用了下面这个只适用于正数的解决方案:
#define DIV_ROUND_UP(x, y) (((float)(x) / (float)(y)) > (uint32_t)((float)(x) / (float)(y)) ? \ (uint32_t)((float)(x) / (float)(y)) + 1u : \ (uint32_t)((float)(x) / (float)(y)))
为什么初始宏会返回11u?
10u / 1u转成float是精确的10.0f,加上1.0f - FLT_EPSILON后,结果是11.0f - FLT_EPSILON。但32位float的精度有限:FLT_EPSILON是2-23≈1.19e-7,而11.0f作为float,其最低有效位对应的数值是2(3-23)=2^-20≈9.54e-7——这是FLT_EPSILON的8倍。也就是说,float无法精确表示11.0f - FLT_EPSILON,这个值会被舍入回11.0f,转成uint32_t自然就是11u。
为什么5倍FLT_EPSILON有效?
当用1.0f - 5*FLT_EPSILON时,总和是11.0f -5*FLT_EPSILON,这个值比11.0f小了约5.95e-7,而11.0f的最低有效位是约9.54e-7,这个差值足够让float把它表示成比11.0f小的下一个可取值(即11.0f减去一个最低位单位)。此时转成uint32_t就会被截断为10u。
最终方案的合理性
这个方案通过比较浮点除法结果和它转成uint32_t后的值,判断是否存在小数部分:如果浮点值大于整数转换后的值,说明除法结果有小数,需要加1;否则直接取整数部分。它避开了浮点精度舍入的坑,不需要依赖浮点加法的精确性,对于正数场景是可靠的。
内容的提问来源于stack exchange,提问作者radoslav006

