首页 / 知识库 / 进大厂必备计算机基础 / 计算机组成原理

08 - 负数和小数在计算机里长什么样

第 01 章说过,整数可以直接用二进制表示。但计算机里只有 0 和 1,没有「负号」、也没有「小数点」这种符号。那 -53.14 是怎么存的?这一章讲清楚思路,你会顺便理解一个困扰无数程序员的问题:为什么 0.1 + 0.2 ≠ 0.3

负数:用一个 bit 当符号,但没那么简单

最朴素的想法:拿最高位当「符号位」,0 表示正、1 表示负。这叫「原码」。听起来完美,但有个麻烦:这样一来 +0-0 变成两个不同的编码,而且做减法时电路要额外判断符号,很别扭。

计算机实际用的是一种更聪明的方案,叫补码(Two’s Complement)。你不需要会手算补码,只要理解它为什么值得

补码的妙处在于——用它表示负数后,减法可以直接当加法来做,加减法用同一套电路就能搞定,不用为减法单独设计电路。

举个直觉例子:时钟是「模 12」的系统。把时针从 3 点往回拨 4 小时(减 4),结果是 11 点;而往前拨 8 小时(加 8),结果也是 11 点。所以在时钟这个系统里,「减 4」和「加 8」效果一样。补码就是利用类似的原理,把「减一个数」变成「加另一个数」。

  • 好处:硬件大大简化(一套加法器通吃加减法),也没有了 +0/-0 的尴尬。
  • 你只要记住结论:计算机里的负整数用「补码」存储,目的是让减法能复用加法电路。

小数:用「浮点数」,像科学计数法

小数更麻烦,因为「小数点」在哪是浮动的。计算机借用了科学计数法的思路。

回忆科学计数法:314.159 可以写成 3.14159 × 10²。它由三部分组成:符号、有效数字(尾数)、指数(小数点该往哪挪、挪几位)。

计算机里的**浮点数(Floating Point)**就是这个思路的二进制版,一个小数被拆成三部分存起来:

   符号位        指数部分            尾数部分
  (正还是负)   (小数点挪多少位)     (有效数字)

「浮点」这个名字就来自「小数点的位置是浮动的」。常见的 float(32 位)、double(64 位),区别就是给这三部分分配的 bit 多少不同——位数越多,能表示的范围越大、精度越高。

为什么 0.1 + 0.2 ≠ 0.3

这是浮点数最著名的坑。根本原因是:

很多十进制小数,用二进制根本无法精确表示,只能存一个「非常接近但不完全相等」的近似值。

类比一下:十进制里,1/3 = 0.33333… 除不尽,你只能写有限位的近似值 0.3333。同样地,0.1 换算成二进制是一个无限循环的小数,计算机位数有限,只能截断存一个近似值。

于是:

  • 存进去的 0.1 其实是 0.1000000000000000055…
  • 存进去的 0.2 也有微小误差
  • 两者相加,误差累积,结果就成了 0.30000000000000004,不等于精确的 0.3

这不是哪个语言的 bug,是浮点数表示方式的固有特性,几乎所有语言都这样。

实践建议(记住即可):

  • 别用 == 直接比较两个浮点数是否相等,而应判断它们的差是否小于一个极小值。
  • 涉及的计算,不要用浮点数!要么用「整数分」来算(12.34 元存成 1234 分),要么用专门的高精度十进制类型(如 Python 的 Decimal)。

小结

  • 计算机里没有负号和小数点,负数和小数都得靠「约定的编码方式」表示。
  • 负整数用补码:核心好处是让减法能复用加法电路,硬件更简单(类比时钟「减 4 = 加 8」)。
  • 小数用浮点数:借鉴科学计数法,拆成「符号 + 指数 + 尾数」三部分,小数点位置浮动。
  • 很多十进制小数(如 0.1)在二进制里无限循环,只能存近似值,所以 0.1 + 0.2 ≠ 0.3——这是固有特性。
  • 实践:别用 == 比浮点数;算钱要用整数或高精度十进制类型。

下一章 → 09 - 文字和图片是怎么变成 0/1 的 | 回到 README 目录