如何解决最大 int 和 long 值的 log2
为什么我用 log2 (ULONG_MAX)
和 log2 (ULLONG_MAX)
得到错误的结果?我期待 63 但得到了 64。
UINT32_MAX == pow(2,32) - 1
所以 log2(UINT32_MAX) == 31
,(不是 32!)
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()
接收到 double
,但是您平台中的 long
和 long long
具有 64 位精度,远远超过 double
可以存储,因为它可能是 IEEE-754 binary64 并且只有 53 个有效位。 ULLONG_MAX
中最接近 double
的是 ULLONG_MAX + 1.0
,log2(ULLONG_MAX + 1.0)
是 64
如果你想得到这些数字的以 2 为底的对数,那么在某些平台上你需要一个更精确的类型,比如 long double
,以及一个很好的 log2
库(请参阅下面的原因这很重要)。在 x86 上,long double
通常是 80-bit extended precision,具有 64 位有效位,可以毫无问题地存储 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 工作。为什么?由于 IEEE-754 不要求对 log
和其他超越函数进行忠实舍入,因此允许实现使用更快的算法,但可能会返回一些具有 1ULP 错误的结果。上面 Godbolt 的结果是针对 glibc 的,你需要找到一些更好的 log2
库,正如我上面所说的。见
- Floating point accuracy with different languages
- Math precision requirements of C and C++ standard
- Why do sin(45) and cos(45) give different results?
- Why do I get platform-specific result for std::exp?
更新:
正如下面 chux 所评论的,在这种情况下,结果可能会忠实地四舍五入,但不幸的是,最接近 log218446744073709551615 = 63.999999999999999999921791345121706111... 的 long double
值是 64.0L>
这意味着您仍然需要更高的精度才能获得预期的输出
但可能你做错了。如果您只想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 中,您需要特定于实现的解决方案,例如
-
__builtin_clz
在 GCC 和 Clang 中 -
_BitScanReverse
在 MSVC 中 -
_bit_scan_reverse
在 ICC
在
中还有其他快速按位解决方案- What is the fastest/most efficient way to find the highest set bit (msb) in an integer in C?
- Fast computing of log2 for 64-bit integers
- Find the highest order bit in C
- How to get position of right most set bit in C
log2
被声明为 double log2(double)
;它接受一个 double
参数并产生一个 double
结果。计算 log2(ULLONG_MAX)
时,ULLONG_MAX
会转换为 double
。
通常用于 double
的格式在有效数(浮点表示的小数部分)中有 53 位。表示 ULLONG_MAX
需要 63 位。所以 ULLONG_MAX
不能用 double
表示。相反,转换会产生可表示的最接近的值,即 264。
然后将 log2
应用于 264 产生 64。
您可以通过在转换为 ULLONG_MAX
之前和之后打印 double
来看到这一点:
printf("%llu\n",ULLONG_MAX);
printf("%.0f\n",(double) ULLONG_MAX);
印刷品:
18446744073709551615 18446744073709551616,
为什么我用 log2(ULONG_MAX)
和 log2(ULLONG_MAX)
得到错误的结果?我期待 63 但得到了 64。
两步和四舍五入精度不够。
ULLONG_MAX
或 18,446,744,073,709,551,615 或 264-1 在传递给 double log2(double)
时转换为 18,616.0 before。结果是 64.0,这对 log2(264)
我尝试了 log2l 函数。它需要很长的双精度值 - same problem。
使用具有 64 位精度的 80-bit "double extended" extended precision format,当传递给 ULLONG_MAX
时,long double log2l(long double)
转换为 18,615.0L。结果仍然是 64.0L,因为 64.0L 是该编码的最佳 long double
答案。
64.0 long double
63.99999999999999999992... math log2(18,615)
63.99999999999999999653... next smaller long double
要使 log2(ULLONG_MAX)
产生小于 64 的良好结果((int)
截断为 63),浮点编码需要:
-
至少 64 位精度以适应
ULLONG_MAX
的精确转换。 -
至少约 69 位精度才能形成小于 64.0 的舍入答案。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。