【问题标题】:log2 for max int and long valueslog2 用于最大 int 和 long 值
【发布时间】:2021-08-06 02:28:04
【问题描述】:

为什么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 用于浮点,可能会丢失精度。对于整数,这相当于找到最重要的 1 位。对此没有标准的 C 函数,但您可以在此处找到一些算法和特定于编译器的方法:Fast computing of log2 for 64-bit integers
  • @NateEldredge: (a) log2 可能会丢失精度,但不会丢失精度:输入精度 double 与输出精度 double 完全相同。 (b) log2 的准确性不是这个问题的一个因素。值的变化发生在log2 执行之前。

标签: c floating-point floating-accuracy


【解决方案1】:

TL;DR:

如果您想获得最高 1 位的位置,那么您以最慢且最容易出错的方式完全错误。见文末解决方案


log2() 接收到 double,但您平台中的 longlong 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 double218446744073709551615 = 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 中,您需要特定于实现的解决方案,例如

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

【讨论】:

  • 我对“因为日志和其他超越函数不需要被 IEEE-754 忠实四舍五入”有疑问适用于此。 log2l(18446744073709551615.0L) 是 64.0L,而不是 63.9999999999999999 9653...L 的下一个较小的 long double,因为 64.0L 是更接近的答案 - 在正确答案 63.9999999999999999 9992... 的 0.5 ULP 内。在我看来,答案是忠实的。
  • @chux-ReinstateMonica 是的,这可能是问题所在。更新了答案
【解决方案2】:

为什么log2(ULONG_MAX)log2(ULLONG_MAX) 会得到错误的结果?我期待 63,但得到了 64。

2 步和四舍五入的精度不够。


ULLONG_MAX 或 18,446,744,073,709,551,615 或 264-1 在传递给 double log2(double) 时转换为 18,446,744,073,709,551,616.0 之前。结果是 64.0,这对于 log2(264)

是数学正确的

我尝试了 log2l 函数。它需要很长的双精度值 - same problem

80-bit "double extended" extended precision format 具有 64 位精度,ULLONG_MAX 在传递给 long double log2l(long double) 时会转换为 18,446,744,073,709,551,615.0L。结果仍然是 64.0L,因为 64.0L 是使用该编码的最佳 long double 答案。

64.0                       long double
63.99999999999999999992... math log2(18,446,744,073,709,551,615)
63.99999999999999999653... next smaller long double

要让log2(ULLONG_MAX) 产生小于 64 的良好结果((int) 截断为 63),浮点编码需要:

  • 至少 64 位精度以适应 ULLONG_MAX 的精确转换。

  • 至少大约 69 位精度才能形成小于 64.0 的舍入答案。

【讨论】:

    【解决方案3】:

    log2 被声明为double log2(double);它接受double 参数并产生double 结果。在评估 log2(ULLONG_MAX) 时,ULLONG_MAX 将转换为 double

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

    然后log2 应用于 264 产生 64。

    您可以通过在转换为double 前后打印ULLONG_MAX 看到这一点:

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

    打印:

    18446744073709551615 18446744073709551616

    【讨论】:

    • 我试过 log2l-function。它需要很长的双精度值——同样的问题。但 ULLONG_MAX 远小于 LDBL_MAX。
    • @top-of-stack:在每个 C 实现中,long double 格式并不比 double 更精确。包含&lt;float.h&gt; 后,您可以打印DBL_MANT_DIGLDBL_MANT_DIG 以查看每个数字的有效位数和FLT_RADIX 以查看数字的基数(可能为2)。如果 DBL_MANT_DIGLDBL_MANT_DIG 都得到 53,那么它们在 C 实现中的精度相同。如果您得到更大的 LDBL_MANT_DIG 数字,则显示演示结果的确切代码。
    猜你喜欢
    • 1970-01-01
    • 2013-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-12
    • 1970-01-01
    • 1970-01-01
    • 2012-03-12
    相关资源
    最近更新 更多