Форум программистов, компьютерный форум, киберфорум
Evg
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Представление вещественных чисел "на пальцах"

Запись от Evg размещена 15.02.2012 в 13:29
Показов 45982 Комментарии 8
Метки c, c++, си

ВНИМАНИЕ! Вопросы по существу обсуждаемого вопроса просьба задавать здесь или создать тему на форуме и кинуть на неё ссылку в блог или мне в личку.
Объясняю почему

Причин для этого несколько.

Я, как и любой другой автор, всегда могу упустить интересный момент обсуждаемой темы (что подтвердилось на практике). А потому задаваемый вопрос может закрывать пробел в статье. Ответ на конкретный вопрос, как правило, дать несложно. Сложнее его аккуратно сформулировать так, чтобы ответ являлся законченной частью статьи. Поэтому, как правило, на первых порах я ограничиваюсь конкретным ответом на конкретный вопрос, а в статью временно вставляю ссылку на пост, где был дан ответ. А когда дойдут руки, то вместо ссылки пишу нормальное пояснение. Технические возможности блога не позволяют в комментариях пользоваться широкими возможностями, доступными на форуме (то как выделение текста жирным, вставка фрагментов исходников в удобном для чтения виде и т.п.), поэтому будет удобнее, если вопрос и ответ будут опубликованы на форуме

Любая статья является изложением знаний в общем случае. У многих людей мышление устроено так, что прочтя на форуме конкретный вопрос и конкретный ответ на этот вопрос, у них появится бОльшее понимание, чем после прочтения теоретических выкладок (даже если они подкреплены конкретными примерами). Ссылки на такие обсуждения я, как правило, включаю в последний раздел статьи.

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

Исторически сложилось, что раньше (когда ещё не было блога) статьи располагались на форуме и представлены были в виде двух тем. Первая тема создавалась в специально отведённой свалке и представляла собой черновик, который со временем дорабатывался до законченной статьи. После этого статья переезжала во вторую тему в тематическом разделе. А первая тема оставалась дополнительной свалкой для замечаний и мелких вопросов по теме. Ссылку на старое местоположение данной свалки я помещаю в начале статьи. Вопросы, по возможности, прошу создавать в отдельных темах, но если вопрос действительно мелкий, то можно его задать и в указанной свалке.





1. Общие сведения

По поводу машинного представления в машине плавающих чисел имеется целая куча статей, а так же об этом написано почти в любом учебнике. Но практика показывает, что начинающим этот момент понять сложно, даже при наличии описания. Я собираюсь пояснить только принцип представления плавающих чисел "на пальцах", после чего, как мне кажется, понять конкретное представление будет уже проще. А может и вообще этого делать не понадобится (для общего понимания такого описания может оказаться достаточным).

Пояснить попробую на примере десятичного представления чисел, т.к. это проще для восприятия. Представим, что у нас есть табло, состоящее из шести индикаторов, на каждом из которых может быть представлена цифра от нуля до девяти. Значение "шесть" взято от балды. Конкретное количество индикаторов не важно, важно лишь то, что их ограниченное количество (поскольку в машине любой тип данных имеет вполне конкретный ограниченный размер). Допустим, что между первым и вторым индикатором нарисована десятичная запятая (или точка, кому как удобнее), а между четвёртым и пятым - некий разграничитель (нпример, вертикальная чёрточка). Таким образом шесть нулей будут отображаться в виде "0.000|00".

Теперь возьмём некотрое вещественное число, например, 153.7. Запишем число в экспотенциальной форме: 1.537*10^2. При этом часть "1.537" по научному называется мантисса, а "2" - экспонента. Теперь отобразим такую форму записи значения на табло и получим такое состояние: "1.537|02" (при этом "10" мы не пишем, поскольку мы "знаем", что работаем в десятичной системе). Напомню, что десятичная точка и разграничитель "нарисованы" на табло (т.е. они всегда находятся в одних и тех же позициях и не могут быть сдвинуты или перерисованы). В итоге мы наше вещественное число неким хитрым образом отобразили на наше табло, которое обладает тем свойством, что десятичная точка всегда находится между первым и вторым индикатором, а разграничитель - между четвёртым и пятым. Теперь можно стереть десятичную точку и разграничитель, в результате мы получим как бы целое число "153702", в котором на самом деле закодировано вещественное значение 153.7. В машине, которая умеет работать только с двоичными числами, вещественные значения хранятся (кодируются) примерно по такому же принципу: имеется N-разрядная ячейка памяти, часть разрядов которой отведена под мантиссу (записанную в двоичной форме), а часть - под экспоненту (тоже в двоичной форме), при этом машина "знает", что она работает в двоичной системе (аналогично тому, как мы "знали", что работаем в десятичной системе).

Теперь посмотрим, какие эффекты вытекают из подобного представления чисел.

2. Приблизительные значения

Допустим, у нас имеется число 234.567. После переведения его в экспотенциальную форму мы получаем 2.34567*10^2, а при дальнейшем записи на табло получаем "2.345|02". Мы видим, что мантисса не может целиком влезть в те 4 индикатора, которые под неё отведены. Из этого следует, что в такой форме можно хранить не точные, а лишь приблизительные значения. Но не всегда, потому как для числа 12.34 мы можем сохранить точное значение, потому как мантисса экспотенциального представления числа аккуратно влезет в наши 4 разряда: "1.234|01"

Если мы посмотрим на пример

C
#include <stdio.h>
 
float f = 123456.123456;
 
int main (void)
{
  printf ("%f\n", f);
  return 0;
}
то увидим на исполнении, что наше число представлено в машине не точно, а приблизительно (округлённо)

Code
$ gcc t.c
$ ./a.out
123456.125000
3. Разная точность для больших и маленьких чисел

Рассмотрим числа из разных диапазонов:
  • Число 1.23456, отображённое на табло, будет выглядеть как "1.234|00", что есть 1.234 при обратном раскодировании. Т.е. представлено с точностью до 0.001.
  • Число 123.456, отображённое на табло, будет выглядеть как "1.234|02", что есть 123.4 при обратном раскодировании. Т.е. представлено с точностью до 0.1.
  • Число 123456, отображённое на табло, будет выглядеть как "1.234|05", что есть 123400 при обратном раскодировании. Т.е. представлено с точностью до 100 (таким образом, мы даже целую часть потеряли).

Из этого можно заключить, что маленькие значения можно представить с большой точностью, а большие значения - с маленькой точностью. Всё это мы по прежнему можем увидеть на конкретном примере:

C
#include <stdio.h>
 
float f1 = 1.2345678912345;
float f2 = 12345678912.345;
 
int main (void)
{
  printf ("%.10f\n", f1);
  printf ("%.10f\n", f2);
  return 0;
}
Code
$ gcc t.c
$ ./a.out
1.2345678806
12345678848.0000000000
Из печати мы видим, что напечатанные числа даже немного не похожи на наши оригинальные. Всё это из-за того, что записываем мы их в десятичной форме, но в машине они хранятся в двоичной форме (и округляются не с точностью до десятичных целых и дробных, а с точностью до двоичных)

4. Потери точности при вычислениях

Математически (1 / 3) * 3 строго равняется единице. Но если мы всё это воспроизведём через наше табло, то получим, что 1 / 3 = "0.333|00", а (1/3) * 3 = "0.333|00" * 3 = "0.999|00", что вовсе не равно единице. Теперь расмотрим другое выражение: (1 / 30) * 30 (которое математически так же равно единице). 1 / 30 = "0.033|00", "0.033|00" * 30 = "0.990|00". Т.е. мы получили, что значение выражения "(1 / 3) * 3" с точки зрения нашего представления НЕ эквивалентно "(1 / 30) * 30".

Теперь рассмотрим сложение большого и маленького значения: 1234 + 0.5. Сумма должна быть 1234.5, но эта сумма, записанная на нашем табло, будет отражаться как "1.234|03", т.е. эквивалентна первому слагаемому и мы имеем эффекто того, что "a + b == a"

Едем дальше. Рассмотрим два казалось бы эквивалентных выражения "1234 + 0.5 - 1234" и "1234 - 1234 + 0.5". Эти выражения отличаются только порядком слагаемых. Для первого выражения будем иметь 1234 + 0.5 == 1234 (как следует из предыдущего абзаца), и итоговое значение выражения будет равняться нулю. Для второго выражения 1234 - 1234 равняется нулю и итоговое значение выражения равно 0.5. Т.е. правило ассоциативности "(a + b) + c == a + (b + c)" для вещественных чисел в машинном представлении в общем случае неверно

Такие "странные" эффекты являются следствием двух свойств, описанных в предудущих разделах: мы храним приблизительные значения и точность представления маленьких и больших значений разная.

5. Представление чисел, меньших единицы

FIXME дописать

6. Представление отрицательных чисел

FIXME дописать

7. Сравнение плавающих чисел при помощи целочисленного сравнения

FIXME дописать

За основу взят пост https://www.cyberforum.ru/post766330.html

8. Краткие итоги

Многие могли наблюдать такие эффекты, что при печати чисел с большим количеством знаков после запятой в конце появляются "лишние" разряды. Например, число 0.8 является "точным" (т.е. записывается в виде конечного количества значащих разрядов после запятой), однако при печати можно увидеть "хвост". Это является следствием того, что в машинах числа хранятся в двоичном представлении, и при этом не всякое "точное" число в десятичном представлении будет являться "точным" в двоичном представлении: числа 1.25 или 0.75 при переводе в двоичное представление будут "точными", а 0.8 или 1.7 - нет. С другой стороны число 1/3, записанное в десятичном представлении НЕ будет точным, а в троичном - будет. Именно те причины, которые описаны в статье, являются проблемным местом всех вещественных вычислений на процессорах, просто эти эффекты происходят на двоичном представлении, а не на десятичном: природа всех эффектов в двоичном представлении точно такая же, но в деталях и конкретных значениях выглядит немного не так, как в десятичном представлении. Но всю конкретику можно понять только усвоив, каким образом формируется запись дробных чисел в различных системах счисления.

На мой взгляд сделанные "на пальцах" пояснения на примере десятичных чисел помогут начинающим понять подобные "странные" эффекты, которые наблюдаются при работе с вещественной арифметикой, и дадут общее представление о хранении вещественных чисел в машине. Для тех, кому стала интересна конкретика, мне остаётся лишь подкинуть ссылки на статьи по конкретному представлению вещественных чисел в машине, где всё уже расписывается с точностью до бита и согласно стандарту (см. ссылки в разделе 11). Цель моего пояснения сводилась лишь к тому, чтобы дать некие базовые понятия, которые облегчат процесс восприятия при чтении подобных статей и книг.

9. Одно из стандартных заблуждений

FIXME явным образом написать об этом, а пока в виде ссылки: Неправильное значение переменной в for`e

10. Ссылки на темы, где обсуждался данный вопрос

11. Внешние ссылки по данной тематике

12. Программа для наглядности

snake32 предложил программу-визуализатор, которая по простому позволяет "пощупать" 32-битные плавающие числа в двоичном представлении. Программа немного вываливается за рамки статьи, поскольку в статье были объяснены только базовые принципы и даже не было описания хранения вещественных чисел в двоичном формате. Но если есть полезная в обучении вещь, то почему бы её не прицепить. Скачать программу: SinglePrecision.rar. Скачать исходники на Delphi XE 2: SinglePrecision.zip



Нажмите на изображение для увеличения
Название: singleprecision.jpg
Просмотров: 9227
Размер:	37.6 Кб
ID:	1915
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 8
Комментарии
  1. Старый комментарий
    Запись от LVV размещена 02.03.2017 в 07:18 LVV вне форума
  2. Старый комментарий
    Статья супер!! Автору огроменное спасибо и уважение. Действительно "на пальцах" все просто и понятно.
    Запись от txtbit размещена 26.07.2018 в 19:54 txtbit вне форума
  3. Старый комментарий
    Аватар для D1973
    Вот прямо респектище!!!
    У сына вопрос по информатике возник как раз по этому поводу. Сам умом-то это понимаю, а объяснить внятно не смог...
    Теперь и у того в мозгах прояснение появилось, спасибо!
    Запись от D1973 размещена 11.01.2019 в 07:50 D1973 вне форума
  4. Старый комментарий
    Моя шпаргалка:

    ...Точность хуже вдвое с увеличением вдвое числа
    16777216... Точность 2, хуже целых чисел
    8388608...16777215 Точность 1, как целые числа
    4194304...8388607 Точность 0,5
    2097152...4194303 Точность 0,25
    1048576...2097151 Точность 0,125
    524288...1048575 Точность 0,0625, меньше 0,1
    262144...524287 Точность 0,03125
    131072...262143 Точность 0,015625
    65536...131071 Точность 0,0078125, меньше 0,01
    32768...65535 Точность 0,00390625
    16384...32767 Точность 0,001953125
    8192...16383 Точность 0,0009765625, меньше 0,001
    4096...8191 Точность 0,00048828125
    2048...4095 Точность 0,000244140625
    1024...2047 Точность 0,0001220703125
    512...1023 Точность 0,00006103515625, меньше 0,0001
    ...511 Точность 0,000030517578125, целая часть непригодно мала
    ...Точность точнее вдвое с уменьшением вдвое числа

    Если к числу с точностью 0,5 прибавить 0,4- число не изменится.
    Например: для точности 1 микрон, не следует превышать значение 16383 мм.
    Запись от IhKa размещена 23.01.2021 в 14:38 IhKa вне форума
  5. Старый комментарий
    Аватар для 66Андрей
    Спасибо за статью! Я сам долго мучался от непонятно откуда появляющихся "хвостов" в конце
    арифметических вычислений. Теперь пришло понимание... но хвосты остались!
    Как бороться с этим явлением?
    Ставить на переменную двойную точность?
    А если приходится работать с довольно мелкими числами: 0, 0000000765; до какого знака после запятой
    можно доверять?
    Запись от 66Андрей размещена 11.11.2021 в 18:10 66Андрей вне форума
  6. Старый комментарий
    Аватар для 66Андрей
    Спасибо за статью! Я сам долго мучался от непонятно откуда появляющихся "хвостов" в конце
    арифметических вычислений. Теперь пришло понимание... но хвосты остались!
    Как бороться с этим явлением?
    Ставить на переменную двойную точность?
    А если приходится работать с довольно мелкими числами: 0, 0000000765; до какого знака после запятой
    можно доверять?
    Запись от 66Андрей размещена 11.11.2021 в 18:10 66Андрей вне форума
  7. Старый комментарий
    Цитата Сообщение от 66Андрей
    А если приходится работать с довольно мелкими числами: 0, 0000000765; до какого знака после запятой можно доверять?
    В вычислительной математике принято оперировать не с "точностью" чисел, а с погрешностью. Есть два вида погрешности.
    1. Абсолютная погрешность
    2. Относительная погрешность
    Например даны два числа A = 314 , B = 3,14
    Так вот абсолютная погрешность чисел A и B равна
    https://www.cyberforum.ru/cgi-bin/latex.cgi?<br />
\Delta A=0,5<br />
\Delta B = 0,005<br />
    Относительная погрешность (или просто погрешность) обоих чисел равна
    https://www.cyberforum.ru/cgi-bin/latex.cgi?<br />
\delta A=\frac{\Delta A}{A} = \frac{0,5}{314} = 1/628=0,0016=0,16%<br />
\delta B=\frac{\Delta B}{B} = \frac{0,005}{3,14}=1/628 = 0,0016=0,16%<br />

    Андрей, в вашем числе 0,0000000765 три верных (точных) знака. Вот им и доверяйте. А вообще советую познакомиться с элементами теории погрешностей. (судя по вашему вопросу вы об этом мало что знаете)
    Запись от wer1 размещена 11.11.2021 в 20:51 wer1 вне форума
  8. Старый комментарий
    Аватар для 66Андрей
    Спасибо за ответ. Обязательно посмотрю теорию погрешностей...
    Запись от 66Андрей размещена 11.11.2021 в 22:25 66Андрей вне форума
 
Новые блоги и статьи
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru