This commit is contained in:
krahets
2026-04-14 18:06:14 +08:00
parent 065a978848
commit e53a7f2498
93 changed files with 565 additions and 570 deletions
@@ -12,11 +12,11 @@ comments: true
В таблице из предыдущего раздела можно заметить, что все целочисленные типы могут представлять на одно отрицательное число больше, чем положительных. Например, диапазон `byte` равен $[-128, 127]$ . Это выглядит не слишком интуитивно, и внутренняя причина связана с прямым, обратным и дополнительным кодами.
Прежде всего нужно отметить, что **числа хранятся в компьютере в виде "дополнительного кода"**. Прежде чем разбирать причины такого решения, сначала дадим определения всем трем способам представления.
Прежде всего нужно отметить, что **числа хранятся в компьютере в виде «дополнительного кода»**. Прежде чем разбирать причины такого решения, сначала дадим определения всем трем способам представления.
- **Прямой код**: старший бит двоичного представления числа рассматривается как знаковый, где $0$ означает положительное число, а $1$ - отрицательное; остальные биты представляют значение числа.
- **Обратный код**: для положительного числа обратный код совпадает с прямым; для отрицательного числа он получается инверсией всех битов прямого кода, кроме знакового бита.
- **Дополнительный код**: для положительного числа дополнительный код совпадает с прямым; для отрицательного числа он получается добавлением $1$ к его обратному коду.
- **Прямой код**: старший бит двоичного представления числа рассматривается как знаковый, где $0$ означает положительное число, а $1$ - отрицательное. Остальные биты представляют значение числа.
- **Обратный код**: для положительного числа обратный код совпадает с прямым. Для отрицательного числа он получается инверсией всех битов прямого кода, кроме знакового бита.
- **Дополнительный код**: для положительного числа дополнительный код совпадает с прямым. Для отрицательного числа он получается добавлением $1$ к его обратному коду.
На рисунке 3-4 показаны способы преобразования между прямым, обратным и дополнительным кодами.
@@ -29,8 +29,8 @@ comments: true
$$
\begin{aligned}
& 1 + (-2) \newline
& \rightarrow 0000 \; 0001 + 1000 \; 0010 \newline
& = 1000 \; 0011 \newline
& \rightarrow 0000 \. 0001 + 1000 \. 0010 \newline
& = 1000 \. 0011 \newline
& \rightarrow -3
\end{aligned}
$$
@@ -40,10 +40,10 @@ $$
$$
\begin{aligned}
& 1 + (-2) \newline
& \rightarrow 0000 \; 0001 \; \text{(прямой код)} + 1000 \; 0010 \; \text{(прямой код)} \newline
& = 0000 \; 0001 \; \text{(обратный код)} + 1111 \; 1101 \; \text{(обратный код)} \newline
& = 1111 \; 1110 \; \text{(обратный код)} \newline
& = 1000 \; 0001 \; \text{(прямой код)} \newline
& \rightarrow 0000 \. 0001 \. \text{(прямой код)} + 1000 \. 0010 \. \text{(прямой код)} \newline
& = 0000 \. 0001 \. \text{(обратный код)} + 1111 \. 1101 \. \text{(обратный код)} \newline
& = 1111 \. 1110 \. \text{(обратный код)} \newline
& = 1000 \. 0001 \. \text{(прямой код)} \newline
& \rightarrow -1
\end{aligned}
$$
@@ -52,8 +52,8 @@ $$
$$
\begin{aligned}
+0 & \rightarrow 0000 \; 0000 \newline
-0 & \rightarrow 1000 \; 0000
+0 & \rightarrow 0000 \. 0000 \newline
-0 & \rightarrow 1000 \. 0000
\end{aligned}
$$
@@ -61,36 +61,36 @@ $$
$$
\begin{aligned}
-0 \rightarrow \; & 1000 \; 0000 \; \text{(прямой код)} \newline
= \; & 1111 \; 1111 \; \text{(обратный код)} \newline
= 1 \; & 0000 \; 0000 \; \text{(дополнительный код)} \newline
-0 \rightarrow \. & 1000 \. 0000 \. \text{(прямой код)} \newline
= \. & 1111 \. 1111 \. \text{(обратный код)} \newline
= 1 \. & 0000 \. 0000 \. \text{(дополнительный код)} \newline
\end{aligned}
$$
При добавлении $1$ к обратному коду отрицательного нуля возникает перенос, но длина типа `byte` составляет всего 8 бит, поэтому переполнившаяся в 9-й бит единица отбрасывается. Иными словами, **дополнительный код отрицательного нуля равен $0000 \; 0000$ и совпадает с дополнительным кодом положительного нуля**. Значит, в представлении дополнительного кода существует только один ноль, и проблема неоднозначности положительного и отрицательного нуля тем самым устраняется.
При добавлении $1$ к обратному коду отрицательного нуля возникает перенос, но длина типа `byte` составляет всего 8 бит, поэтому переполнившаяся в 9-й бит единица отбрасывается. Иными словами, **дополнительный код отрицательного нуля равен $0000 \. 0000$ и совпадает с дополнительным кодом положительного нуля**. Значит, в представлении дополнительного кода существует только один ноль, и проблема неоднозначности положительного и отрицательного нуля тем самым устраняется.
Остается последний вопрос: диапазон типа `byte` равен $[-128, 127]$ , откуда берется лишнее отрицательное число $-128$ ? Мы замечаем, что у всех целых чисел из интервала $[-127, +127]$ есть соответствующие прямой, обратный и дополнительный коды, а прямой и дополнительный коды можно преобразовывать друг в друга.
Однако **дополнительный код $1000 \; 0000$ является исключением: у него нет соответствующего прямого кода**. Согласно правилу преобразования, прямой код для этого дополнительного кода должен быть равен $0000 \; 0000$ . Это очевидное противоречие, потому что такой прямой код обозначает число $0$ , а его дополнительный код должен совпадать с ним самим. Компьютер просто определяет, что этот особый дополнительный код $1000 \; 0000$ представляет число $-128$ . На самом деле результат вычисления $(-1) + (-127)$ в дополнительном коде как раз и равен $-128$ .
Однако **дополнительный код $1000 \. 0000$ является исключением: у него нет соответствующего прямого кода**. Согласно правилу преобразования, прямой код для этого дополнительного кода должен быть равен $0000 \. 0000$ . Это очевидное противоречие, потому что такой прямой код обозначает число $0$ , а его дополнительный код должен совпадать с ним самим. Компьютер просто определяет, что этот особый дополнительный код $1000 \. 0000$ представляет число $-128$ . На самом деле результат вычисления $(-1) + (-127)$ в дополнительном коде как раз и равен $-128$ .
$$
\begin{aligned}
& (-127) + (-1) \newline
& \rightarrow 1111 \; 1111 \; \text{(прямой код)} + 1000 \; 0001 \; \text{(прямой код)} \newline
& = 1000 \; 0000 \; \text{(обратный код)} + 1111 \; 1110 \; \text{(обратный код)} \newline
& = 1000 \; 0001 \; \text{(дополнительный код)} + 1111 \; 1111 \; \text{(дополнительный код)} \newline
& = 1000 \; 0000 \; \text{(дополнительный код)} \newline
& \rightarrow 1111 \. 1111 \. \text{(прямой код)} + 1000 \. 0001 \. \text{(прямой код)} \newline
& = 1000 \. 0000 \. \text{(обратный код)} + 1111 \. 1110 \. \text{(обратный код)} \newline
& = 1000 \. 0001 \. \text{(дополнительный код)} + 1111 \. 1111 \. \text{(дополнительный код)} \newline
& = 1000 \. 0000 \. \text{(дополнительный код)} \newline
& \rightarrow -128
\end{aligned}
$$
Ты, вероятно, уже заметил, что все приведенные выше вычисления были операциями сложения. Это указывает на важный факт: **аппаратные схемы внутри компьютера в основном проектируются на основе операций сложения**. Причина в том, что сложение по сравнению с другими операциями (например умножением, делением и вычитанием) проще реализуется на аппаратном уровне, легче распараллеливается и выполняется быстрее.
Обрати внимание: это не означает, что компьютер умеет только складывать. **Комбинируя сложение с некоторыми базовыми логическими операциями, компьютер может реализовать и другие математические операции**. Например, вычитание $a - b$ можно преобразовать в сложение $a + (-b)$ ; умножение и деление можно свести к многократному сложению или вычитанию.
Обрати внимание: это не означает, что компьютер умеет только складывать. **Комбинируя сложение с некоторыми базовыми логическими операциями, компьютер может реализовать и другие математические операции**. Например, вычитание $a - b$ можно преобразовать в сложение $a + (-b)$. Умножение и деление можно свести к многократному сложению или вычитанию.
Теперь можно подвести итог, почему компьютеры используют дополнительный код: с представлением в дополнительном коде компьютер может использовать одни и те же схемы и операции для сложения положительных и отрицательных чисел, без необходимости проектировать специальные аппаратные схемы для вычитания и без особой обработки неоднозначности положительного и отрицательного нуля. Это значительно упрощает аппаратную архитектуру и повышает эффективность вычислений.
Идея дополнительного кода очень изящна; из-за ограничений по объему мы на этом остановимся. Если тебе интересно, стоит изучить эту тему глубже.
Идея дополнительного кода очень изящна. Из-за ограничений по объему мы на этом остановимся. Если тебе интересно, стоит изучить эту тему глубже.
## 3.3.2   Кодирование чисел с плавающей точкой
@@ -133,15 +133,15 @@ $$
<p align="center"> Рисунок 3-5 &nbsp; Пример вычисления float по стандарту IEEE 754 </p>
Посмотрим на рисунок 3-5: если взять пример $\mathrm{S} = 0$ , $\mathrm{E} = 124$ , $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ , то получим:
Как видно на рисунке 3-5, если взять пример $\mathrm{S} = 0$ , $\mathrm{E} = 124$ , $\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ , то получим:
$$
\text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875
$$
Теперь мы можем ответить на исходный вопрос: **в представлении `float` присутствуют биты экспоненты, поэтому его диапазон значений намного больше, чем у `int`**. Согласно приведенным выше вычислениям, максимально возможное положительное число для `float` равно $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ; если изменить бит знака, получим минимальное отрицательное число.
Теперь мы можем ответить на исходный вопрос: **в представлении `float` присутствуют биты экспоненты, поэтому его диапазон значений намного больше, чем у `int`**. Согласно приведенным выше вычислениям, максимально возможное положительное число для `float` равно $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$. Если изменить бит знака, получим минимальное отрицательное число.
**Хотя число с плавающей точкой `float` расширяет диапазон значений, побочным эффектом становится потеря точности**. Целочисленный тип `int` использует все 32 бита для представления числа, и числа распределены равномерно; а из-за существования битов экспоненты у `float` чем больше число, тем больше обычно становится разница между двумя соседними представимыми значениями.
**Хотя число с плавающей точкой `float` расширяет диапазон значений, побочным эффектом становится потеря точности**. Целочисленный тип `int` использует все 32 бита для представления числа, и числа распределены равномерно. А из-за существования битов экспоненты у `float` чем больше число, тем больше обычно становится разница между двумя соседними представимыми значениями.
Как показано в таблице 3-2, значения экспоненты $\mathrm{E} = 0$ и $\mathrm{E} = 255$ имеют специальный смысл и **используются для представления нуля, бесконечности, $\mathrm{NaN}$ и т.д.**