ldexp(1, x) 和 exp2(x) 的区别
Difference between ldexp(1, x) and exp2(x)
似乎如果浮点表示的基数为 2(即 FLT_RADIX == 2
),std::ldexp(1, x)
和 std::exp2(x)
都会将 2
提高到给定的幂 x
.
标准是否定义或提及它们之间任何预期的行为 and/or 性能差异?不同编译器的实际经验是什么?
exp2(x)
和 ldexp(x,i)
执行两种不同的操作。前者计算2x,其中x
是一个浮点数,而后者计算x*2i,其中 i
是一个 整数 。对于 x
、exp2(x)
和 ldexp(1,int(x))
的整数值将是等效的,前提是 x
到整数的转换不会溢出。
关于这两个功能的相对效率的问题没有明确的答案。这将取决于硬件平台的功能和库实现的细节。虽然从概念上讲,ldexpf()
看起来像是对浮点操作数的指数部分的简单操作,但实际上比这要复杂一点,一旦考虑到通过非正规化的溢出和逐渐下溢。后一种情况涉及浮点数的有效数字(尾数)部分的舍入。
由于 ldexp()
通常是一个不常用的函数,根据我的经验,与其他数学函数相比,数学库作者对其应用的优化工作更少。
在某些平台上,ldexp()
或它的更快(自定义)版本将用作 exp2()
软件实现中的构建块。以下代码为 float
个参数提供了此方法的示例性实现:
#include <cmath>
/* Compute exponential base 2. Maximum ulp error = 0.86770 */
float my_exp2f (float a)
{
const float cvt = 12582912.0f; // 0x1.8p23
const float large = 1.70141184e38f; // 0x1.0p127
float f, r;
int i;
// exp2(a) = exp2(i + f); i = rint (a)
r = (a + cvt) - cvt;
f = a - r;
i = (int)r;
// approximate exp2(f) on interval [-0.5,+0.5]
r = 1.53720379e-4f; // 0x1.426000p-13f
r = fmaf (r, f, 1.33903872e-3f); // 0x1.5f055ep-10f
r = fmaf (r, f, 9.61817801e-3f); // 0x1.3b2b20p-07f
r = fmaf (r, f, 5.55036031e-2f); // 0x1.c6af7ep-05f
r = fmaf (r, f, 2.40226522e-1f); // 0x1.ebfbe2p-03f
r = fmaf (r, f, 6.93147182e-1f); // 0x1.62e430p-01f
r = fmaf (r, f, 1.00000000e+0f); // 0x1.000000p+00f
// exp2(a) = 2**i * exp2(f);
r = ldexpf (r, i);
if (!(fabsf (a) < 150.0f)) {
r = a + a; // handle NaNs
if (a < 0.0f) r = 0.0f;
if (a > 0.0f) r = large * large; // + INF
}
return r;
}
大多数 exp2()
的实际实现不会调用 ldexp()
,而是调用自定义版本,例如当支持整数和浮点数据之间的快速按位传输时,此处表示通过内部函数 __float_as_int()
和 __int_as_float()
将 IEEE-754 binary32
重新解释为 int32
,反之亦然:
/* For a in [0.5, 4), compute a * 2**i, -250 < i < 250 */
float fast_ldexpf (float a, int i)
{
int ia = (i << 23) + __float_as_int (a); // scale by 2**i
a = __int_as_float (ia);
if ((unsigned int)(i + 125) > 250) { // |i| > 125
i = (i ^ (125 << 23)) - i; // ((i < 0) ? -125 : 125) << 23
a = __int_as_float (ia - i); // scale by 2**(+/-125)
a = a * __int_as_float ((127 << 23) + i); // scale by 2**(+/-(i%125))
}
return a;
}
在其他平台上,硬件提供 exp2()
的单精度版本作为快速硬件指令。在处理器内部,这些通常通过使用线性或二次插值的 table 查找来实现。在这样的硬件平台上,ldexp(float)
可以根据exp2(float)
来实现,例如:
float my_ldexpf (float x, int i)
{
float r, fi, fh, fq, t;
fi = (float)i;
/* NaN, Inf, zero require argument pass-through per ISO standard */
if (!(fabsf (x) <= 3.40282347e+38f) || (x == 0.0f) || (i == 0)) {
r = x;
} else if (abs (i) <= 126) {
r = x * exp2f (fi);
} else if (abs (i) <= 252) {
fh = (float)(i / 2);
r = x * exp2f (fh) * exp2f (fi - fh);
} else {
fq = (float)(i / 4);
t = exp2f (fq);
r = x * t * t * t * exp2f (fi - 3.0f * fq);
}
return r;
}
最后,有些平台基本上在硬件上同时提供 exp2()
和 ldexp()
功能,例如 x86 处理器上的 x87 指令 F2XM1
和 FSCALE
。
似乎如果浮点表示的基数为 2(即 FLT_RADIX == 2
),std::ldexp(1, x)
和 std::exp2(x)
都会将 2
提高到给定的幂 x
.
标准是否定义或提及它们之间任何预期的行为 and/or 性能差异?不同编译器的实际经验是什么?
exp2(x)
和 ldexp(x,i)
执行两种不同的操作。前者计算2x,其中x
是一个浮点数,而后者计算x*2i,其中 i
是一个 整数 。对于 x
、exp2(x)
和 ldexp(1,int(x))
的整数值将是等效的,前提是 x
到整数的转换不会溢出。
关于这两个功能的相对效率的问题没有明确的答案。这将取决于硬件平台的功能和库实现的细节。虽然从概念上讲,ldexpf()
看起来像是对浮点操作数的指数部分的简单操作,但实际上比这要复杂一点,一旦考虑到通过非正规化的溢出和逐渐下溢。后一种情况涉及浮点数的有效数字(尾数)部分的舍入。
由于 ldexp()
通常是一个不常用的函数,根据我的经验,与其他数学函数相比,数学库作者对其应用的优化工作更少。
在某些平台上,ldexp()
或它的更快(自定义)版本将用作 exp2()
软件实现中的构建块。以下代码为 float
个参数提供了此方法的示例性实现:
#include <cmath>
/* Compute exponential base 2. Maximum ulp error = 0.86770 */
float my_exp2f (float a)
{
const float cvt = 12582912.0f; // 0x1.8p23
const float large = 1.70141184e38f; // 0x1.0p127
float f, r;
int i;
// exp2(a) = exp2(i + f); i = rint (a)
r = (a + cvt) - cvt;
f = a - r;
i = (int)r;
// approximate exp2(f) on interval [-0.5,+0.5]
r = 1.53720379e-4f; // 0x1.426000p-13f
r = fmaf (r, f, 1.33903872e-3f); // 0x1.5f055ep-10f
r = fmaf (r, f, 9.61817801e-3f); // 0x1.3b2b20p-07f
r = fmaf (r, f, 5.55036031e-2f); // 0x1.c6af7ep-05f
r = fmaf (r, f, 2.40226522e-1f); // 0x1.ebfbe2p-03f
r = fmaf (r, f, 6.93147182e-1f); // 0x1.62e430p-01f
r = fmaf (r, f, 1.00000000e+0f); // 0x1.000000p+00f
// exp2(a) = 2**i * exp2(f);
r = ldexpf (r, i);
if (!(fabsf (a) < 150.0f)) {
r = a + a; // handle NaNs
if (a < 0.0f) r = 0.0f;
if (a > 0.0f) r = large * large; // + INF
}
return r;
}
大多数 exp2()
的实际实现不会调用 ldexp()
,而是调用自定义版本,例如当支持整数和浮点数据之间的快速按位传输时,此处表示通过内部函数 __float_as_int()
和 __int_as_float()
将 IEEE-754 binary32
重新解释为 int32
,反之亦然:
/* For a in [0.5, 4), compute a * 2**i, -250 < i < 250 */
float fast_ldexpf (float a, int i)
{
int ia = (i << 23) + __float_as_int (a); // scale by 2**i
a = __int_as_float (ia);
if ((unsigned int)(i + 125) > 250) { // |i| > 125
i = (i ^ (125 << 23)) - i; // ((i < 0) ? -125 : 125) << 23
a = __int_as_float (ia - i); // scale by 2**(+/-125)
a = a * __int_as_float ((127 << 23) + i); // scale by 2**(+/-(i%125))
}
return a;
}
在其他平台上,硬件提供 exp2()
的单精度版本作为快速硬件指令。在处理器内部,这些通常通过使用线性或二次插值的 table 查找来实现。在这样的硬件平台上,ldexp(float)
可以根据exp2(float)
来实现,例如:
float my_ldexpf (float x, int i)
{
float r, fi, fh, fq, t;
fi = (float)i;
/* NaN, Inf, zero require argument pass-through per ISO standard */
if (!(fabsf (x) <= 3.40282347e+38f) || (x == 0.0f) || (i == 0)) {
r = x;
} else if (abs (i) <= 126) {
r = x * exp2f (fi);
} else if (abs (i) <= 252) {
fh = (float)(i / 2);
r = x * exp2f (fh) * exp2f (fi - fh);
} else {
fq = (float)(i / 4);
t = exp2f (fq);
r = x * t * t * t * exp2f (fi - 3.0f * fq);
}
return r;
}
最后,有些平台基本上在硬件上同时提供 exp2()
和 ldexp()
功能,例如 x86 处理器上的 x87 指令 F2XM1
和 FSCALE
。