Вы когда-нибудь задумывались, как компьютеры работают с числами с плавающей запятой? Я имею в виду - где десятичная точка? Что, если вас спросят на собеседовании?
Стандарт IEEE 754 для чисел с плавающей запятой определяет, как нецелочисленные значения кодируются в типах фиксированного размера, таких как число с плавающей запятой C ++ и число JavaScript. Это дает нам пять разных форматов, но не беспокойтесь, все они основаны на одной концепции. В остальной части статьи я назову его IEEE 754.
Если вы ошеломлены, читая все различные приемы, используемые в IEEE 754 - не волнуйтесь, в конце я привел достаточно примеров, чтобы все уладилось в вашей голове.
Концепция
Так же, как целые числа могут быть записаны в любой «базе», нецелые значения также могут быть записаны в любой базе.
5.1 = 1(2²) + 0(2¹) + 1(2⁰) + 1/2¹ = 1011.1
Точно так же мы можем написать 3,25 = 11,01, 8,75 = 1000,11. Значения после запятой (это уже не десятичная точка) умножаются на отрицательную степень двойки.
IEEE 754 основан на этой методике. Чтобы преобразовать любое значение в формат IEEE 754, мы должны выполнить следующие шаги:
- Запишите число в двоичной форме с точкой счисления.
- Отформатируйте его в экспоненциальном представлении так, чтобы перед точкой счисления ставилась только одна цифра.
- Кодируйте различные компоненты в соответствии с выбранным форматом IEEE.
Например, возьмем значение 934893.109375:
- 934893.109375 можно точно представить (подробнее об этом позже) в двоичной форме как 11100100001111101101.000111.
- Научная форма: 1.1100100001111101101000111 x 2 ^ -19
ПРИМЕЧАНИЕ. Некоторые десятичные значения не могут быть представлены точно с основанием 2, так же как треть не может быть записана точно с основанием 10. Однако вы можете приблизить 1/3 к 0,33333333333. Точно так же значение может быть приблизительно равно основанию 2 (а не быть точным). Например, 1,9 в двоичном формате приблизительно равно 1,11100110011001100110011 (обратите внимание на повторяющееся 0011, потому что 1,9 является рациональным значением, а точное значение будет представлено бесконечным его повторением).
Формат
IEEE 754 определяет три компонента, которые записываются в 16/32/64 или более -битное значение: бит знака, показатель степени и мантисса.
Эти компоненты написаны в следующем порядке:
- Знаковый бит (s): знаковый бит имеет значение 0 для положительных значений и 1 для отрицательных значений.
- Показатель (e) : Это показатель степени, который мы получаем в научной форме.
- Мантисса (m) : мантисса или мантисса - это коэффициент, записанный в научной форме, только без точки счисления. Таким образом, мантисса 3,25 = 11,01 будет 1101 или 13.
Чтобы вернуть наше значение из IEEE 754: V = s x (m ^ e)
Ширина каждого формата фиксирована, как и ширина мантиссы и экспонент. ширина мантиссы определяет точность, а ширина экспоненты определяет диапазон значения.
Теперь IEEE 754 также использует некоторые приемы для кодирования реальных чисел, которые я перечислил ниже под разными заголовками:
- Экспонентное смещение
- Соглашение о начальных битах
- Субнормальные числа
- ± Бесконечность и NaN
- ± Ноль (и примеры в ECMAScript)
Показатель смещения
Показатель e может быть отрицательным, и для поддержки отрицательных чисел IEEE 754 определяет смещение. Смещение добавляется к экспоненте, чтобы получить фактическую закодированную экспоненту. Например, формат binary32 предоставляет 8 бит для показателей степени, где смещение составляет 2⁷-1 = 127 в поле экспоненты. Таким образом, -1 будет закодирован как -1 + 127 = 128, а показатель степени +5 будет закодирован как 5 + 127 = 132.
Смещение выбирается таким образом, чтобы наименьший показатель степени кодировался как 1, а наибольший показатель экспоненты кодировался как 2⁸ - 2 = 254 (в двоичном формате 32). Это объясняет, почему emin равен -126, а emax равен +127.
ПРИМЕЧАНИЕ. Вы могли заметить, что значения 0 и 2⁸ - 1 не указаны. Если показатель степени закодирован как ноль, то представленное число равно ± ∞ или NaN. Если все биты экспоненты равны единице (т. Е. 2⁸ - 1 = 255), то представленное число является специальным субнормальным (подробнее об этом позже).
Соглашение о ведущих битах
Самая левая цифра любого числа, записанного в экспоненциальном представлении, никогда не равна нулю (если только само число не равно 0). Если вы когда-нибудь застряли с 0 слева, вам придется уменьшить показатель степени. Например,
0.12 x 10² = 1.2 x 10¹
Поскольку мы работаем с основанием два, и ведущая цифра не может быть нулем: это означает, что ведущая цифра должна быть - 1 и только 1. Этот факт используется IEEE 754, и ведущий бит исключается из закодированной мантиссы.
Субнормальные числа
IEEE 754 определяет два типа чисел: нормальные и субнормальные. Нормальные числа на самом деле нормальные - они могут быть представлены в формате m x 2 ^ e, где e равно emin ≤ e ≤ emax. Однако, если e падает ниже emin, то IEEE 754 называет их субнормальными.
Поскольку закодированный e не может опускаться ниже 0, фактическая экспонента для субнормальных чисел всегда равна -127. Представленные ниже экспоненты могут быть представлены нарушением соглашения о начальных битах и добавлением нулей слева от мантиссы. Это приводит к потере точности мантиссы (поскольку ведущие нули приводят к падению самых правых битов).
// emin = -126, width of mantissa = 24 bits // 1. NORMAL Number, V = 2^-126 Encoded: m = 00000000000000000000000, e = 1 Actual: m = 100000000000000000000000, e = -126 V = 2^-126 // 2. SUBNORMAL Number, V = 2^-127 Encoded: m = 10000000000000000000000, e = 0 (e must be 0) Actual: m = 10000000000000000000000, e = -127 The leading bit convention doesn't work in subnormal numbers, where e = 0. This means that the encoded mantissa is the actual mantissa. The power e (actual) is always -127. The mantissa's leading bit could be 0. See the example below. // 3. SUBNORMAL Number, V = 2^-129 Encoded: m = 00100000000000000000000, e = 0 (subnormal) Actual: m = 00100000000000000000000, e = -127 V = 0.0100000000000000000000 x 2^-127 = 0.25 * 2^-127 = 2^-129
± Бесконечность и NaN
Показатель экспоненты имел два специальных значения: 0 и 2⁸-1 (где 8 на самом деле является шириной экспоненты в binary32). Первый был для субнормальных чисел, а второй - для «особых» значений. 2⁸-1 также является значением, когда все биты экспоненты равны единице.
- Если значение мантиссы равно 0, то представленное число является положительной или отрицательной бесконечностью. Знак определяется битом знака.
- Значение мантиссы не равно нулю, тогда представленное число фактически не является числом или NaN. Есть два типа NaN - сигнальные и тихие. Тип определяется значением мантиссы, и в этой статье это не рассматривается. Сигнальный NaN используется для завершения любой числовой операции, в то время как тихий NaN позволяет продолжить операцию. По моему опыту, вам никогда не нужно будет различать ч / б эти NaN. Скорее всего, они вам бесполезны.
± Нулевой случай
Удивительно, что в IEEE 754 есть два нуля - положительный и отрицательный. Для нас с вами они идентичны. Любая операция с +0 даст тот же результат, если вместо этого используется -0, или это правда? Нет, это не так.
1 / ∞ = 0, а также 1 / -∞ = 0, тогда 1 / (1 / ∞) = 1/0 = ∞ и 1 / (1 / -∞) = ∞. Знак не сохраняется, если мы используем только один положительный ноль в приведенных выше уравнениях. Это решается с помощью ± 0. 1 / -∞ = -0, тогда 1 / (1 / -∞) = - ∞.
Опять же, если используется только 0: тогда 4 / ∞ = 0 и -4 / ∞ = 0. Однако использование ± 0 приводит к: 4 / ∞ = + 0 и -4 / ∞ = -0.
Однако IEEE 754 требует, чтобы любое сравнение ч / б +0 и -0 возвращало положительный результат. Другими словами, +0 == -0 верно.
Большинство языков скрывают от вас значения +0 и -0, и вы не сможете различить их напрямую (это можно было бы сделать, если бы вы разделили на ноль и проверили результат на ± ∞). Однако JavaScript является особенным и предоставляет метод Object.is(arg1, arg2)
, который будет различать ч / б +0 и -0.
Object.is(+0, -0);// false
Примеры
Я обещал очистить ваш мозг от всей путаницы своими примерами.
// All examples use binary32 here // 1. Encode 127872.12781278 in IEEE 754 Step 1: Write in binary notation 127872.12781278 = 11111001110000000.0010000 (24-bits) Step 2: Write in scientific notation 1.11110011100000000010000 x 2^16 Step 3: Encode m(encoded) = 11110011100000000010000 (23-bits only) e(encoded) = 16+127 = 143 = 10001111 (sign)(e)(m) = 0 10001111 11110011100000000010000(32-bits) // 2. Encode (-1.25 x 2^-130) in IEEE 754 Step 1: Write in binary notation (excluding sign here) 1.25x2^-130 = 1.01 x 2^-130 (shift by 130 right to remove scale) Step 2: Already done!!! Step 3: Encode As e < emin, this is a subnormal number e = -127 V = 1.01 x 2^-130 = 0.00101 x 2^-127 m = 0.0010100000000000000000 (23-bits only, no leading bit conv.) sign = 1 (sign)(e)(m) = 1 00000000 00010100000000000000000 (32-bits)
Наконец, он плавает?
Название обещало ответить на этот вопрос. Просто нужно было.
Название «с плавающей точкой» происходит от того факта, что точка счисления может быть размещена в любом месте числа. Типы с плавающей запятой могут кодировать любое число максимум с заданным количеством цифр (мантисса ограничивает точность), где бы ни была размещена точка счисления (кроме того факта, что может быть небольшая потеря точности).
Это противоположно типам с фиксированной точкой, где представление фиксирует цифры, представленные слева и справа от точки счисления.
Тип float C ++ также происходит из системы с плавающей запятой.
Дополнительная информация: десятичные типы с плавающей запятой
(ПРИМЕЧАНИЕ. Десятичные типы с плавающей запятой широко не используются. Они более важны в коммерции из-за важности точности денежных значений.)
В 2008 году IEEE 754 добавил еще два формата: decimal32 и decimal64. В десятичных форматах мантисса масштабируется степенями 10 вместо 2. Это сохраняет десятичные значащие цифры нашего ввода и, что наиболее важно, не теряет точности для чисел, которые могут быть представлены точно с основанием 10.
Однако мантисса кодируется с основанием 2 (показатель степени также кодируется с основанием 2, только фактическое значение вычисляется как V = m x 10 ^ д). Поскольку мантисса находится в базе 2, вы не можете записать ее в научной нотации:
102 = 1.02 x 10^2 = 1.000001010001111010111000 x 10^2 202 = 2.02 x 10^2 = 10.00000101000111101011100 x 10^2
Например, у 202 две цифры (’10’) перед точкой счисления, а у 101 только одна цифра (‘1’) перед точкой счисления. Не существует целой степени 10, которая могла бы использоваться для представления 202 в двоичной научной форме (только с одной цифрой перед точкой счисления).
ПРИМЕЧАНИЕ. Этот побочный эффект вызван тем, что мантисса и масштабный коэффициент (10) находятся в разных основаниях.
Чтобы преодолеть это ограничение, IEEE 754 кодирует числа, в которых мантисса является целым числом.
1234.31212 = 123431212 x 10^-5 = 111010110110110100100101100 x 10^-5 // The mantissa will be 111010110110110100100101100 // The exponent will be -5.
Десятичные форматы определяют два способа кодирования целочисленной мантиссы: двоичное целое число (как показано в примере выше) и плотно упакованное десятичное число (DPD). В десятичных форматах также есть особые приемы, которые выходят за рамки данной статьи. О них я напишу в отдельном рассказе.
Дальнейшее чтение из Шукант Пал:
- Полный обзор HTML Canvas
- Удаление циклических зависимостей в JavaScript (мое предложение)
- Как синхронизировать игровое приложение на нескольких устройствах (Android)
- Как использовать Firebase для создания игр для Android
Я Шукант Пал - создатель ядра Silcos. Я много знаю о низкоуровневом коде C ++ и немного о внутренней структуре кода ядра Linux. Мне нравятся детали аппаратного уровня здесь и там. Следуйте за мной в моих профилях в социальных сетях.