strtof() 的实现,浮点乘法和尾数舍入问题

问题描述

这个问题与其说是关于C,不如说是关于算法。我需要实现 strtof() 函数,它的行为与 GCC 完全相同 - 从头开始​​(没有 GNU MPL 等)。

让我们跳过检查,只考虑正确的输入和正数,例如345.6e7。我的基本算法是:

  1. 将数字拆分为分数和整数指数,因此对于 345.6e7,分数为 3.456e2,指数为 7。
  2. 创建一个浮点指数。为此,我使用了以下表格:
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。

  1. 将分数乘以浮动指数并返回结果。

这里出现了第一个问题:由于我们做了很多乘法,每次都会因为四舍五入的乘法结果而得到一个有点大的错误。所以,我决定深入研究浮点乘法算法并自己实现它:一个函数需要一些浮点数(在我的例子中 - 最多 7 个)并在位级别将它们相乘。考虑我有 uint256_t 类型适合尾数产品。

现在,第二个问题:将尾数乘积舍入为 23 位。我尝试了几种舍入方法(舍入到偶数,冯诺依曼舍入 - a small article about them),但没有一种方法可以为所有测试数字提供正确的结果。其中一些真的让我感到困惑,比如这个:

7038531e-32。 GCC 的 strtof() 返回 0x15ae43fd,因此正确的无偏尾数是 2e43fd。我选择 7.038531e6(有偏尾数 d6cc86)和 1e-32(b.m. 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 - 错误!因此,对于这个数字,获得正确尾数的唯一方法就是将其截断 - 但截断不适用于其他数字。

经过无数个晚上的工作,我的问题是:

  1. 这种 strtof() 方法正确吗? (我知道 GCC 为此使用 GNU MPL,并试图对其进行研究。但是,尝试复制 MPL 的实现需要移植整个库,这绝对不是我想要的)。也许这种先拆分后乘的算法不可避免地容易出错?我做了一些其他的小技巧(例如,为浮点范围内的所有整数指数创建指数表),但它们导致更多失败的转换。

  2. 如果是这样,我是否在舍入时遗漏了什么?想了半天,这个7038531e-32的号码却把我搞糊涂了。

解决方法

如果我想尽可能精确,我通常会做这样的事情(但是我通常会做相反的操作 float -> text):

  1. 只使用整数(不要使用浮点数)

    如您所知,float 是由整数指数位移的整数尾数,因此不需要浮点数。

    为了构造最终的 float 数据类型,您可以使用简单的 union 与 float 和 32 位无符号整数......或指向相同地址的此类类型的指针。

    这将避免完全适合的数字的舍入错误,并缩小不适合的数字的错误。

  2. 使用十六进制数字

    您可以将运行时的十进制数文本转换为其十六进制对应物(仍为文本),从那里创建尾数和指数整数很简单。

    这里:

    是在文本上完成的 dec2hexhex2dec 数字转换的 C++ 实现示例

  3. 在转换时使用更多位作为尾数

    对于这样的任务和单精度浮点数,我通常使用 2 或 3 个 32 位 DWORD 作为 24 位尾数,以便在乘法后仍然保持一定的精度如果你想精确,你必须同时处理 128+24 位数字的整数和小数部分,因此按顺序排列为 5x32 位数字。

有关更多信息和灵感,请参阅(反向操作):

您的代码将与此相反(很多部分将是相似的)

自从我发帖以来,我制作了更高级的版本,可以识别格式,就像 printf 一样,支持更多的数据类型和更多,而无需使用任何库(但它的代码约为 22.5 KB)。我需要它用于 MCU,因为打印的 GCC 实现不是很好......

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...