TL;DR:
如果您想获得最高 1 位的位置,那么您以最慢且最容易出错的方式完全错误。见文末解决方案
log2() 接收到 double,但您平台中的 long 和 long long 有 64 位精度,这远远超过 double 可以存储的精度,因为它可能是 IEEE-754 binary64 并且只有 53有效位。 double 中最接近 ULLONG_MAX 的是 ULLONG_MAX + 1.0 = 264 显然 log2(ULLONG_MAX) = log2(264 sup>) = 64。像这样使用double 永远无法获得 63
如果您想获得这些数字的以 2 为底的对数,那么在某些平台上您将需要更精确的类型,例如 long double,还需要一个好的 log2 库(请参阅下文了解为什么重要)。在 x86 上,long double 通常是具有 64 个有效位的 80-bit extended precision,并且可以毫无问题地存储 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));
}
Demo on 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
请注意,ULONG_MAX 不能用 double 精度表示,正如我之前提到的。但也请注意,即使在 long double 中,我们也会得到 log2l(18446744073709551615.0L) = 64!!! 只有 __float128 这是 libquadmath 的 IEEE-754 quadruple precision 有效。为什么?因为log 和其他超越函数非常复杂并且不需要被IEEE-754 忠实地舍入,所以允许实现使用更快的算法但可能返回一些带有1ULP 错误的结果。上面 Godbolt 的结果是针对 glibc 的,正如我上面所说的,您需要找到一些更好的 log2 库。见
更新:
正如下面 chux 所评论的,在这种情况下,结果可能会被忠实地四舍五入,但不幸的是,最接近日志的 long double 值218446744073709551615 = 63.999999999999999999921791345121706111... 是 64.0升
这意味着您仍然需要更高的精度才能获得预期的输出
但可能你做错了。如果你只是想get the position of the highest 1 bit 那么永远不要使用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 中,您需要特定于实现的解决方案,例如
还有其他快速的按位解决方案