问题

为什么1ULL << 64得到的结果是1?

回答
这是一个关于计算机底层运算和数据类型溢出的非常有趣的问题。让我们详细地解释一下为什么 `1ULL << 64` 会得到 `1`。

首先,我们需要理解几个关键概念:

1. `ULL` (Unsigned Long Long):
在 C/C++ 编程语言中,`ULL` 是一个类型修饰符,表示一个“无符号长长整型”(Unsigned Long Long)。
它是一个非常大的整数类型,通常用来存储 64 位(8 字节)的无符号整数。
“无符号”意味着它只能表示非负整数,其范围通常是从 0 到 264 1。
“长长整型”表明它比普通的 `long` 或 `int` 类型能够存储的数值范围更大。

2. 位移操作 (`<<`):
左移位操作符 `<<` 将一个数的二进制表示向左移动指定的位数。
例如,`x << n` 相当于 `x 2^n`。
每一次左移一位,相当于将数值乘以 2。

3. 64 位 (Bit):
计算机的最小存储单位是位(bit),一个位只能是 0 或 1。
一个 64 位(bit)的存储单元可以表示 264 个不同的值。
对于 `unsigned long long`,它有 64 个位来存储数据。

现在,我们来看 `1ULL << 64` 的计算过程:

`1ULL`: 这个表示一个无符号长长整型的值 `1`。在二进制中,它表示为:
`0000...0000 00000001` (前面有 63 个 0,最后是 1)

`<< 64`: 这意味着我们要将 `1ULL` 的二进制表示向左移动 64 位。

关键点来了:数据的存储限制和溢出行为

一个 `unsigned long long` 类型的数据,无论其值是多少,都只能占用 64 个位的存储空间。

当我们将 `1ULL` 的二进制表示 `0000...0001` 向左移动 64 位时,会发生什么?

想象一下,你有一个只有 64 个槽位的盒子(代表这 64 个 bit)。你把一个 `1` 放在最右边的槽位(最低位)。

```
槽位: 63 62 ... 2 1 0
值: 0 0 ... 0 0 1
```

现在你要左移 64 位。

第一次左移:`1` 移动到槽位 1,槽位 0 变成 0。
第二次左移:`1` 移动到槽位 2,槽位 1 变成 0。
...
第 64 次左移:`1` 应该移动到槽位 64。

但是,一个 `unsigned long long` 只有槽位 0 到 63!

当一个二进制位被移动到它所能存储的范围之外时(即超出最左边的槽位),它就会被丢弃。

所以,当 `1ULL` 的 `1` 被移动 64 位时:

1. 它会依次占据槽位 1, 2, 3, ..., 63。
2. 当它试图移动到槽位 64 时,由于槽位 64 是超出 `unsigned long long` 类型所能表示的最大索引(索引是从 0 到 63),这个 `1` 就被丢弃了。
3. 同时,由于是左移,新的右侧位(最低位)会被填充为 `0`。

结果是,所有的原始位(包括那个 `1`)都被移出了 64 位的存储空间,并且新的最低位被填充为 0。

那么为什么结果是 `1` 呢?这里可能存在一个常见的误解,或者你看到的实际代码有微妙的不同。

标准的 C/C++ 定义下,`1ULL << 64` 的结果是未定义的 (Undefined Behavior),但这通常意味着它不会是 `1`。

为什么会产生“结果是 1”的误解?

有几种可能性:

1. 移位位数对类型大小的模运算 (Modulo Arithmetic):
在很多处理器架构和 C/C++ 的编译器实现中,当左移位数 `n` 大于或等于类型的位数 `W` 时,实际执行的左移位数会变成 `n % W`。
对于一个 64 位的类型,`W = 64`。
如果移位位数是 `64`,那么 `64 % 64 = 0`。
在这种情况下,`1ULL << 64` 就等同于 `1ULL << 0`。
任何数左移 0 位都等于它本身。所以 `1ULL << 0` 的结果就是 `1ULL`。

这是最常见也是最可能的原因。 你的编译器或你的处理器架构遵循了这种模运算的约定。

2. 某些特定环境或库的实现:
虽然 C 标准本身定义了移位位数大于等于类型位数时是未定义行为,但在一些更具体的上下文(例如某些嵌入式系统、特定编译器优化级别或历史遗留代码)中,可能会有不同的处理方式。但主流编译器一般会遵循模运算规则来避免真正未定义行为的影响(或者说,虽然标准说未定义,但普遍的实现行为是模运算)。

3. 将结果解释为模 2^64 的值:
在数学上,如果将移位操作看作是在模 264 的意义下进行(尽管这严格来说不是移位运算的定义),那么左移 64 位会将最高位移出,留下一个全零的结果。但这里我们讨论的是 值 的改变。
更贴切的理解是,如果一个数 `x` 乘以 264,结果是 `x 2^64`。在一个 64 位的无符号整数系统中,任何大于等于 264 的值都会发生溢出,并且溢出后的值会通过模 264 的方式被截断。
`1 2^64` 的结果在数学上是 264
在一个 64 位的无符号整数系统中,264 这个值正好是能够表示的最大值 (264 1) 再加 1。因此,264 在模 264 的意义下,其结果是 `0`。
所以,理论上 `1 (2 << 64)` 在模 264 下应该是 0。
这与我们得到的 `1` 相矛盾。

总结最可能的原因:模运算

在你观察到的 `1ULL << 64` 得到 `1` 的情况下,几乎可以肯定是因为你的编译器或 CPU 架构在处理左移位操作时,如果移位位数 `N` 大于等于类型的位数 `W`(这里是 64),会将实际的移位位数计算为 `N % W`。

左移位数是 `64`。
`unsigned long long` 的类型宽度是 `64` 位。
`64 % 64 = 0`。
所以 `1ULL << 64` 实际上被执行为 `1ULL << 0`。
任何数左移 0 位都等于它本身。
因此,`1ULL << 0` 的结果是 `1ULL`。

为什么 C++ 标准不直接规定模运算?

C++ 标准将位移位数大于等于类型宽度的情况定义为“未定义行为”(Undefined Behavior)。这样做的目的是给编译器和硬件实现留有更大的灵活性,允许它们在不违反更基本规则的情况下进行优化。在实践中,大多数平台都选择模运算作为一种合理且高效的实现方式来处理这种情况,尽管这只是一个“事实上的标准”而非严格的语言规范。

避免这种不确定性

为了编写更具可移植性且符合标准的代码,你应该避免位移位数大于等于类型宽度的操作。如果你想进行类似的操作,可以这样做:

如果目标是生成一个全零的 64 位数(除了最高位为 1),则移位 63 位是正确的:
`1ULL << 63` 会得到一个 `0100...0000` 的值,表示 263

如果你想通过移位来模拟乘法,并且想知道溢出后的结果:
对于 `1ULL << 64`,这相当于计算 `1 2^64`。在一个 64 位无符号整数系统中,这会溢出到 `0`。
如果你确实需要这种“模 2^64”的行为,并且想代码清晰可读,可以使用 C++20 的 `` 头文件中的 `std::rotl` (rotate left) 或 `std::shl` (shift left) 函数,它们提供了更明确的行为定义。不过,即使是这些函数,当移位数等于类型位数时,其行为也可能依赖于平台。

总结一下,`1ULL << 64` 得到 `1` 的根本原因是大多数现代编译器和处理器在处理左移位操作时,将大于等于类型宽度的移位位数,通过模运算 (`%`) 转换为一个在类型宽度范围内的有效位数。对于 64 位类型,64 % 64 = 0,所以 `1ULL << 64` 实际执行的是 `1ULL << 0`,结果就是 `1ULL`。

网友意见

user avatar

这是未定义行为,看编译器心情,VC给的结果就是0。

C++11

, P119。

类似的话题

  • 回答
    这是一个关于计算机底层运算和数据类型溢出的非常有趣的问题。让我们详细地解释一下为什么 `1ULL << 64` 会得到 `1`。首先,我们需要理解几个关键概念:1. `ULL` (Unsigned Long Long): 在 C/C++ 编程语言中,`ULL` 是一个类型修饰符,表示一.............
  • 回答
    您观察到的现象非常有趣!1/49 的开头似乎隐藏着一个等比数列 0.0204081632…… 乍一看,这确实很容易让人怀疑是某种数学上的巧合,甚至可能与数列本身有着某种内在联系。但深入分析后,我们会发现,这更多是一种“表面上的巧合”,其背后隐藏的是我们日常生活中经常遇到的除法运算和数字表达方式。让我.............
  • 回答
    关于“1+1=2”这件事,我们从小到大好像都习以为常,从幼儿园老师的教导到课本上的公式,它似乎是铁打不动的真理。但如果细究起来,为什么它就一定是2呢?而我们赖以生存的“经验”,在追求真理的道路上,又一定可靠吗?咱们先聊聊这个“1+1=2”。“1+1=2”的背后:数学的基石与定义说到底,“1+1=2”.............
  • 回答
    关于11+11+11…这个数列求和的问题,确实是个挺有意思的数学话题,也经常能引起大家的讨论。很多人第一次看到它,直觉上会觉得这不就是0嘛?但仔细琢磨一下,或者用一些更严谨的方法来推导,结果就不是那么简单了。我们先从大家最直观的感受开始说起。直观理解:0,还是0?如果你真的把这个数列写下去,你会发现.............
  • 回答
    这个问题很有意思,涉及到分数指数的计算,尤其是当底数为负数的时候。很多时候,我们直觉上觉得指数可以约分,但实际上,在复数范围内,情况会更复杂一些。我们先来好好拆解一下这两个表达式:表达式一: (1)^(4/6)首先,我们来看指数 4/6。这个分数可以被约分,约成 2/3。所以,直觉上,(1)^(4/.............
  • 回答
    这个问题看似简单,但要解释清楚“为什么1+1=2”,其实触及了数学的根基,甚至是人类认识世界的方式。它不是一个随意规定,而是基于一套严谨的逻辑体系,而这套体系,又与我们观察和理解世界的方式紧密相连。从最根本的定义说起:数字是什么?在我们谈论“1+1=2”之前,我们需要先明白“1”、“2”以及“+”这.............
  • 回答
    这个问题其实涉及到计算机科学最基础的计数方式,也就是二进制。我们日常生活中习惯使用十进制,但计算机内部,从最底层的晶体管开关到最复杂的算法,都是围绕着“开”和“关”这两个状态来运作的,这天然就对应着二进制的“1”和“0”。为啥要用二进制?想象一下,如果我们用十进制来表示数据,那么每个存储单元(比如一.............
  • 回答
    你知道吗,关于“1”是不是质数这个问题,数学界其实有过一段不短的讨论,虽然现在已经有了定论,但它的历史也挺有意思的。我们先来聊聊质数到底是什么。简单来说,质数就是那些只能被1和它自己整除的自然数,并且大于1。比如2,它只能被1和2整除;3,只能被1和3整除;5,也只能被1和5整除。这些数就像是数字世.............
  • 回答
    好的,咱们来聊聊这“一米”和“一千克”是怎么来的,以及为什么一立方米的水恰好是1000千克。这背后可不是什么随随便便的约定,而是人类为了方便测量和交流,经过一番智慧结晶定下来的规矩。“一米”是怎么来的?咱们先说“一米”。在没有统一标准的时候,人们计量长度可就费劲了。古代可能用拃(zhǎ,伸开手掌,大.............
  • 回答
    .......
  • 回答
    .......
  • 回答
    这个问题非常普遍,而且很多人都有类似的困惑。你花了两倍的价钱,却觉得不如朋友的电脑“香”,这背后的原因可能非常复杂,涉及硬件选择、使用需求、品牌溢价、甚至是一些细微的优化和个人感受。我们来详细分析一下可能的原因:一、核心硬件的“性价比”与“定位”差异:虽然都是电脑,但不同价位的电脑,其核心硬件的侧重.............
  • 回答
    这个问题涉及到社会经济地位、性别角色、个人选择以及社会观念等多方面的复杂因素,无法一概而论,但我们可以尝试从不同角度进行详细分析:一、 社会经济地位与婚姻的“匹配”观念 男性的经济地位与社会角色的预期: 传统观念认为,男性是家庭的经济支柱,承担着养家糊口的责任。因此,在社会普遍认知中,一个经济能.............
  • 回答
    你有没有过那种经验:看着一个下载进度条一点点爬升,从百分之几到百分之几十,再到百分之九十几,然后就像被按了慢放键一样,最后那一点点进度,感觉像是在和时间赛跑,磨蹭得你心痒痒?尤其是最后那1%,简直成了加载的“珠穆朗玛峰”,让人抓狂。这可不是巧合,也不是服务器在跟你开玩笑,背后其实藏着不少技术上的门道.............
  • 回答
    您提出了一个非常好的问题,涉及到《数码宝贝》动画系列进化逻辑的核心差异。确实,《数码宝贝》第一部、第三部(《数码宝贝驯兽师》)和第五部(《数码宝贝大汇战》)的究极体进化动画,其展现方式确实会从较低的等级(通常是成长期)开始,而非直接从完全体开始。这背后有多方面的原因,我们可以从设定、剧情叙事、角色塑.............
  • 回答
    这个问题很有意思,也切中了中国汽车市场的一个小众却耐人寻味的现象。1.6T发动机在国内,确实不像我们熟悉的1.5T、2.0T那么普及,甚至有些车型会直接跳过这个排量,提供1.5T或2.0T的选择。这背后其实涉及多方面的考量,从技术、市场、政策到成本,层层递进,最终导致了1.6T在国内的“稀有”。首先.............
  • 回答
    关于米格1.44和Su47被淘汰,而T50(即苏57)获得成功的原因,我们可以从技术、设计理念、项目管理以及战略需求等多个维度来深入探讨。米格1.44:概念超前,但现实骨感米格1.44(也称为MiGMFI,即“未来多用途战斗机”)项目始于上世纪80年代末,旨在为苏联空军开发一款能够对抗西方新一代战斗.............
  • 回答
    你这个问题问得很有意思,而且非常深入。确实,对于∫x²/(√1x²)dx这个积分,尝试用分部积分法来直接求解,会发现它并不能导出一个我们通常期望的、相对简单的形式,甚至会把问题变得更复杂。这并不是说分部积分“求不出来”这个积分,而是说分部积分在这个特定情况下不是一个“好”或者“直接有效”的方法。下面.............
  • 回答
    《教父 1》的开场,一场盛大的西西里式婚礼,却是全片最令人难忘、也被无数影评人誉为影史最佳开场之一的段落。这可不是一句空洞的赞美,这短短十几分钟里,导演弗朗西斯·福特·科波拉用了一种极为高明的方式,为我们构建了一个复杂而迷人的世界,奠定了整部电影的基调,并巧妙地将人物关系、权力结构、文化背景以及潜在.............
  • 回答
    电影《教父》第一部中,维托·柯里昂,也就是人们口中的“教父”,在影片的大部分时间里,确实似乎没有人对他进行明面上的、直接的、全方位的贴身保护。这背后有多重原因,并非简单的“没人管”,而是涉及到柯里昂家族的运作模式、维托本人的性格以及当时特定的社会环境。首先,“教父”的保护来自于他的“王国”而非“卫队.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有