如何在 c++ 下对尾数和双精度或浮点数的指数部分进行操作(快速)

How to operate (fast) on mantissa and exponent part of double or float at c++?

本文关键字:指数部 操作 快速 浮点数 c++ 双精度      更新时间:2023-10-16

我使用c ++来计算各种类型的特殊函数(例如Lambert函数,用于计算反演的迭代方法等(。在许多情况下,显然有一种更好的方法可以直接使用尾数和指数。

我找到了很多如何提取尾数和指数部分的答案,

但是所有这些答案都只是"计算速度不是很有效的学术案例",对我来说有点没用(我使用尾数和指数操作的动机是提高计算速度(。有时我需要调用一些关于十亿次的特定函数(非常昂贵的计算(,所以每个保存的计算工作都很好。使用"frexp"将尾数返回为双倍不是很合适。

我的问题是(对于带有IEEE 754浮点数的c ++编译器(:

1(如何读取浮子/双位的特定尾数?

2(如何将整个尾数读成浮点/双精度的整数/字节?

3(与1(,2(指数相同的问题。

4(与1(,2(,3(相同的问题用于写作。

关于我的动机是如果我直接使用尾数或指数,计算速度更快。我想一定有一个非常简单的解决方案。

在许多情况下,显然有一种更好的方法可以直接使用尾数和指数。

我从我的信号处理工作中非常了解这种感觉,但事实是指数和尾数不能简单地用作单独的数字;IEEE754指定了一些特殊情况和偏移量等。

我想一定有一个非常简单的解决方案。

工程经验告诉我:通常以"一个简单的解决方案"结尾的句子是不正确的。

"学术案例">

但是,绝对不是真的(我会在最后提到一个例子(。

IEEE754浮点数上,优化在现实世界中有非常可靠的用法。但是,我发现,由于后来的 x86 处理器能够执行 SIMD(单指令,多数据(,并且浮点与大多数"位移"操作一样快,我通常怀疑您尝试自己在位级别上执行此操作是不明智的。

通常,由于IEEE754是一个标准,因此您会在任何地方找到有关如何将其存储在特定体系结构上的文档。如果你看过,你至少应该找到维基百科的文章解释如何做1(和2((它并不像你想象的那么静态(。

更重要的是:不要试图比编译器更聪明。您可能不会,除非您明确知道如何矢量化多个相同的操作。

尝试特定编译器的数学优化。如前所述,如今他们通常不会做太多事情;CPU 执行浮点计算的速度不一定比在整数上慢。

我宁愿看看你的算法,并在那里寻找优化的潜力。

另外,当我在这里时,让我们介绍一下VOLK(内核的矢量优化库(,它主要是用于信号处理的数学库。 http://libvolk.org 有一个概述。例如32f_expfast,查看以 32f 开头的内核。您会注意到,每个 SIMD 指令集都有不同的实现,一种是通用的和 CPU 优化的,都是不同的。

您可以将 fp 值的地址复制到unsigned char*中,并将生成的指针视为覆盖 fp 值的数组的地址。

在 C 或 C++ 中,如果 x 是 IEEE 双精度,那么如果 L 是一个 64 位长的 int,则表达式

L = *((long *) &x);

将允许直接访问位。如果 s 是表示符号的字节(0 = '+',1 = '-'(,e 是表示无偏指数的整数,f 是表示小数位的长整型,则

s = (byte)(L >> 63);

e = ((int)(L >> 52) & 0x7FF) - 0x3FF;

f = (L & 0x000FFFFFFFFFFFFF);

(如果 f 是一个规范化的数字,即不是 0、非正规、inf 或 NaN,那么最后一个表达式应该0x0010000000000000添加到其中,以允许 IEEE 双精度格式的隐式高阶 1 位。

将符号、指数和分数重新打包回双精度数是类似的:

L = (s <<63( + ((e + 0x3FF( <<52( + (f & 0x000FFFFFFFFFFFFF(;

x = *((双倍*(&L(;

上面的代码在用 64 位

代码编译的 64 位机器上只生成几个机器指令,没有子例程调用。 对于 32 位代码,有时会调用 64 位算术,但一个好的编译器通常会生成内联代码。 无论哪种情况,这种方法都非常快。

类似的方法适用于使用 L = bitConverter.DoubleToInt64Bits(x);x = BitConverter.Int64BitsToDouble(L); 的 C#,或者如果允许不安全的代码,则完全按照上述方法工作。