这个问题与其说是关于 C,不如说是关于算法。我需要实现strtof()
功能,它的行为与 GCC 完全相同 - 并且从头开始(没有 GNU MPL 等)。
让我们跳过检查,只考虑正确的输入和正数,例如 345.6e7。我的基本算法是:
- 将数字拆分为分数和整数指数,因此对于 345.6e7,分数为 3.456e2,指数为 7。
- 创建一个浮点指数。为此,我使用这些表:
static const float powersOf10[] = {
1.0e1f,
1.0e2f,
1.0e4f,
1.0e8f,
1.0e16f,
1.0e32f
};
static const float minuspowersOf10[] = {
1.0e-1f,
1.0e-2f,
1.0e-4f,
1.0e-8f,
1.0e-16f,
1.0e-32f
};
并将浮点指数作为整数指数中相应位的乘积,例如 7 = 1+2+4 => float_exponent = 1.0e1f * 1.0e2f * 1.0e4f。
- 将分数乘以浮动指数并返回结果。
第一个问题来了:因为我们做了很多乘法,我们得到一个有点大的错误,因为每次四舍五入的乘法结果。因此,我决定深入研究浮点乘法算法并自己实现它:一个函数采用多个浮点数(在我的情况下 - 最多 7 个)并将它们在位级别上相乘。考虑我有uint256_t
适合尾数产品的类型。
现在,第二个问题:将尾数积舍入到 23 位。我尝试了几种舍入方法(四舍五入,冯诺依曼四舍五入 -一篇关于它们的小文章),但没有一个可以为所有测试数字提供正确的结果。其中一些真的让我很困惑,比如这个:
7038531e-32。GCC 的strtof()
返回 0x15ae43fd,所以正确的无偏尾数是 2e43fd。我选择乘以 7.038531e6(偏置尾数 d6cc86)和 1e-32(bm cfb11f)。得到的二进制形式的无偏尾数是
( 47)0001 ( 43)0111 ( 39)0010 ( 35)0001
( 31)1111 ( 27)1110 ( 23)1110 ( 19)0010
( 15)1011 ( 11)0101 ( 7)0001 ( 3)1101
我必须四舍五入到 23 位。但是,通过所有舍入方法,我必须将其四舍五入,结果我会得到 2e43fe - 错误!因此,对于这个数字,获得正确尾数的唯一方法就是将其切碎 - 但切碎不适用于其他数字。
这在无数个晚上都有效,我的问题是:
这种 strtof() 方法是否正确?(我知道 GCC 使用 GNU MPL,并试图深入了解它。但是,尝试复制 MPL 的实现需要移植整个库,这绝对不是我想要的)。也许这种先拆分后乘算法不可避免地容易出错?我做了一些其他的小技巧,(例如,为浮点范围内的所有整数指数创建指数表),但它们导致了更多失败的转换。
如果是这样,我在四舍五入时是否遗漏了什么?我想了很久,但是这个 7038531e-32 的号码让我完全糊涂了。