提问人:top-of-stack 提问时间:5/17/2021 最后编辑:phuclvtop-of-stack 更新时间:7/31/2021 访问量:776
log2 表示最大 int 和 long 值
log2 for max int and long values
问:
为什么我用 和 得到错误的结果?我本来以为是 63 分,但得到了 64 分。log2 (ULONG_MAX)
log2 (ULLONG_MAX)
UINT32_MAX == pow(2, 32) - 1
所以,(不是 32 岁!log2(UINT32_MAX) == 31
ULLONG_MAX == pow(2, 64) - 1
所以我会期待log2(ULLONG_MAX) == 63
但我得到了 64 分。为什么?
// 15
printf ("u_int16: %d\n", (int)log2 (UINT16_MAX));
// 31
printf ("u_int: %d\n", (int)log2 (UINT32_MAX));
// 64
printf ("ul_int: %d\n", (int)log2 (ULONG_MAX));
// 64
printf ("ull_int: %d\n", (int)log2 (ULLONG_MAX));
答:
log2
被宣布 ;它接受一个参数并产生一个结果。When 被计算,将转换为 .double log2(double)
double
double
log2(ULLONG_MAX)
ULLONG_MAX
double
通常用于的格式在有效(浮点表示的分数部分)中具有 53 位。表示需要 63 位。所以不能用 表示。相反,转换会生成最接近的可表示值,即 264。double
ULLONG_MAX
ULLONG_MAX
double
然后应用于 2 64 产生64.log2
您可以通过在转换为之前和之后打印来看到这一点:ULLONG_MAX
double
printf("%llu\n", ULLONG_MAX);
printf("%.0f\n", (double) ULLONG_MAX);
指纹:
18446744073709551615 18446744073709551616
评论
long double
double
<float.h>
DBL_MANT_DIG
LDBL_MANT_DIG
FLT_RADIX
DBL_MANT_DIG
LDBL_MANT_DIG
LDBL_MANT_DIG
TL;博士:
如果你想获得最高 1 位的位置,那么你用最慢和最容易出错的方式就完全错了。最后查看解决方案
log2()
接收一个 ,但在您的平台中具有 64 位的精度,这远远超出了可以存储的范围,因为它可能是 IEEE-754 binary64,并且只有 53 位有效位。最接近 in 的是 ULLONG_MAX + 1.0 = 2 64,显然 log2(ULLONG_MAX) = log2(2 64) =64。这样使用你永远无法获得 63double
long
long long
double
ULLONG_MAX
double
double
如果你想获得这些数字的以 2 为底的对数,那么你需要一个更精确的类型,就像在某些平台上一样,还有一个好的库(为什么这很重要,见下文)。在 x86 上通常是 80 位扩展精度,具有 64 位有效位,可以毫无问题地存储long double
log2
long double
ULLONG_MAX
#include <stdio.h>
#include <math.h>
#include <quadmath.h>
#include <limits.h>
#include <float.h>
int main()
{
printf("sizeof(long) = %zu\n", sizeof(long));
printf("sizeof(long long) = %zu\n", sizeof(long long));
printf("sizeof(double) = %zu\n", sizeof(double));
printf("sizeof(long double) = %zu\n", sizeof(long double));
printf("double has %d significant bits\n", DBL_MANT_DIG);
printf("long double has %d significant bits\n", LDBL_MANT_DIG);
printf("-----------------------------------------------------\n");
printf("ULONG_MAX = %lu\n", ULONG_MAX);
printf("ULLONG_MAX = %llu\n", ULLONG_MAX);
printf("(double)ULONG_MAX = %f\n", (double)ULONG_MAX);
printf("(double)ULLONG_MAX = %f\n", (double)ULLONG_MAX);
printf("(long double)ULONG_MAX = %Lf\n", (long double)ULONG_MAX);
printf("(long double)ULLONG_MAX = %Lf\n", (long double)ULLONG_MAX);
printf("-----------------------------------------------------\n");
printf("ul_int (double):\t\t\t%d\n", (int)log2(ULONG_MAX));
printf("ull_int (double):\t\t\t%d\n", (int)log2(ULLONG_MAX));
printf("ul_int (long double):\t\t\t%d\n", (int)log2l((long double)ULONG_MAX));
printf("ull_int (long double):\t\t\t%d\n", (int)log2l((long double)ULLONG_MAX));
printf("ul_int (18446744073709551615.0L):\t%d\n",
(int)log2l(18446744073709551615.0L));
printf("ul_int (__float128):\t\t\t%d\n", (int)log2q((__float128)ULONG_MAX));
printf("ull_int (__float128):\t\t\t%d\n", (int)log2q((__float128)ULLONG_MAX));
printf("ull_int (18446744073709551615.0q):\t%d\n",
(int)log2q(18446744073709551615.0q));
}
在 Godbolt 上演示。示例输出:
sizeof(long) = 8
sizeof(long long) = 8
sizeof(double) = 8
sizeof(long double) = 16
double has 53 significant bits
long double has 64 significant bits
-----------------------------------------------------
ULONG_MAX = 18446744073709551615
ULLONG_MAX = 18446744073709551615
(double)ULONG_MAX = 18446744073709551616.000000
(double)ULLONG_MAX = 18446744073709551616.000000
(long double)ULONG_MAX = 18446744073709551615.000000
(long double)ULLONG_MAX = 18446744073709551615.000000
-----------------------------------------------------
ul_int (double): 64
ull_int (double): 64
ul_int (long double): 64
ull_int (long double): 64
ul_int (18446744073709551615.0L): 64
ul_int (__float128): 63
ull_int (__float128): 63
ull_int (18446744073709551615.0q): 63
请注意,正如我之前提到的,不能以精确度表示。但也要注意,即使在长双倍
中,我们也得到 log2l(18446744073709551615.0L) = 64
!!只有 libquadmath 的 IEEE-754 四倍精度才能工作。为什么?因为和其他超越函数非常复杂,并且不需要 IEEE-754 忠实地舍入,所以允许使用更快的算法实现,但可能会返回一些带有 1ULP 错误的结果。上面 Godbolt 的结果是针对 glibc,正如我上面所说,您需要找到一些更好的库。看ULONG_MAX
double
__float128
log
log2
更新:
正如 chux 在下面评论的那样,在这种情况下,结果可能会忠实地四舍五入,但不幸的是,最接近对数2的值 18446744073709551615 = 63.999999999999999999921791345121706111... 是 64.0Llong double
这意味着您仍然需要更高的精度才能获得预期的输出
但可能你做错了。如果您只想获得最高 1 位的位置,那么永远不要使用 log2()
!!它非常慢,并且容易出现如上所述的浮点错误。大多数架构都有一个指令,可以在 1 个或几个周期内获得结果。在 C++20 中,只需使用 std::bit_width(x)
或等效项
return std::numeric_limits<T>::digits - std::countl_zero(x);
在较旧的 C++ 版本中,可以使用 boost::multiprecision::msb(x)
、boost::static_log2(x)。
在 C 语言中,你需要特定于实现的解决方案,例如
- 海湾合作委员会(GCC)和克朗(Clang)的
__builtin_clz
- MSVC 中的
_BitScanReverse
- ICC中的
_bit_scan_reverse
还有其他快速按位解决方案
评论
log2l(18446744073709551615.0L)
long double
63.9999999999999999 9653...L
63.9999999999999999 9992...
为什么我用 和 得到错误的结果?我本来以为是 63 分,但得到了 64 分。
log2(ULONG_MAX)
log2(ULLONG_MAX)
2 步和四舍五入的精度不足。
ULLONG_MAX
或 18,446,744,073,709,551,615 或 2 64-1 转换为 18,446,744,073,709,551,616.0 在传递给 之前。结果为 64.0,对 log2(264double log2(double)
)
我尝试了log2l-function。它需要很长的双精度值 - 同样的问题。
使用 80 位“双扩展”扩展精度格式及其 64 位精度,当传递给 时,转换为 18,446,744,073,709,551,615.0L。结果仍然是 64.0L,因为 64.0L 是该编码的最佳答案。ULLONG_MAX
long double log2l(long double)
long double
64.0 long double
63.99999999999999999992... math log2(18,446,744,073,709,551,615)
63.99999999999999999653... next smaller long double
为了获得小于 64(截断为 63)的良好结果,浮点编码需要:log2(ULLONG_MAX)
(int)
至少 64 位精度以适应 的精确转换。
ULLONG_MAX
至少大约 69 位的精度才能形成小于 64.0 的四舍五入答案。
评论
log2
用于浮点,可能会失去精度。对于整数,这相当于找到最高有效 1 位。没有标准的 C 函数,但您可以在此处找到一些算法和特定于编译器的方法: 快速计算 64 位整数的 log2log2
double
double
log2
log2