log2 表示最大 int 和 long 值

log2 for max int and long values

提问人:top-of-stack 提问时间:5/17/2021 最后编辑:phuclvtop-of-stack 更新时间:7/31/2021 访问量:776

问:

为什么我用 和 得到错误的结果?我本来以为是 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));
c 点浮 点精度

评论

3赞 Shawn 5/17/2021
这些数字可能无法在系统上用双精度表示。
1赞 Nate Eldredge 5/17/2021
log2用于浮点,可能会失去精度。对于整数,这相当于找到最高有效 1 位。没有标准的 C 函数,但您可以在此处找到一些算法和特定于编译器的方法: 快速计算 64 位整数的 log2
2赞 Eric Postpischil 5/17/2021
@NateEldredge:(a)可能会损失精度,但不会损失精度:输入精度,与输出精度完全相同。(b) 准确性不是这个问题的一个因素。值的更改发生在执行之前。log2doubledoublelog2log2

答:

2赞 Eric Postpischil 5/17/2021 #1

log2被宣布 ;它接受一个参数并产生一个结果。When 被计算,将转换为 .double log2(double)doubledoublelog2(ULLONG_MAX)ULLONG_MAXdouble

通常用于的格式在有效(浮点表示的分数部分)中具有 53 位。表示需要 63 位。所以不能用 表示。相反,转换会生成最接近的可表示值,即 264doubleULLONG_MAXULLONG_MAXdouble

然后应用于 2 64 产生64.log2

您可以通过在转换为之前和之后打印来看到这一点:ULLONG_MAXdouble

printf("%llu\n", ULLONG_MAX);
printf("%.0f\n", (double) ULLONG_MAX);

指纹:

18446744073709551615
18446744073709551616

评论

0赞 top-of-stack 5/17/2021
我尝试了log2l-function。它需要很长的双精度值 - 同样的问题。但ULLONG_MAX比LDBL_MAX小得多。
2赞 Eric Postpischil 5/17/2021
堆栈@top:格式并不比每个 C 实现更精确。包含后,您可以打印并查看每个数字的有效位数,并查看数字的基数(可能是 2)。如果两者的精度均为 53,则 和 的精度相同。如果 的数值较大,则显示演示结果的确切代码。long doubledouble<float.h>DBL_MANT_DIGLDBL_MANT_DIGFLT_RADIXDBL_MANT_DIGLDBL_MANT_DIGLDBL_MANT_DIG
4赞 phuclv 5/17/2021 #2

TL;博士:

如果你想获得最高 1 位的位置,那么你用最慢和最容易出错的方式就完全错了。最后查看解决方案


log2() 接收一个 ,但在您的平台中具有 64 位的精度,这远远超出了可以存储的范围,因为它可能是 IEEE-754 binary64,并且只有 53 位有效位。最接近 in 的是 ULLONG_MAX + 1.0 = 2 64,显然 log2(ULLONG_MAX) = log2(2 64) =64。这样使用你永远无法获得 63doublelonglong longdoubleULLONG_MAXdoubledouble

如果你想获得这些数字的以 2 为底的对数,那么你需要一个更精确的类型,就像在某些平台上一样,还有一个好的库(为什么这很重要,见下文)。在 x86 上通常是 80 位扩展精度,具有 64 位有效位,可以毫无问题地存储long doublelog2long doubleULLONG_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_MAXdouble__float128loglog2

更新:

正如 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 语言中,你需要特定于实现的解决方案,例如

还有其他快速按位解决方案

评论

0赞 chux - Reinstate Monica 5/18/2021
我怀疑“因为 IEEE-754 不需要忠实地舍入对数和其他超越函数”适用于这里。 是 64.0L,而不是下一个较小的,因为 64.0L 是更接近的答案 - 在正确答案的 0.5 ULP 以内。在我看来,答案是忠实的。log2l(18446744073709551615.0L)long double63.9999999999999999 9653...L63.9999999999999999 9992...
0赞 phuclv 5/28/2021
@chux-ReinstateMonica:是的,这可能是问题所在。更新了答案
0赞 chux - Reinstate Monica 5/18/2021 #3

为什么我用 和 得到错误的结果?我本来以为是 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_MAXlong 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 的四舍五入答案。