一个整数在内存中是如何存储的?

11

这可能是任何人都会问的最愚蠢的问题,但无论如何,我希望我能找到一个清晰的答案。

我的问题是 - 计算机内存中如何存储整数?

在c#中,整数大小为32位。MSDN表示,我们可以在整数变量中存储从-2,147,483,648到2,147,483,647的数字。

按照我的理解,一个位只能存储两个值,即0和1。如果我只能在一个位上存储0或1,那么我如何能够在一个位上存储2到9的数字呢?

更精确地说,假设我有这段代码int x = 5;它将如何在内存中表示,换句话说,5是如何转换为0和1,背后的约定是什么?


作为提示,您可能想搜索“二进制如何工作”,“如何将十进制转换为二进制”等等。 :-) - John Parker
1
可能是[“2的补码”是什么?]的重复问题。(https://dev59.com/6XNA5IYBdhLWcg3wNq8y) - John Kugelman
1
我们每天使用的数字只能是0到9,但我们能够表示更大的数字。您在帖子中刚刚做到了!这完全取决于数字相对于小数点的位置。 - Steve
4个回答

14

它以二进制(基数为2)表示。 了解有关数字基数的更多信息。在基数2中,您只需要2个不同的符号来表示一个数字。我们通常使用符号01。在我们通常的基数中,我们使用10个不同的符号来表示所有数字,012,... 8,和9

为比较起见,考虑一个在我们通常系统中不能匹配的数字。像14这样的数字。我们没有14的符号,所以我们如何表示它?很容易,我们只需将两个符号14组合在一起。基数10中的14意味着1*10^1 + 4*10^0

在基数2(二进制)中,1110意味着1*2^3 + 1*2^2 + 1*2^1 + 0*2^0 = 8 + 4 + 2 + 0 = 14。因此,尽管两种基数都没有足够的符号来用单个符号表示14,但我们仍然可以在这两种基数中表示它。

在另一种常用的基数16,也称为十六进制,我们有足够的符号只使用一个符号来表示14。您通常会看到用十六进制符号e来写14

对于负整数,我们使用方便的表示方式称为二补码,它是补码(所有的1都翻转成0,所有的0都翻转成1)并加上1。

这样做的两个主要原因如下:

  • 我们可以通过查看一个比特位,也就是我们使用的32个比特中最高有效位,来立即确定一个数字是正数还是负数。

  • 从数学上讲,x - y = x + -y 是正确的,使用普通的加法方式,就像你在小学学习的一样。这意味着如果处理器已经有了加法运算,它们不需要做任何特殊的事情来实现减法。它们可以简单地找到y的二进制补码(记住,翻转所有位并加上1),然后使用它们已经拥有的加法电路将xy相加,而不是为减法设计专门的电路。


12

这绝不是一个愚蠢的问题。

我们先从 uint 开始,因为它稍微容易一些。约定如下:

  • 你有 32 位在一个 uint 中。每个位都分配了一个从 0 到 31 的数字。按照惯例,最右边的位是 0,最左边的位是 31。
  • 取每个位数并将其提高到 2 的幂次方,然后将其乘以该位的值。所以如果第三位是 1,则为 1 x 23。如果第十二位是 0,则为 0 x 212
  • 将所有这些数字加起来。这就是该值。

所以五将会是 00000000000000000000000000000101,因为 5 = 1 x 20 + 0 x 21 + 1 x 22 + ... 其余的位都是零。

那是一个 uint。int 的约定是:

  • 将该值计算为 uint
  • 如果该值大于或等于 0 并且严格小于 231,则完成。int 和 uint 值相同。
  • 否则,从 uint 值中 减去 232,这就是 int 值。

这可能看起来很奇怪。我们使用它是因为事实证明,可以非常快速地构建执行此格式中的算术运算的芯片。


换句话说,uints使用基数2。与基数10相比,其中一个人将205表示为205,因为205 = 2*10^2 + 0*10^1 + 5*10^0 - Brian

4
二进制的工作方式如下(基于32位):
   1  1  1  1 | 1  1  1  1 | 1  1  1  1 | 1  1  1  1 | 1 1 1 1 | 1 1 1 1 | 1 1 1 1 | 1 1 1 1

2^ 31 30 29 28  27 26 25 24  23 22 21 20  19 18 17 16......................................0
   x

x = 符号位(如果为1,则为负数,如果为0,则为正数)

因此,最大的数字是0111111111............1(除了负数位,其他都为1),它等于2^30 + 2 ^29 + 2^28 +........+2^1 + 2^0,即2,147,483,647。

最小的数字是1000000.........0,意味着-2^31或-2147483648。


二进制补码没有符号位。例如,零没有符号。 - Joey
1
它肯定有一个符号位。这里有一个 Stack Overflow 问题讨论它的工作原理:https://dev59.com/6XNA5IYBdhLWcg3wNq8y。 - Tricky12
1
关于“符号位”的问题...这取决于你的定义。在1的补码和2的补码中,有一个比特可以用来测试数字是否为负数,通常被称为表示中的“符号位”。然而,某些定义要求你能够通过简单地翻转符号位来对一个数字取反...而这显然无法在1的补码或2的补码表示中实现。 - Corey

1

这就是高级语言的后果吗!?呃!

正如其他人所说,这是一种二进制计数系统。人类大多数情况下天生是10进制计数的,尽管出于某种原因,时间是基于60进制的,而在13进制中6×9=42。Alan Turing显然擅长17进制的心算。

计算机采用二进制是因为电子元件很容易处于开或关状态,分别表示1和0,这是二进制所需要的全部。你可以以这样的方式构建电子元件,即让其处于开、关或介于两者之间的状态。这样就有了3个状态,可以进行三进制运算(与二进制运算不同)。然而,由于更难区分这三个状态之间的差异,而且电子元件更加复杂,因此可靠性降低了。更多的状态会导致可靠性更差。

尽管如此,在多层次单元闪存中仍在使用。在其中,每个存储单元表示开、关和若干中间值。这提高了容量(每个单元可以存储多个位),但对于可靠性来说是个坏消息。这种芯片用于固态驱动器中,这些驱动器非常接近完全不可靠的边缘,以最大限度地提高容量。


15
以贬损性评论为开场是不必要的。有一天你还不知道这件事,而第二天你就知道了。今天是迈克的日子。 - Eric Lippert
有趣的事实:在某些领域中普遍使用60进制的起源存在争议,但其中一个主要起源是巴比伦数学,其计数符号使用了10进制和60进制的组合,以十为单位计数,但以60而不是100作为更高数字的单位。 - UpQuark
是的,这就是高级语言所带来的效果。我还可以指出,无需知道触发器电路中电压水平代表一个一也是语言所导致的。你是否感到不知道翻转器中电压水平而错失了什么? - Eric Lippert
@EricLippert,好的,让我们看看。对于很多程序员来说,这并没有什么区别。问题在于,有相当多的编程需要对源代码的电子后果(以及功率和热量;这些对像Google等公司非常重要)有良好的了解。例如,有多少程序员知道内存分配实际上是什么,为什么在很多情况下你可能想尽可能避免它?了解你的硬件,这样当它真正重要时,你就可以编写有用的代码。 - bazza
@EricLippert; 我来自一个需要在TTY上输出“Hello, World”之前就必须掌握这些知识的时代。我们当时学到的关于“计算机实际是什么”的一切对于有野心提升操作的任何人都具有巨大价值。此外,作为一个老古董,我发现有人能够在不了解一些基本东西的情况下走得如此远,这让我感到惊讶。 - bazza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接