Плавающая точка в .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.