Skip to content

Latest commit

 

History

History

09-floats

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Файлики с кодом (пояснения внутри)

youtube - https://youtu.be/cRToBPl9yz0

vk - https://vk.com/video-221776054_456239041

Представление вещественных чисел

Фиксированная точка:

Фиксируем точку в каком-то месте и делаем следующее:

2 ^ i + 2 ^ (i - 1) + ... + 2 ^ 0 + 2 ^ -1 + 2 ^ -2 ....

Пусть у нас числа 8 битные и точку мы ставим после первых 4х бит:

2.5   -> 0010.1000
3.125 -> 0011.0010

Но мы не можем точно представить это число, поэтому будет ошибка.

0.2   -> 0000.0011 [00110011] -> 0.1875

Ошибка составила 0.0125

Плавающая точка:

Идея такая же как в научной нотации: 1.6E-35

(-1)^S * M * 2^E

, где

M - мантисса (числа дробной части)

S - бит знака

E - экспонента (на сколько нужно умножить мантиссу)

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

Имеют вид 1.xxxx * 2^yyyy (1 стоит на первом месте. В бинарном виде мы знаем, что она там стоит и не храним явно)

Денормализованные значения

Имеют вид 0.xxxxx * 2^0000 (Если экспонента состоит из 0, то в начало добавляется не 1, а 0. Нужно для представления близких к 0 чисел)

Особые значения

Экспонента Мантисса Значение
11111111 все нули бесконечность (+ или -)
11111111 не все нули not a number (NaN)
00000000 все нули ноль
00000000 не все нули денормализованный вид

Особые свойства

  • 0 в float = 0 в int
  • Сравнивать можно как инты (с некоторыми оговорками)

Оговорки:

  • Нужно проверить бит знака
  • -0 = 0
  • корректно обрабатывать NaN (NaN != NaN)
  • Остальное корректно: денормализованныые < нормализованные < бесконечность

Округление

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

Значение 1.40 1.60 1.50 2.50 -1.50
К нулю (towards 0) 1 1 1 2 -1
Вниз (−inf) 1 1 1 2 -2
Вверх (+inf) 2 2 2 3 -1
К ближайшему целому вверх 1 2 2 3 -1
Ближайшее четное (умолчанию) 1 2 2 2 -2

Битность

Format Sign bits Exponent bits Mantissa bits Exponent bias
32 bit 1 8 23 (+1) 2^(8-1)-1=127
64 bit 1 11 52 (+1) 2^(11-1)-1=1023

Перевод чисел в float (конечная дробь):

11.8125 -> ?

  1. Определим бит знака.

В нашем случае 0.

  1. Переведем его в двоичное дробное число как обычно: целую часть делим на 2, дробную умножаем на 2.

целая часть (смотрим остатки):

11 / 2 = 5 [1]
5  / 2 = 2 [1]
2  / 2 = 1 [0]
1  / 2 = 0 [1]

дробная часть (смотрим переход через разряд):

8125 * 2 = 6250 [1]
6250 * 2 = 2500 [1]
2500 * 2 = 5000 [0]
5000 * 2 = 0    [1]

Результат (собираем снизу вверх сначала целую часть потом дробную):

1011.1101
  1. Нормализуем (считаем, на сколько нужно передвинуть точку, чтобы единственная цифра перед точкой была 1)

В нашем случае нужно сдвинуть на 3 влево. Это будет предварительное значение экспоненты. Если бы нужно было двигать вправо, то предварительная экспонента была бы отрицательной.

Результат:

1.0111101 * 2 ^ 3
  1. Считаем смещенную экспоненту.

Нам нужно уметь хранить отрицательные экспоненты. Поэтому сначала идут 127 отрицательных экспонент, затем положительные. Поэтому к экспоненте нужно прибавить сдвиг (bias) = 127.

Результат:

1.0111101 * 2 ^ 130
  1. Убираем первую 1.

Так как мы договорились, что у нас первая всегда 1, то нет смысла ее хранить, можно ее просто подразумевать там.

Результат:

0111101 * 2 ^ 130

Итог:

sign = 0
mantissa = 0111101
exp = 130 = 10000010

Перевод чисел в float (бесконечная дробь):

-114.3 -> ?

  1. Определим бит знака.

В нашем случае 1.

  1. Переведем его в двоичное дробное число как обычно: целую часть делим на 2, дробную умножаем на 2.

целая часть (смотрим остатки):

114 / 2 = 57 [0]
57  / 2 = 28 [1]
28  / 2 = 14 [0]
14  / 2 = 7  [0]
7   / 2 = 3  [1]
3   / 2 = 1  [1]
1   / 2 = 0  [1]

дробная часть (смотрим переход через разряд):

3 * 2 = 6 [0]
6 * 2 = 2 [1]
2 * 2 = 4 [0]
4 * 2 = 8 [0]
8 * 2 = 6 [1]
Зациклились 1001

Результат:

1110010.0100110011001....
  1. Нормализуем (считаем, на сколько нужно передвинуть точку, чтобы единственная цифра перед точкой была 1)

В нашем случае нужно сдвинуть на 6 влево. Это будет предварительное значение экспоненты. Если бы нужно было двигать вправо, то предварительная экспонента была бы отрицательной.

Результат:

1.1100100100110011001... * 2 ^ 6
  1. Считаем смещенную экспоненту.

Нам нужно уметь хранить отрицательные экспоненты. Поэтому сначала идут 127 отрицательных экспонент, затем положительные. Поэтому к экспоненте нужно прибавить сдвиг (bias) = 127.

Результат:

1.1100100100110011001... * 2 ^ 133
  1. Убираем первую 1.

Так как мы договорились, что у нас первая всегда 1, то нет смысла ее хранить, можно ее просто подразумевать там.

Результат:

1100100100110011001... * 2 ^ 133
  1. Приводим мантиссу к 23 битному виду

Результат:

11001001001100110011001 * 2 ^ 133
  1. Округление

Округляем к ближайшему четному. Нужно посмотреть, какой бит шел бы следующим (в нашем случае 1). Если 1, то к результату прибавляем 1. иначе ничего не делаем.

Результат:

11001001001100110011010 * 2 ^ 133

Итог:

sign = 1
mantissa = 11001001001100110011010
exp = 133 = 10000101

Работа с float

#include <math.h>

компилировать с -lm

Функции из библиотеки:

int isinf(float) - вернет 0 если не inf, любое другое число если inf
int isnan(float) - аналогично про NaN
int isfinite(float) - вернет 0 если inf, любое другое число если не inf
int isnormal(float) - нормализованное ли число

int fpclassify(float) - классифицирует float и вернет одну из констант:

 FP_INFINITE   Число inf
 FP_NAN        Число - NaN Indicates that x is not a number (NaN).
 FP_NORMAL     Число нормализованное
 FP_SUBNORMAL  Число денормализованное
 FP_ZERO       Число 0

Bit fields hack:

Обратите внимание, что поведение, описанное тут является implementation defined. Это означает, что на него нельзя полагаться в общем виде.

typedef union {
    float f;
    struct {
        // implementation defined поведение заключается в порядке бит.
        // нет строгих гарантий, что сначала будут идти 23, потом 8, а потом 1.
        unsigned int mantissa : 23;
        unsigned int exponent : 8;
        unsigned int sign : 1;
    } parts;
} float_uni;

Об этом сказано в стандарте C11: 6.7.2.1, пункт 11, также об этом можно почитать на cppreference в разделе Notes.

Для gcc есть некоторые гарантии того, что поведение такое, как мы ожидаем.

И пример такого кода есть в glibc или его можно самостоятельно увидеть по такому пути: /usr/include/x86_64-linux-gnu/ieee754.h.

Однако, такой код трудно переносится на другие платформы (нужно дополнительно искать подтверждения того, что код будет работать ожидаемо) и поведение может меняться.

В контест этот код приниматься не будет относитесь к нему как к интересному факту

Полезные ссылки

Float калькулятор

Презентация с лекции

Материалы прошлого года