繁体   English   中英

从 double 转换为 size_t 会产生错误的结果?

[英]Cast from double to size_t yields wrong result?

以下代码有效。 我的问题是,2)不应该导致非常接近 1)的结果吗? 为什么 2) 铸造的数量如此之少? 因此,也许值得注意的是 2) 正好是 1) 的一半:

std::cout << "1)  " << std::pow(2, 8 * sizeof(size_t)) << std::endl;
std::cout << "2)  " << static_cast<size_t>(std::pow(2, 8 * sizeof(size_t))) << std::endl; 

output 是:

  1. 18446744073709551616
  2. 9223372036854775808

这是由于规范的那一部分:

7.3.10 浮点整数转换[conv.fpint]

浮点类型的纯右值可以转换为 integer 类型的纯右值。 转换截断; 也就是说,小数部分被丢弃。 如果截断的值不能在目标类型中表示,则行为未定义。

18446744073709551616 (即截断部分)大于系统上的std::numberic_limit<size_t>::max() ,因此,该强制转换的行为未定义。

如果我们想计算某个无符号整数数据类型可以表示的不同值的数量,我们可以计算

 std::cout << "1)  " << std::pow(2, 8 * sizeof(size_t)) << std::endl; // yields 18446744073709551616

这将计算 2 的 64 次方并产生 18446744073709551616。由于 sizeof(size_t) 是 8 字节,在 64 位机器上,并且一个字节有 8 位,因此 size_t 数据类型的宽度是 64 位,因此是 2^64。

这并不奇怪,因为通常系统上的 size_t 具有其底层硬件总线系统的宽度,因为我们希望消耗不超过一个时钟周期来传递数组或向量的地址或索引。

上面的数字表示可以用 64 位无符号整数数据类型表示的所有不同整数值的数量,如 size_t 或 unsigned long long,包括 0 作为一种可能性。 并且由于它确实包含 0,因此要表示的最高值正好少一,所以 18446744073709551615。

这个号码也可以通过

 std::cout << std::numeric_limits<size_t>::max() << std::endl; // yields 18446744073709551615
 std::cout << std::numeric_limits<unsigned long long>::max() << std::endl; // yields the same

现在一个无符号数据类型存储它的值,比如

   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 is 0 
   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 is 1 or 2^0
   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010 is 2 or 2^1
   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000011 is 3 or 2^1+2^0
   00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000100 is 4 or 2^2
   ...
   11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 is 18446744073709551615
   and if you want to add another 1, you would need a 65th bit on the left which you dont have:
 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 is 0 because 
   there are no more bits on the left.

任何高于您希望表示的最高可能值的金额都将归结为以最大可能值 + 1 为模的金额。(金额 % (max + 1)),正如我们在上面的示例中看到的那样,这导致为零。

由于这很自然,因此标准定义,如果您将任何有符号或无符号整数数据类型转换为另一种无符号整数数据类型,则将转换为最大可能值 + 1 的模数。漂亮。

但是,当我们希望将负积分转换为无符号积分(例如 -1 到 unsigned long long 示例)时,这条简单的规则对我们来说有点意外。 你首先有一个 0 值,然后你减去 1。发生的是上面示例的相反序列。 看一看:

  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 is 0 and now do -1
  11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 is 18446744073709551615

所以是的,将 -1 转换为 size_t 会导致 std::numeric_limits<size_t>::max()。 起初非常令人难以置信,但经过一些思考和玩弄之后可以理解。

现在我们的第二行代码

 std::cout << "2)  " << static_cast<size_t>(std::pow(2, 8 * sizeof(size_t))) << std::endl;

我们会天真地期待 18446744073709551616,当然,结果与第一行相同。

但是,既然我们现在知道模最大 + 1 并且我们现在知道最大的加 1 给出 0,我们也会再次天真地接受 0 作为答案。

为什么天真? 因为 std::pow 返回一个 double 而不是整数数据类型。 double 数据类型也是 64 位的,但在内部它的表示完全不同。

 0XXXXXXX XXXX0000 00000000 00000000 00000000 00000000 00000000 00000000

只有这 11 个 X 位代表 2^n 形式的指数。 这意味着只有这 11 位必须显示 64,而 double 将表示 2^64 * 1。所以我们的大数的表示在 double 中比在 size_t 中紧凑得多。 在将 2^64 的表示更改为 64 位线之前,是否有人想要对最大加 1 进行模数转换。

例如,可以在https://docs.microsoft.com/en-us/cpp/build/ieee-floating-point-representation?view=msvc-160中找到有关浮点表示的一些进一步阅读。

标准规定,如果将浮点值转换为目标整数数据类型无法表示的整数,则结果为 UB,即未定义的行为。

请参阅 C++17 标准 ISO/IEC14882:7.10 浮点积分转换 [conv.fpint]

  1. 浮点类型的纯右值可以转换为 integer 类型的纯右值。 转换截断; 也就是说,小数部分被丢弃。 如果截断的值不能在目标类型中表示,则行为未定义。 ...

所以 double 可以轻松容纳 2^64,这就是为什么第 1 行可以如此轻松地打印出来的原因。 但是在 size_t 中表示太多了,所以结果是 UB。 所以无论我们第 2 行的结果是什么,都是无关紧要的,因为它是 UB。

好的,但是如果任何随机结果都可以,为什么 UB 结果正好是一半? 首先,结果来自 MSVC。 Clang 或其他编译器可能会提供任何其他 UB 结果。

但是让我们看看“一半”的结果,因为它很容易。

   Trying to add 1 to the largest  
   11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 is 18446744073709551615
   would if only integrals would be involved lead to, 
 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
   but thats not possible since the bit does not exist and it is not integral but double datatype and 
   hence UB, so accidentially the result is
   10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 which is 9223372036854775808
   so exactly half of the naively expected or 2^63.

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM