Комментарии 229
Всё равно что написать статью «НЕВОЗМОЖНОСТЬ ПРЕДСТАВЛЕНИЯ ЦЕЛЫХ ОТРИЦАТЕЛЬНЫХ ЧИСЕЛ С ПОМОЩЬЮ ТЕКУЩЕЙ РЕАЛИЗАЦИИ ДВОИЧНОЙ СИСТЕМЫ СЧИСЛЕНИЯ В ЭЛЕКТРОННЫХ ВЫЧИСЛИТЕЛЬНЫХ МАШИНАХ.»
В учебниках же пишут про float и double, что они не точные.
Ну а в качестве иллюстрации нагляднее такое поведение — просто преобразование одного и того же числа 0.18 в VBA:
из double в float
?csng(cdbl(0.18))
0.18
из float в double
?cdbl(csng(0.18))
0.180000007152557
В учебниках же пишут про float и double, что они не точные.
Да, это всем известно. Но в статье речь идет не об ошибках преобразования десятичных чисел в двоичные и обратно, а к последствиям, к которым эти преобразования приводят при простейших арифметических вычислениях.
0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1 != 1.0
и далее объясняю нюансы.
Сам пример даю так:
a = 0;
while (a != 1) a += 0.1;
Какое значение будет после окончания цикла? Или сколько итераций цикла будет выполнено?
Шок гарантируется. Потому хорошо запоминается, что вещественные числа не стоит проверять на равенство и неравенство.
2) Получить при вычислениях непредсказуемые результаты, конечно можно, если вы используете неустойчивые алгоритмы.
" Все ваши примеры эксплуатируют один и тот же эффект: не каждая конечная десятичная дробь является конечной дробью в двоичной системе счисления. Тут как бы на IEEE754 не стоит всех собак вешать. Скорее, следует сетовать на конечность представления чисел (каким бы оно не было)".
Вы совершенно правы. И про IEEE754 вы правы. Стандарт работает с теми числами, которые ему предлагают. Но как Excel понять, где правильное число, а где не правильное. Если в большинстве случаев на выходе правильный результат, а в некоторых, случайных, не правильный, как быть?
2) Получить при вычислениях непредсказуемые результаты, конечно можно, если вы используете неустойчивые алгоритмы.
Но почему же Excel использует неустойчивый алгоритм стандарта IEEE754 для сложения, вычитания и проч. Если на выходе мы имеем непредсказуемый результат?
Ведь есть библиотеки для точных вычислений. Но они, естественно, медленнее чем железная реализация. И железная реализация сделана такой какая она есть ради скорости и экономии памяти.
0,0056-0,005599976 =0,005599976/105.3256 ≈0,006*100=0.6%
(0,0056 — 0,005599976) / 0,0056 ≈ 0.000004 относительной погрешности. Нет?
float af = 0.1;
double ad = 0.1;
printf("af - ad = %le;\n", af - ad);
Ответ:
af - ad = 1.490116e-09;
Когда я написал
float a = 0.1
я уже согласился на определенную погрешность представления десятичного значения 0.1, являющегося в двоичном представлении бесконечной периодической дробью. Далее я делаю миллион вычислений по определенному алгоритму и должен быть уверен, что данная ошибка представления (да и все остальные ошибки, возникающие при каждом вычислении) в результате не накопятся и не превысят определенный порог. Это и называется устойчивым алгоритмом.
К слову, вот этот алгоритм во float неустойчив:
const int N = 1e8;
float af = 0.1;
float S = 0;
for(int i=0; i<N; ++i){
S += af;
}
printf("error = %le\n", 0.1*N - S);
Ответ:
error = 7.902848e+06
Далее я делаю миллион вычислений по определенному алгоритму и должен быть уверен, что данная ошибка представления (да и все остальные ошибки, возникающие при каждом вычислении) в результате не накопятся и не превысят определенный порог.
В статье представлен пример, когда всего за три арифметические операции с числами формата doubl мы получили ошибку в младшем разряде равную 4. Этот пример легко продолжить. А вы говорите о миллионах операций.
Вообще, я и не говорил, что формат, принятый для представления вещественных чисел, идеален. Да, можно придумать примеры, когда получаются неожиданные результаты. Да, иногда нужно знать, как все устроено, чтобы не наступать на грабли.
Какой именно пример из приведенных вами доставляет такие страдания?
Последний пример в статье, не то что доставляет страдания, но заставляет ощущать некий дискомфорт, когда тебя так нагло «обвешивают». В мантиссе, как в десятичной, так и в двоичной, имеются цифры, которые считают справа налево от первой значащей цифры справа. Вот ее я и называю младшим разрядом в мантиссе числа (не путать с машинной мантиссой). К порядку чисел это никакого отношения не имеет.
Да, можно придумать примеры, когда получаются неожиданные результаты.
А вы уверены, что такой неожиданный результат из придуманных нами, случайно не выскочит при контроле, например, за ядерной установкой?
В итоге результат Excel и калькулятор совпадают в 15 знаках после запятой. Я все правильно понимаю?
Простите, может я слишком хочу спать и неправильно интерпретирую ваши 18 значные дроби.
теперь, следуя вашей логике, я округляю до десятых, надеясь получить один правильный знак после запятой (N = 1)
1.3/0.3 = 4.(3)
Сколько верных знаков после запятой у меня получилось?
В Вашем примере с ядерной установкой, десятичной системе просто неоткуда взяться.
Часть проблем автора поста связаны с тем, что не все конечные десятичные дроби имеют конечное двоичное представление.
0.9 ⁄ 0.7 = 1.49(249) в 16-ричной системе счисления.
Если же это результат физического измерения (масса, например), то точность измерения будет на порядки ниже точности округления. Если значение измерено с точностью до 1 знака, то мы можем смело 0.2 представить как 21/256, и ничего не потеряем.
В статье представлен пример, когда всего за три арифметические операции с числами формата doubl мы получили ошибку в младшем разряде равную 4. Этот пример легко продолжить. А вы говорите о миллионах операций.
А это уже вопрос используемого алгоритма и контроля погрешностей. В частности, Алгоритм Кэхэна как раз и используется для суммирования большого количества элементов без фатального накопления погрешности.
Это все равно, что увеличить в два раза аппаратный регистр.
Это как? По моей логике, «увеличить в два раза аппаратный регистр» = «увеличить точность в 2 раза», в то время как в алгоритме Кэхэна ничего подобного не наблюдается. Там просто контролируется накопление погрешности. Опять же, с ограниченной точностью.
Например, для обучения свёрточных нейронных сетей зачастую достаточно даже не float, а half-float (2 байта).
Представим в этом выражении сомножители в нормализованном двоичном виде:
0.06543455=1.00001100000001010001101〖2〗^(-4)
139=1.0001011〖2〗^10
Так вот, число 0.06543455 лишь приближенно равно 1.00001100000001010001101〖2〗^(-4), (точное двоичное значение — двоичная периодическая дробь, т.е. число с бесконечным количеством знаков), что собственно говоря и является причиной, почему 139*0.06543455 !=139*1.00001100000001010001101〖2〗^(-4)
0.06543455=1.00001100000001010001101〖2〗^(-4)
лишь приближенное? Ведь слева и справа рациональные числа, т.е. дроби, где слева знаменатель 10^8, а справа 2^27, а 5 на два не делится. Такие дроби никак не могут быть в общем случае равными.
Теперь заменяем везде в статье, где слева десятичная дробь, а справа двоичная, знак точного равенства на знак приближенного равенства. И видим теперь, что Ваш текст «разошелся» с математикой. Попробуйте его скорректировать чтобы и математика не пострадала, и идея вашей статьи. Я сомневаюсь, что это возможно.
Далее, почему «виноватой» в во всем этом «безобразии» оказывается именно двоичная система счисления? А если бы в компьютере была использована троичная система счисления? Или пользователь бы троичную систему счисления использовал, а компьютер десятичную? Где потерялась симметрия?
Ну и наконец, во всех физических и математических расчетах рациональных чисел не бывает. Чему там равна скорость света? А никто точно не знает. Лишь доверительный интервал известен (между двумя рациональными числами). Плевать на ошибку оцифровки в значительном числе случаев.
Ведь слева и справа рациональные числа, т.е. дроби, где слева знаменатель 10^8, а справа 2^27, а 5 на два не делится. Такие дроби никак не могут быть в общем случае равными.
Вы правы, в приведенном равенстве надо поставить знак приближения. Число справа, если его привести к десятичному виду не будет равно десятичному числу слева. Вот эта разница в представлении и дает ошибку в вычислениях.
И видим теперь, что Ваш текст «разошелся» с математикой.
Так где расхождение?
Далее, почему «виноватой» в во всем этом «безобразии» оказывается именно двоичная система счисления?
Виновата не двоичная система, а несоизмеримость оснований систем счисления. Такая же проблема имеется и для других систем с несоизмеримыми основаниями.
Плевать на ошибку оцифровки в значительном числе случаев.Полагаю, что этот акт надо оставить на усмотрение человека. А машина должна считать точно.
Так где расхождение?
Так вы же сами совершенно правильно ответили:
Виновата не двоичная система, а несоизмеримость оснований систем счисления. Такая же проблема имеется и для других систем с несоизмеримыми основаниями.
… только теперь надо бы заголовок статьи поменять?
Вы напрасно поставили многоточие.
Не напрасно. В заголовке Вашей статьи (Фатальные ошибки двоичной арифметики при работе с числами с плавающей точкой) нет упоминаний про десятичную систему счисления.
Речь идет именно об ошибках (или погрешностях) при двоичных вычислениях с десятичными ЧПТ.
Ну нет внутри современных компьютеров (ну уровне железа) десятичных чисел с плавающей запятой. Только двоичные.
Во первых, FPU выполняет вычисления с внутренней точностью, далеко превосходящей даже long double (10-байт). Усечение происходит «на выходе»
Во вторых, на самом деле FPU считает «не совсем по стандарту». В тоже же библиотеке, например GCC, масса воркэраундов. AFAIK -ffast-math будет выполнять расчеты только инструкциями FPU, и результат может вас удивить.
Во первых, FPU выполняет вычисления с внутренней точностью, далеко превосходящей даже long double (10-байт). Усечение происходит «на выходе».Да похоже я слегка загнул — внутренние вычисления происходят в x87 в 80-битном виде. Т.е операнды расширяются к 80-бит, выполняются вычисления и после этого происходит обратная конверсия с понижением точности.
Если же используются SSE/AVX то там максимум 64-бит числа с плавающей точкой.
Утверждается (например на stackoverflow), что с предварительным расширением до 80-бит и без него можно получить разные результаты вычислений.
Математики, которым критичен этот хвост, и так в курсе того, что компьютер может хранить лишь конечное множество всех возможных значений, в курсе мантисс и экспонент, и в курсе различий между численным и аналитическим решением уравнения.
Но, позвольте, а как пользователю относиться к тому, что мы получаем на выходе Excel? Математики может и в курсе, что некоторые числа могут получаться неточными. А как пользователю определить, когда можно доверять результату, а когда нет?
Честно-честно.
/*
* gcc -O0 -Wall -msoft-float -o test test.c
*/
#include <stdio.h>
#include <assert.h>
#include <float.h>
#include <math.h>
static double f(double a, double b)
{
double r = a — b;
return r;
}
int main (int argc, char **argv)
{
double a = 0.66;
double b = 0.659999996423721;
x = f(a, b);
printf(«x=%.40f\n», x);
return 0;
}
x=0.00000000357627905067658 95770513452589512 ≈0.00000000357627905067659
Сравните с тем, что получится на калькуляторе.
double a = f1(), b = f2();а так:
if ( a != b ) {… }
double a = f1(), b = f2();и на сколько полезна ваша серия статей (аж в год длинной) про недостатки текущего стандарта?
if ( fabs( a — b ) > EPSILON ) {… }
Всё известно, кто хочет точности — пользуется специальными библиотеками.
на сколько полезна ваша серия статей (аж в год длинной) про недостатки текущего стандарта?
Ну, статья не про IEEE754, а об ошибках, которые получаются в результате арифметических операций при использовании двоичной арифметики для десятичных ЧПТ. Эти ошибки не совсем очевидны, так как в большом количестве случаев результаты вычислений будут верными при использовании стандарта. Но где гарантия, что мы не столкнемся именно с теми немалочисленными ситуациями, когда будут получены совершенно неприемлемые результаты.
Вопрос, какое должно быть EPSILON, чтобы убедиться, что это число равно числу 0.00000000357627905067659
cfloat / float.h
FLT_EPSILON 1E-5 or smaller
DBL_EPSILON 1E-9 or smaller
LDBL_EPSILON 1E-9 or smaller
EPSILON — Difference between 1 and the least value greater than 1 that is representable.
То есть даже для float это «всего лишь» 0,00001
Вы сейчас боретесь или с ветряными мельницами.
У меня получилось 0.0069580
#include <cmath>
#include <cfloat>
#include <iostream>
int main()
{
float a = 1234.567, b = 1234.56;
float c = a - b;
float real_c = 0.007;
std::cout << "1234.567 - 1234.56 = " << c << std::endl; // 1234.567 - 1234.56 = 0.00695801
std::cout << "c - \"real c\" = " << ( real_c - c ) << std::endl; // c - "real c" = 4.19924e-05
return 0;
}
при замене на double:
// 1234.567 - 1234.56 = 0.007
// c - "real c" = -6.18455e-14
Так и не понял что Вы хотите этим доказать?
О «точности» float все знают.
1234.56710 = 1.0011010010.100100010010011011101001〖∙2〗^10
=1234.5670166015625
Так про какой 5 знак идет речь?
Когда вы записали 1234.567 во float, вы уже ошиблись в 5 десятичном знаке после запятой.
2) Вы снова передергиваете и приводите числа, не представимые в конечном двоичном коде.
3) Какова относительная погрешность для чисел, которые вы в реальности записали в память?
1) И какова относительная погрешность?
Если говорить о погрешности представления — разная для разных чисел
2) Вы снова передергиваете и приводите числа, не представимые в конечном двоичном коде.
Я все время именно о таких числах и веду речь. Представимые числа — точные и с ними проблем нет до тех пор пока в результате арифметических действий они не превращаются в приблизительные.
|a-b| < epsilon*max(|a|, |b|)
00 – 24 бита мантисса, 7 бит порядок;
10 – 53 бита мантисса, 10 бит порядок (двойная точность);
11 – 64 бита мантисса, 15 бит порядок (расширенная точность).
Из этого следует, что при выполнении вычислений на FPU вы будете получать разные ответы в зависимости от того, в каком режиме точности он работает. Более того, вы будете получать различные результаты и в зависимости от того, как именно компилятор оптимизирует ваш код и в каком конкретно формате он хранит промежуточные вычисления.
Стек FPU ограничен восемью регистрами. Пока остаются свободные регистры для хранения промежуточных данных, очевидно, их точность остаётся неизменной. Но как только свободные регистры заканчиваются, компилятор вынужден сохранять их во внешней памяти — и обычно это делается в 64-х битном формате, с потерей 80-битной точности (как показало изучение ассемблерного кода от компиляторов Intel и Microsoft).
As this may sometimes be problematic for some semi-numerical calculations written to assume double precision for correct operation, to avoid such problems, the x87 can be configured via a special configuration/status register to automatically round to single or double precision after each operation
Когда вы работаете на сопроцессоре, вы накапливаете результат в одном регистре расширенной точности, другие вам для этого не нужны.
В эксплуатационной документации к первым ЭВМ так и писали, что машина получает не сам результат математической операции, а доступное приближение к нему. Потом как-то все привыкли, и писать перестали.
0,789012345005176Погрешность меньше допустимой 1Е-9, так что всё укладывается в рамки допустимого.
А мы ожидали получить число: 0, 789012345
#include <stdio.h>
int main () {
double d;
d = 105.3256-105.32;
printf («d = %lf\n», d);
return 0;
}
d = 0.005600
Если Эксель вам врёт, то причём тут машинная арифметика? Он вообще с символьными данными работает.
1) отсутствует определение переменной x, поэтому она не может быть скомпилирована;
2) после вставки определения “double x;”, результат, выдаваемый программой, соответствует теоретическому с точностью до 16 десятичных цифр, что отвечает точности формата double.
Рано или поздно человечество перейдёт к шестнадцатиричной в быту.
printf («a = %.15lf\n», a);
a = 0.789012345005176
Кто вам сказал, что Эксель работает с числами в формате double и в соответствии с IEEE 754?
Microsoft Excel was designed around the IEEE 754 specification to determine how it stores and calculates floating-point numbers.
Эксель в статье был использован как инструмент для иллюстрации правильности приведенных вычислений в стандарте IEEE754.
Запрограммируйте приведенный вами пример на любом языке, в котором используется формат IEEE754, задав формат дабл, и вы получите тот же результат. Так что эксель здесь не причем.
Я свои примеры даю на языке Си с использованием арифметики IEEE 754.
Однако, вы можете реализовать все операции самостоятельно, с любой точностью, какой пожелаете, ограничиваясь объемом памяти кристалла, конечно же.
Но и это не все, там есть аппаратная функция FMUL которая умножает два байта. И она может ошибаться на 1-2 значения.
P.S. еще раз: я про целые числа, не про числа с плавающей запятой. Если даже в операциях с целыми числами могут быть такие ошибки вычислений, то что уж говорить про числа с плавающей запятой. И стоит добавить что у чисел с плавающей запятой неточность ожидаемая, то неточность при использовании целых чисел может застать в расплох. И да, я про переполнения конечно же.
Это значит, что если попытаться с её помощью перемножить два целых числа вы получите число в два раза большее чем ожидали.
По моему, у него, каша в голове: Excel, какие-то листинги программы, обещания 7 знаков после запятой наследственной грамотой…
Вы сами-то понимаете о чём речь?
Если вы показываете листинг программы, где написанна десятичная константа, то она не десятичная, это всего лишь соглашение, точно так же как кодировка листинга (может быть в utf8, но это не значит, что print(«hello world») небудет в ANSI) и вывода программы. Если в листинге написано a := 0.6, почему вырешили, что в компьютере материализуется довоичнодесятичный аппаратный блок арифметики, а не разумное: «пишите как удобно, компилятор сам преобразует»?
Значит вычёркивайте вообще все слова «десятичные» из текста.
Почему вы утверждаете что при точности в 5 знаков результат сложения тоже 5 знаков? Теория говорит, что будет уже 4 точных знака и не надо делать выпученные глаза и тыкать нас в это носом, идите и читайте книжки.
Если по существу,
Почему вы утверждаете что при точности в 5 знаков результат сложения тоже 5 знаков?
Если следовать вашим знаниям, то в формате float, после 7 шагов суммирования, мы уже имеем совершенно неверный результат, даже, если складывались точные числа. Ну или 16 шагов для дабл.
Если в кратце, то замените разряд на X и подумайте сами, способен ли он после сложения повлиять на старшие знаки?
1.0X+1.1X= это может быть, например, так 1.09+1.19 =2.28 а не 2.1X, точность — упала. После 7 шагов она упадёт ещё больше, но оценка сверху по частям и общая оценка сверху будут разниться, так же как и в бухгалтерии сумма НДС товаров не равна НДС от суммы товаров.
Проще говоря, если вам числа даны с определённой точностью, суммирование будет накапливать погршеность, умножение её преумножать, на каждой итерации всё больше разрядов могут отличаться и на целую часть это тоже распространяется. Есть попытки бороться с этим в финансах внося стохастическую составляющую. но по факту смысл не меняется и совсем от этого никуда не деться, можно лишь немного изменять масштабы бедствия в конкретных практических случаях.
cout << 0x0.123p-1 << endl;
cout << std::hexfloat << 6.6;
Может я не прав, но тем кто про эти очевидные вещи знает — статья не очень интересна.
А тем, для кого это "открытие" лучше было бы еще привести практические рекомендации (и примеры придумать/привести) типа :
- Никогда не сравнивай результат вычисления double/float типа с константой на равенство.
- Ошибки вычисления double/float имеют свойство накапливаться и результат может быть очень непредсказуемым.
3… и т.д.
впрочем, хозяин барин...
Ошибки вычисления double/float имеют свойство накапливаться и результат может быть очень непредсказуемым.
Это, если использовать медицинские аналогии, которые здесь приводил один пользователь, все равно, что сказать: " Я вам даю таблетку от головной боли, но последствия непредсказуемы".
Вообще то с таблеткой обычно дают рекомендации по использованию. Если уж пошли такие удаленные аналогии.
А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.
Причем эта тема весьма стара и классическая. Начиная с FORTRAN 66, когда программированием начали заниматься люди, не вникающие (да не надо им это) как выполняется, например, работа с числами с плавающей точкой на разных архитектурах и пр.
Да самое элементарное (одно из..) — выравнивание (умножение + деление результата на константу) там где нужно исходя из задачи и предполагаемой размерности чисел.
Тут проскакивало что то про расчет на калькуляторе и на компе и что цифры не совпадают… А ничего что калькуляторы используют в основном двоично десятичный код для вычисления? Интересно, много людей, что здесь дискутирую вообще знают что это и почему используется?
А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.
Ну да, как у Макаренко в «Республике ШКИД» — «Теоретически она лошадь, а практически она падает» Вы действительно считаете, что все проблемы с обработкой ЧПТ решены? А зачем INTEL в железе реализует обработку ЧПТ в двоично-десятичном формате?
Тут проскакивало что то про расчет на калькуляторе и на компе и что цифры не совпадают… А ничего что калькуляторы используют в основном двоично десятичный код для вычисления? Интересно, много людей, что здесь дискутирую вообще знают что это и почему используется?
Чуть ваше я как раз объяснял вкратце с каким форматом работает калькулятор. Так что, кто не знал, видимо, уже знает.
Вы действительно считаете, что все проблемы с обработкой ЧПТ решены?
С чего Вы вдруг завелись?
Все что я сказал изначально, что желательно было бы привести примеры типичных классических ошибок программиста при работе данными в формате с "плавающей точкой" и как их избежать.
И все…
Все остальное — это Вы уже сами за меня додумывает и высказываете.
А вычислениям с плавающей точкой посвящены целые главы литературы по программированию. Включая и кучу примеров где проблемы и методы их решения рассматриваются.
Вы считаете, что этой куче чего то не хватает? и поэтому
привести примеры типичных классических ошибок программиста при работе данными в формате с «плавающей точкой» и как их избежать
Если же все, что написано в статье, давно всем известно, то дайте ссылку, где эта проблема освещена.
Извините, но просто поговорить, действительно не интересно.
Здесь мы только постараемся привлечь внимание специалистов к проблеме катастрофической неточности вычислений, возникающей при проведении арифметических операций над десятичными числами при использовании двоичной арифметики.
Я вначале подумал, что это такая тонкая шутка, как вступление к лекции "для чайников". Но Ваш последний комментарий показал, что это не было шуткой, а сказано на полном серьезе:
Если же все, что написано в статье, давно всем известно, то дайте ссылку, где эта проблема освещена.
Вы действительно считаете, что изложили в статье что то очень оригинальное и никому не известное?
Тогда "Извините, но просто поговорить, действительно не интересно.".
Ну конспекты лекций я сканировать и отправлять не буду (если найду их еще).
А так google: "проблемы с плавающей запятой учебник".
Если же все, что написано в статье, давно всем известно, то дайте ссылку, где эта проблема освещена
Первая же ссылка в google на запрос «floating point issues»:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
«A relative error of b — 1 in the expression x — y occurs when x = 1.00...0 and y = .rrr..., where r = b — 1» Обозначения изменены, чтобы не вставлять картинки. Далее, доказывается справедливость этого высказывания и, чтобы избежать этой ситуации автор предлагает ввести защитную цифру, которая позволяет получить результат близкий к factor times e. Который характеризует ошибку вычислений и определяется как e=(b/2)b^(-p). Далее доказывается, что добавление защитной цифры делает относительную погрешность не хуже 2e.
Возвращаясь к моей статье. Для формата float: b=2, p=24, a=105.3256, b=105.32 e=0,00000006
a-b=0,000000024. Относительная ошибка 0,000000024/0,0056=0.0000043, что существенно больше 2e=0,00000012
Относительная погрешность может получиться какая угодно, в зависимости от соотношения порядков результата и операндов
О каких соотношениях порядков результата и операндов идет речь? Желательно с примерами.
Предполагая, что вместо второго числа Вы подразумевали 105.3200, можно заметить, что его абсолютная погрешность равна 0.00005, а разность чисел равна 0.0056 с абсолютной погрешностью 0.0001. Так как сама величина разности мала по абсолютной величине, то относительная погрешность разности оказывается велика, 0.0001/0.0056 = 0.0179.
Заметьте, это всё чисто аналитические выкладки, не имеющие пока никакого отношения к компьютеру и его представлению вещественных чисел.
Может быть, в идеальном платоновском мире и существуют точные числа, но в реальности каждое значение имеет погрешность, связанную с различными причинами
Берем 5 яблок и отнимаем от них 2. Получаем 3? Или все же 3+- немножко? Какой ответ верен? Яблоки могут быть совершенно разные по размерам. Если мы считаем в столбик разность двух чисел 105.3256 и 105.32 мы получаем в ответе 0.0006. Или +- что то еще? Калькулятор не знает предыстории чисел с которыми имеет дело. Это вы должны интерпретировать полученные результаты.
(Говоря о единичных яблоках, это, в практических целях, 5.0 и 2.0. Половину яблока ещё различаем при подсчёте количества, а 0.05 – уже нет.).
То, что вы и ваш калькулятор при вычитании 105.32 в столбик получаете ответ 0.0056, говорит только о том, что вы с калькулятором не учитываете погрешность вычислений, то есть считаете метрологически неверно. Инженеров, как правило, отучают вычитать 105.32 из 105.3256 ещё на первом курсе.
Пока вы отнимаете яблоки, они истираются, или, наоборот, что-нибудь с рук на них налипает.
Если какое-нибудь яблоко не прилипнет к рукам раздающего, они будут разложены ровно на две кучки по 5 и 3 в каждой.
То, что вы и ваш калькулятор при вычитании 105.32 в столбик получаете ответ 0.0056, говорит только о том, что вы с калькулятором не учитываете погрешность вычислений, то есть считаете метрологически неверно
Ну и слава Богу. Не его это калькуляторное дело интерпретировать результаты вычислений.
Вы почему-то хотите узурпировать всю вычислительную технику под метрологические задачи. Но ведь есть еще всевозможные задачи: математического моделирования, криптография. И, в конце концов, не лишайте человечество узнать значение числа пи в N-том знаке после запятой:).
А если серьезно, то точные числа существуют, а натуральные числа вообще связаны исключительно со счетными задачами. Это потом появились рациональные числа, в результате операций сравнения. Что больше (меньше) и во сколько раз. И даже для них, если числа соизмеримы, т.е. в них укладывается ровное количество меры, они будут точными. Не в смысле точности измерения, а в смысле количества мер, которые в них укладывается. Пример: эталон — человек обыкновенный. Сколько человек в кинотеатре? Скажем — 201. Это точное число или приблизительное?
Вообще, понятий точности много. Большой энциклопедический политехнический словарь понятие точности определяет как «степень приближения истинного значения рассматриваемого процесса, вещества, предмета к его теоретическому номинальному значению» [ dic.academic.ru/dic.nsf/polytechnic/9524/ТОЧНОСТЬ ]. Для численных задач, мне кажется, это верное определение. Поэтому результат, полученный в вычислительном устройстве, тем точнее, чем он ближе к вычисленному столбиком вручную.
Извините, если сделаете свой комент, скорее всего отвечу послезавтра. Уезжаю.
Что касается точности, то в инженерном деле стараются не использовать этот термин, если стремятся к строгости терминологии. Хотя в вычислительной технике существуют понятия «одинарная точность», «двойная точность» и т.д., качественно характеризующие конкретные форматы представления чисел в компьютере.
Когда вы говорите о своих детях, то это их нумерация натуральными числами в вашем списке детей, а не измерение количества, поэтому в данном случае говорить о погрешности не приходится.
Считаю, все ли дети дома? Один, два, три. Так, общее количество — приблизительно 3!
Хотя в вычислительной технике существуют понятия «одинарная точность», «двойная точность» и т.д., качественно характеризующие конкретные форматы представления чисел в компьютере.
Не качественно, а количественно. В формате флоат можно представить точно до 7 значащих десятичных цифр. В формате дабл — до 16 десятичных цифр. И хотя, после перевода в десятичное представление дробного двоичного числа конечной длительности, вы можете получить бесконечную дробь, первые 16 значащих цифр десятичного числа, проконвертированного из 2 в 10 систему, будут точными.
Считаю, все ли дети дома? Один, два, три. Так, общее количество — приблизительно 3!
Если вы измеряете наполненность дома своими детьми, то ребёнок, высунувшись в окно, может быть дома на любую дробную часть.
Хотя в вычислительной технике существуют понятия «одинарная точность», «двойная точность» и т.д., качественно характеризующие конкретные форматы представления чисел в компьютере.
Не качественно, а количественно. В формате флоат можно представить точно до 7 значащих десятичных цифр.
Нет. Никакое количество десятичных цифр в дробной части нельзя представить в двоичном виде точно, о чём Вы сами же и пишете. Можно говорить о том, что погрешность представления чисел в компьютерной арифметике одинарной точности не превышает их погрешности при записи в десятичной системе с 6 значащими цифрами.
Я же выше говорил только о том, что “одинарная точность” и “двойная точность” – это некое условное обозначение форматов представления чисел, а не то, что есть некая формальная метрика “точность”, различающаяся ровно в два раза.
Если вы измеряете наполненность дома своими детьми, то ребёнок, высунувшись в окно, может быть дома на любую дробную часть.
В качестве наказания я их всех запер в кладовке, предварительно законопатив все щели. Пересчитал, получилось приблизительно 3 штуки.
Нет. Никакое количество десятичных цифр в дробной части нельзя представить в двоичном виде точно, о чём Вы сами же и пишете.
Я писал: «И хотя, после перевода в десятичное представление дробного двоичного числа конечной длительности, вы можете получить бесконечную дробь».
0.5(10)= 0.1(2), 0.625(10)=0.101(10) и т.д. Это точные равенства.
Математика создавалась не для решения инженерных задач. Это для решения инженерных задач привлекается математика.
Чешуйки кожи постоянно отслаиваются. Всё имеет определённую точность рассмотрения
Я все таки не пойму, какое количество детей заперто в кладовке? За единицу измерения берем голову или килограммы?.. Или мы не имеем права определять количество детей по головам? Тогда, если определяем только по массе, то, простите, из туалета только что вышло 45,46 кг. Васи:), а заходило 45,8. Посчитал по головам, получилось — ровно одна. Сколько Вась мы имеем в наличии? По вашему, где-то 45(+-1) кг. А он растет и становится его уже 92 кг. Было в кГ. 45,46 Васи, а стало в два раза больше — 92кг. или 92(+-)кг. Сколько взрослых Вась мы имеем?
Что касается детей в кладовке, то всё правильно, сначала надо определиться с тем, что и как мы измеряем. А когда вы под конец в точности определения дойдёте до квантовых величин, то придётся учесть, что предметы, кажущиеся нам материальными объектами, на самом деле – просто локализации плотности распределения вероятностей, и нахождение Васи в кладовке хотя и очень близко к единице, но с очень небольшой вероятностью он находится в любом другом месте во Вселенной.
5 — число целое, хранится в целочисленном типе.
Хотите точные операции с дробями — используйте fixed point арифметику.
Хотите точные операции с дробями — используйте fixed point арифметику.
Это каким же образом формат fixed point обеспечит точность операций для чисел, скажем до 15 знаков после запятой? Зачем тогда ЧПТ?
Это каким же образом формат fixed point обеспечит точность операций для чисел, скажем до 15 знаков после запятой?
Зачем тогда ЧПТ?
Fixed point имеет фиксированную абсолютную точность, тогда как Floating point — фиксированную относительную точность.
64-битное целое может хранить до 18-19 разрядов, дальше сами думайте, куда пихать разделитель дробной части.
64-битное целое может хранить до 18-19 разрядов, дальше сами думайте, куда пихать разделитель дробной части.
Во-первых, 64-битное целое двоичное число может обеспечить представление десятичного числа с точностью не выше 16 значащих цифр. А во-вторых, в целом числе отсутствует дробная часть, которая и приводит к описанным в статье неприятностям.
Хотите точные операции с дробями — используйте fixed point арифметику.
Для точных рациональных вычислений нужно использовать специальные средства, например, класс «ratio» в c++, который хранит отдельно числитель и знаменатель в целочисленном виде.
До сих пор помню, как мне гад препод, из вредности, срезал 5 (на 4) вопросом про разрядность регистра 80487 (FPU сопроцессор, если кто не помнит). На лекции его не ходил. Хотя каждый день на кафедре встречались.
Одна 4-ка за 4 года.
Базовые знание по работе комп. потрохов очень помогают. Меньше ситуаций "Это магия какая то… почему результат не верный".
Сначала несколько пунктов:
1. Никакие FP не имеют к сути статьи никакого отношения.
2. Количество значащих разрядов и точность чисел тут тоже не при чём.
Заголовок статьи должен был быть таким: «Фатальные ошибки двоичного представления дробных десятичных чисел.»
Суть такова: мы бухгалтеры и считаем денюшку, у нас есть десятичне числа с копейками, к примеру.
Есть факт, что конечные десятичные дроби при переводе в двоичные дроби, могут порождать бесконечные. В переводе на русский язык некоторые копейки в компьютере не могут быть представленными точно и будут записаны округлённой двоичной дробью. Округление, это зафиксированная погрешность, она после множества вычислений будет аккумулироваться и нарастать.
В итоге, результат вычислений на бумжке в столбик и с помощью программы на компьютере может не совпадать в копейках. что для бухгалтера очень неудобно.
Решение элементарно: двоично-десятичное представление полностью устраняет проблему.
Если я не угадал, то — сделайте мне лоботомию, я больше не хочу быть телепатом.
Здесь даже точные десятичные вычисления не помогут.
Кстати, а что по поводу What Every Computer Scientist Should Know About Floating-Point Arithmetic? Я что-то не правильно посчитал?
Нет фатальной ошибки в том, что формат с ограниченной точностью имеет ограниченную точность.
Если вы хотите убедить, что в реализации IEEE754 есть ошибки — то надо не сюда писать, а в комитет по стандартизации.
Если вы изобрели новую, улучшенную технологию обработки чисел с плавающей точкой — то её надо срочно патентовать, пока этого не сделал кто-то другой.
Если вы боитесь, что «такой неожиданный результат из придуманных нами, случайно не выскочит при контроле, например, за ядерной установкой», то в любом случае уже поздно что-то менять — в современных процессорах стандарт реализован аппаратно.
Нет фатальной ошибки в том, что формат с ограниченной точностью имеет ограниченную точность.
Ограниченная точность и точность без точных ограничений, извините за тафтологию, разные вещи.
Если вы изобрели новую, улучшенную технологию обработки чисел с плавающей точкой — то её надо срочно патентовать, пока этого не сделал кто-то другой.
Вы правы. Изобрел. И начал патентовать, пока только базовые операции.См. опубликованные евразийские заявки на изобретение № 201500284 и 201500168.
Если вы боитесь, что «такой неожиданный результат из придуманных нами, случайно не выскочит при контроле, например, за ядерной установкой», то в любом случае уже поздно что-то менять — в современных процессорах стандарт реализован аппаратно.
Для спецтехники, по крайней мере при Союзе, разрабатывались оригинальные спецпроцессоры, а не брались универсальные ихние. Думаю, нужда в спецвычислителях и сегодня не отпала.
В следующем топике я планировал дать аналитические выкладки по данной проблематике.
Само же раскрытие технологии возможно только после того, как будет решен вопрос с приоритетом. Отечественное патентование практически ничего, кроме морального удовлетворения, автору не дает. Причем за это приходится не слабо платить. Для зарубежного требуются неподъемные средства. Вопрос пока завис.
Отсюда следует, что проблема существует и решение ее — задача актуальная.
Нет, это утверждение из приведенных тезисов не следует.
И потом, какая проблема-то? Все, что мы тут уже битую неделю обмусоливаем, для инженерных расчетов не является проблемой. Там практически нет точных чисел ни в десятичной системе счисления, ни в двоичной. Важны некоторые гарантии для относительной погрешности при вычислениях, по ним можно строить алгоритмы, устойчивые к ошибкам, как в исходных данных, так и в промежуточных вычислениях.
Ваши арифметические экзерцисы представляют определенный интерес для бухгалтерских расчетов. Но когда точно считают копейки плавающей точкой(запятой) пользуются редко.
Инженерам не интересно точное двоичное представление числа 0.2, если при этом они теряют в производительности. Более того, числа 0.2 у них на самом деле нет. Чаще есть какое-то близкое иррациональное (да пусть хоть рациональное), и им плевать, что компьютер ошибется при переводе его в двоичную дробь, потому что при записи исходной десятичной дроби они уже ошиблись. Попробуйте проанализировать, откуда в инженерных/научных расчетах берутся вещественные дробные числа, записанные в десятичной системе счисления?
Я придумал способ точной обработки десятичных чисел с помощью двоичной арифметики. Тем самым мы получаем такой же точный результат, как при использовании двоично-десятичной арифметики, но по скорости почти не уступающей двоичной арифметике.
Эта фраза означает, что способ будет интересен лишь производителям калькуляторов. Буду рад ошибиться.
И потом, какая проблема-то? Все, что мы тут уже битую неделю обмусоливаем, для инженерных расчетов не является проблемой.
Во-первых, кто вам сказал, что компьютеры созданы исключительно для решения инженерных задач? А во-вторых, если нет проблемы, то не о чем и говорить. Если же такое количество мнений, в том числе и вами, высказано на обсуждаемую тему, то вопрос, по крайней мере, не безразличен.
Там практически нет точных чисел ни в десятичной системе счисления, ни в двоичной.
Этот вопрос обсуждался выше по ветке с пользователем vadimr. Если есть что добавить, милости просим.
Важны некоторые гарантии для относительной погрешности при вычислениях, по ним можно строить алгоритмы, устойчивые к ошибкам, как в исходных данных, так и в промежуточных вычислениях.
Чем точнее представлено число, тем меньше его относительная и абсолютная погрешность, т.е. выше гарантии его верного представления. Или не так? Так точность актуальна для инженеров или нет?
Ваши арифметические экзерцисы представляют определенный интерес для бухгалтерских расчетов. Но когда точно считают копейки плавающей точкой(запятой) пользуются редко.
Редко, но так хотелось бы чаще, ведь так удобно. Но вот точность вычислений не позволяет.
Инженерам не интересно точное двоичное представление числа 0.2, если при этом они теряют в производительности.
А этот INTEL в угоду бухгалтерам и ученым все увеличивает и увеличивает длину машинной мантиссы в ущерб производительности. А сейчас, я слышал, и вообще учудил, в железе реализовал арифметику ЧПТ в двоично-десятичном коде для точности. И еще. Народ совсем обнаглел, придумал сверхдлинную точную арифметику, которая производительность свела почти на нет. Ну совсем не думают об инженерах.
Попробуйте проанализировать, откуда в инженерных/научных расчетах берутся вещественные дробные числа, записанные в десятичной системе счисления?
Сейчас попробую. Ну, во-первых, когда конвертируют двоичную дробь в десятичную, если расчеты ведутся на компьютере. Во-вторых, когда находят отношение двух чисел (не зависимо от системы счисления), связанных какой-либо функциональной зависимостью. В-третьих… Больше не знаю.
1. сумма 0.6000006 и 0.03339874 — формата float недостаточно для вычислений с данной точностью.
2. умножение 0.06543455 на 139 — аналогично, при умножении необходимо иметь удвоенное количество разрядов для хранения результата.
3. деление 131 на 0.066 аналогично предыдущему. 1/0.066=15.151515...=15.(15) имеет бесконечное количество значащих цифр.
4. вычитание 105.3256 и 105.32 — здесь точности уже достаточно, и для получения корректного результата достаточно округлить результат до 4 цифр после запятой. Округление необходимо для отбрасывания погрешности, внесённой при преобразовании из 10-ого представления числа в двоичное. Продемонстрировать это можно, например, так:
float round(float v, float digits)
{
float scale=pow(10,digits);
return floor((v*scale+0.5f))/scale;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout.precision(7);
cout << round(105.3256f-105.32f, 4.f) << endl;
return 0;
}
Вообще, для проверки ваших математических выкладок я бы рекомендовал Вам использовать не Excel или калькулятор, а системы компьютерной алгебры. Например, в Wolfram Mathematica можно явно задавать точность чисел с плавающей точкой для обеспечения корректного результата:
In[1]:= 105.3256`7 - 105.32`7
Out[1]= 0.0056
In[2]:= 131/0.066`7
Out[2]= 1984.848
1. сумма 0.6000006 и 0.03339874 — формата float недостаточно для вычислений с данной точностью.
Да нет. В большинстве случаев он достаточен. Формат float обеспечивает представление десятичных цифр в двоичном коде, гарантируя 7 верных десятичных цифр. Это неопровержимый факт, который легко доказывается. Неопровержимым фактом также является то, что сумма этих чисел с точностью до 7 знака после точки/запятой, посчитанная вручную, равна 0,6333993.
Но самое интересное всегда происходит на границе. Число 0.6000006 является тем числом, в котором после обратной конвертации из двоичного кода в десятичный, после последней цифры 6 возникает «хвост» (или Ulps), который близок к числу 2. Этот самый Ulps и искажает результат суммирования наших чисел, т.к. во втором слагаемом мы имеем 7 значащих цифр и последняя цифра в нем больше или равно 4. После суммирования 2 и 4 получаем число 6, которое заставляет округлить нашу сумму с увеличением. Таким образом, при сложении двух десятичных чисел в двоичном коде, в формате float, можно гарантировать, что 6 значащих цифр результата будут верными.
2. умножение 0.06543455 на 139 — аналогично, при умножении необходимо иметь удвоенное количество разрядов для хранения результата.
Для хранения результата в формате float нам отведено всего 24 двоичных разряда, которые могут обеспечить точное представление 7 значащих десятичных цифр. Удвоенная мантисса нужна для промежуточных вычислений.
3. деление 131 на 0.066 аналогично предыдущему. 1/0.066=15.151515...=15.(15) имеет бесконечное количество значащих цифр.
Также как при умножении, на результат деления оказывает влияние хвост, который может искажать последнюю цифру результата. При делении несоизмеримых чисел всегда получается бесконечная дробь, которую необходимо округлить, чтобы как-то записать в отведенную разрядность. Как работать с десятичными дробями мы знаем. Но результат искажается за счет хвоста.
4. вычитание 105.3256 и 105.32 — здесь точности уже достаточно, и для получения корректного результата достаточно округлить результат до 4 цифр после запятой. Округление необходимо для отбрасывания погрешности, внесённой при преобразовании из 10-ого представления числа в двоичное.
А как вы догадались, что округлить надо до 4 цифр? Глядя на полученный результат? А почему не до 5 или 6 знака после запятой, как здесь кто-то выше предлагал?
Вообще, для проверки ваших математических выкладок я бы рекомендовал Вам использовать не Excel или калькулятор, а системы компьютерной алгебры.
Мои математические выкладки не зависят от инструмента, на котором их можно проверить. Они основаны на свойствах ЧПТ и только.
А как вы догадались, что округлить надо до 4 цифр? Глядя на полученный результат? А почему не до 5 или 6 знака после запятой, как здесь кто-то выше предлагал?
Можно и до 5 или 6, результат будет тот же. А до 4-х исходя из точности исходных данных, а именно 105.3256.
И что же мы видим — исходная предпосылка для первого числа неверная — число 0.6000006 не существует в виде float, и из нее рождается демагогия.
1. сумма 0.6000006 и 0.03339874 — формата float недостаточно для вычислений с данной точностью.
Да нет. В большинстве случаев он достаточен. Формат float обеспечивает представление десятичных цифр в двоичном коде, гарантируя 7 верных десятичных цифр.
Вы как бы не учли, что если привести эти числа к одному порядку, получается уже 8 значащих цифр.
Будет интересно посмотреть.
Я недавно столкнулся с задачей реализовать стандартный набор С-функций math.h на ассемблере. В процессе чего выяснилось — что набор инструкций, удобный процессору, во многом перпендикулярен привычным человеку математическим функциям.
Соответственно, разрабатывая новый формат, Вам нужно спроектировать к нему и набор инструкций, эффективно с ним работающий.
Удачи.
Фатальные ошибки двоичной арифметики при работе с числами с плавающей точкой