Плавающая точка в .NET - часть 1: принципы и форматы - Формат плавающей точки с обычной точностью

ОГЛАВЛЕНИЕ


Формат плавающей точки с обычной точностью

Мы теперь рассмотрим детали форматов с одинарной и двойной точностью. Хотя вам не обязательно знать все в деталях, данная информация может помочь вам понять некоторые нюансы работы с числами с плавающей точкой. Для того, чтобы все было не так сложно, мы сначала рассмотрим формат с одинарной точностью. Формат с двойной точностью схож с первым, и мы поговорим о нем позже.

Нормализованные числа

Типичное двоичное число с плавающей точкой имеет вид s � (m / 2N-1) � 2e, где s является либо -1 или +1, m и e являются мантиссами или значимой частью и экспонента указывается после, а N является числом бит в значимой части, что является константой для конкретного числового формата. Для чисел с одинарной точностью, N = 24. Числа s, m и e упакованы в 32 бита. Представление описано в следующей таблице:

часть знак экспонента дробная часть
номер бита 31 23-30 0-22

Знак хранится в наиболее значимом бите. Значение 0 указывает на положительное значение, а 1 указывает на отрицательное.

Поле экспоненты является 8-битным незнаковым целым числом, называемым смещенной экспонентой. Она равна экспоненте e а также константе, названной смещенной, которая имеет значение равное 127 для чисел с одинарной точностью. Это означает то, что к примеру экспонента -44 будет храниться в виде -44+127= 83 или 01010011. Существует два зарезервированных значения экспоненты: 0 и 255. Причина будет скоро рассмотрена, а в результате наименьшая реальная экспонента будет -126, а наибольшая +127.

Числовой формат кажется слишком сложным: вы можете умножить m на 2 и вычесть 1 из e и получить то же число. Такая двусмысленность разрешается путем минимизации экспоненты, и увеличения размера значимой части. Процесс называется нормализацией. В результате, значимое число m всегда имеет 24 бита, где ведущий бит всегда равен 1. Поскольку оно всегда равно единице 1, нам не стоит хранить данный бит, и потому у нас значимая часть всегда занимает 23 из 24.

Другими словами, нормализация означает то, что число m / 2N-1 всегда расположено между 1 и 2. 23 хранимых бита также являются тем, что идет после десятичной запятой в случае, когда значимая часть делится на 2N-1. Поэтому данные биты иногда называются дробной частью.

Ноль и субнормальные числа

На данном этапе у вас может появиться вопрос о том, как хранится число ноль. Ведь ни m ни s не могут быть нулевыми, и потому ноль отобразить невозможно. Ответом будет то, что 0 является особенным числом с особым представлением. Более того представлений два!

Числа, которые мы пока описывали, чьи значимые части имеют наибольшую длину, называются нормализованными числами. Они представляют большинство чисел, выраженных в виде формата с плавающей точкой. Наименьшее положительное значение их будет 223 .2-126+1-24 = 1.1754e-38 , а наибольшее (224-1).2127+1-24 = 3.4028e+38.

Помните, что смещенная экспонента имеет два зарезервированных значения. Смещенная экспонента 0 используется для отображения числа ноль а также субнормальных и денормализированных чисел. Это такие числа, чья значимая часть не нормализована и их максимальная длина равна 23 битам. Сама экспонента равна -127+1-24=-149, что в результате является наименьшим положительным числом 2-149 = 1.4012e-45.

Когда оба смещенная - экспонента и значимая часть числа - равны нулю, в результате у вас будет число 0. Изменение знака нуля не изменяет его значения, потому у нас два возможных представления числа ноль: одно - с положительным знаком, а другое - с отрицательным. Оказывается, есть смысл в наличии значения отрицательного нуля. Хотя его значение равно значению нормального положительного нуля, такое значение ведет себя по-другому в некоторых ситуациях, о которых мы вскоре поговорим.

Бесконечности и не-числа (Not-a-Number)

Нам все же необходимо пояснить использование другой зарезервированной смещенной экспоненты со значением 255. Данная экспонента используется для представления бесконечностей и не-числовых значений.

Если смещенная экспонента имеет только единицы (то есть равна 255) и значимая часть выражена только нулями, то это будет бесконечностью. Знаковый бит показывает , если мы имеем дело с положительной или отрицательной бесконечностью. Данные числа возвращаются для операторов, которые или  не обладают конечным значением (как 1/0) или же они слишком велики для того, чтобы быть отображенными в виде нормализованного числа (то есть 21,000,000,000).

Знак деления на ноль зависит от знаков как делимого, так и делителя. Если вы поделите +1 на отрицательный ноль, то в результате вы получите отрицательную бесконечность. Если вы поделите -1 на положительную бесконечность, то в результате у вас будет отрицательный ноль.

Если значимая часть отлична от нуля, то значение представляет собой не-число (Not-a-Number) или NaN. NaN могут быть двух видов: сигнальные и не сигнальные, или тихие, в зависимости от ведущего бита в значимой части (1 или 0 соответственно). Такое разделение не очень важно на практике, и может уже быть не использовано в последующих стандартах.

NaN производятся когда результаты подсчетов не существуют (к примеру, Math.Sqrt(-1) не является числом) либо же они не могут быть определены (бесконечность/бесконечность). Одной из особенностей NaN является то, что все арифметические операции, включающие NaN возвращают NaN, за исключением случая, когда результат будет таким же независимо от значения. К примеру, функция hypot(x, y) = Math.Sqrt(x*x+y*y) с x бесконечностью всегда равна положительной бесконечности, независимо от значения y. В результате, hypot(infinity, NaN) = бесконечность.

Также, любое сравнение NaN с любым другим числом, включая NaN, вернет "ложь". Исключением является оператор, который всегда возвращает "истина", даже тогда, когда сравниваемое значение также является NaN!

Значимая часть NaN может быть установлена в произвольное значение, иногда называемое грузом. Стандарт IEC 60559 указывает, что груз должен передаваться при подсчетах. К примеру, когда NaN добавляется к нормальному числу, к примеру 5.3, то результатом будет NaN с тем же грузом, что и первая операнда. Когда обе операнды NaN, то результирующий NaN будет содержать груз одной из операнд. Это позволяет передавать потенциально полезную информацию в NaN значениях. К сожалению, данная функциональность практически не используется.

Некоторые примеры

Давайте рассмотрим некоторые числа и их соответствующие битовые шаблоны.

Номер Знак Экспонента Мантисса
0 0 00000000 00000000000000000000000
-0 1 00000000 00000000000000000000000
1 0 01111111 00000000000000000000000
+Infinity 0 11111111 00000000000000000000000
NaN 1 11111111 10000000000000000000000
3.141593 0 10000000 10010010000111111011100
-3.141593 1 10000000 10010010000111111011100
100000 0 10001111 10000110101000000000000
0.000001 0 01101110 01001111100010110101100
1/3 0 01111101 01010101010101010101011
4/3 0 01111111 01010101010101010101011
2-144 0 00000000 00000000000000000100000

В частности посмотрите на поле экспоненты для 1 и 4/3. Оба числа находятся между 1 и 2, и потому их несмещенная экспонента равна нулю. Смещенная экспонента потому равна несмещенной экспоненте, то есть 127 или 1111111 в десятичном формате. Числа большие чем 2 имеют смещенную экспоненту больше чем 127. Числа меньшие чем 1 имеют смещенную экспоненту равную меньше чем 127.

Последнее число в таблице (2-144) денормализовано. Смещенная экспонента равна нулю, и поскольку 2-144 = 32*2-149 то доля равна 32 = 25.