Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Аппаратные ошибки вычислений с вещественными числами

44K
05 ноября 2012 года
Mixim
18 / / 01.01.2010
Реализовал свой класс, который разбирает строковое выражение (например, "cos(x)+2*x"), переводит его в математическую функцию (Math.Cos(x) + 2*x) и строит её график в определенных пределах и с некоторым шагом. Запускаю через отладчик и в момент исполнения генерируется исключение. Понимаю, что этого исключения быть в моем коде по его природе не может, расставляю точки остановки и смотрю исполнение кода по шагам - обнаруживаю, что вся проблема в следующем методе:
Код:
protected static System.Collections.Generic.LinkedList<Double> CalcFunctionValues(String Function, Single X1, Single X2, Single Step)
                {
                    System.Collections.Generic.LinkedList<Double> returnedValue=new System.Collections.Generic.LinkedList<Double>();
                    Single xMin=X1<X2 ? X1:X2, xMax=X1>X2 ? X1:X2;
                   
                    if(Step<=0)
                    {
                        throw new ArgumentException(String.Format("Invalid parameter 'Step'={0}(Step>0)", Step));
                    }
                    else
                    {
                        Single x;
                        for(x=xMin; x!=xMax;x=x+Step)
                        {

                            returnedValue.AddLast(new ArgumentAndFunctionClass(x, this.MathParser.Parse(this.MathParser.FunctionToCalculatedExpression(Function, PlotterClass.ArgName, x), PlotterClass.IsRadians
                                                                                                        )
                                                                                )
                                                );
                        }
                    }

                    return returnedValue;  
                }
т.к. вызывая данный метод следующим образом:
 
Код:
CalcFunctionValues("x^2", -18, 18, 0.05)
и по завершении цикла в отладчике имею переменную x со значением "18.000002" (откуда взялось "0.000002" непонятно). Пытался даже закомментировать все вычислительные строчки внутри цикла, но величина всеравно немного не та. Начинаю еще более детально разбирать свой код, искать где может изменяться значение счетчика цикла x, но ничего подобного нет (во всем коде x инкрементируется только на "0.05"). Начинаю лазить в сети в поисках ответа на вопрос: "Как так?" и нахожу на Хабре замечательную статью Что нужно знать про арифметику с плавающей запятой, которую внимательно прочитываю. Из этой статьи сделал вывод, что погрешность создает не мой код, а процессор (точнее говоря, не совсем удачное представление чисел в двоичном коде). Немного правлю свой простецкий метод (меняю типы данных):
Код:
protected static System.Collections.Generic.LinkedList<Double> CalcFunctionValues(String Function, Decimal X1, Decimal X2, Decimal Step)
                {
                    System.Collections.Generic.LinkedList<Double> returnedValue=new System.Collections.Generic.LinkedList<Double>();
                    Decimal xMin=X1<X2 ? X1:X2, xMax=X1>X2 ? X1:X2;
                   
                    if(Step<=0)
                    {
                        throw new ArgumentException(String.Format("Invalid parameter 'Step'={0}(Step>0)", Step));
                    }
                    else
                    {
                        Decimal x;
                        for(x=xMin; x!=xMax;x=x+Step)
                        {
                           
                            returnedValue.AddLast(new ArgumentAndFunctionClass(x, this.MathParser.Parse(this.MathParser.FunctionToCalculatedExpression(Function, PlotterClass.ArgName, x), PlotterClass.IsRadians
                                                                                                        )
                                                                                )
                                                );
                        }
                    }

                    return returnedValue;  
                }
и все начинает нормально исполняться. В примененном типе Decimal числа представляются в десятичном виде (если верить приведенной статье и MSDN) и процессор работает с ними как с десятичными, а не с двоичными, но возникает закономерный вопрос: сколько лет назад процессоры начали поддерживать работу с десятичным представлением числа и ни возникнут ли какие-либо проблемы при запуске программы на старом процессоре?
40K
05 ноября 2012 года
D129
228 / / 18.04.2012
>>>> операцией он справляется как-то странно

десятичная система тоже "как-то странно" справляется с простейшей задачей,
разделить 1 на 3, это 1/3 - и все! А в десятичной системе приходится писать 0.3333333333333333 .... - и все равно это меньше, чем 1/3 !

:-)

Что же касается процессорного времени - то его лучше беречь, когда ясно как это делать.
40K
05 ноября 2012 года
D129
228 / / 18.04.2012
Все процессоры работают только с двоичными числами.
Тип Decimal реализует десятичную арифметику на двоичной базе.
То есть делает то, что должны были сделать вы, как программист, при использовании Double.

То что числа с плавающей точкой имеют ограниченную точность - должно быть учтено в алгоритме.

Вам надо округлять до нужного значения или не ставить строгой проверки условия.

Возникает закономерный вопрос - если есть такой хороший тип Decimal - зачем же тогда поддерживать устаревший (который только на старых процессорах и нужен был :-) ) тип Double?

И ответ на него очень прост - вычисления с Decimal медленнее, так как процессору приходится соблюдать точность, которая может быть и не нужна вовсе (в конкретной задаче).

В дот нет обязательно использование типа Decimal только для операций с деньгами, в остальных случаях - не надо стрелять из пушки по воробьям.
465
05 ноября 2012 года
QWERYTY
595 / / 25.03.2012
Цитата:
x со значением "18.000002"



В матлабе была такая же хрень. Когда работаешь с целыми всё норм. Как только дело касается вычислений с точкой начинались чудеса(ну для моего восприятия, с точки зрения машины делает что может).
Например должно получиться 12.2 , а получается неточность в последнем знаке например типа Double.
Там настраивается сколько знаков выводить при отображении но в любом случае показывают отклонение в последнем знаке. Например если настроить вывод коротких значений то выводит 12.200001 или 12.199999.

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

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

44K
05 ноября 2012 года
Mixim
18 / / 01.01.2010
Цитата: D129
Все процессоры работают только с двоичными числами.


Это и ежику понятно

Цитата: D129

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


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

Код:
//абсолютная абстракция. CurArg - текущий аргумент, PrevArg - предыдущий аргумент
Double MyFunction(Double CurArg, Double PrevArg)
{
Double returnedValue;

//если текущий аргумент изменился на величину меньше 0.00001
if(Math.Abs(CurArg-PrevArg)<0.00001)
{
//вернем CurArg в 10 степени
returnedValue=Math.Pow(CurArg, 10);
}
//иначе
else
{
//вернем сумму CurArg и PrevArg в 100 степени
returnedValue=Math.Pow(CurArg+PrevArg,100);
}

return returnedValue;
}
и каким тогда образом поддерживать точность? В таких задачах также можно применять тип Decimal.
Насчет того, что Decimal грузит процессор - думаю для каких-нибудь Core i7, всяких ядер из разряда AMD (Buldozer) это будет вполне простая задача. Еще соглашусь, что это неподъемная проблема для какого-нибудь Celeron 2000.
Спасибо за ответы, но это правда очень странно, всегда воспринимал Double как число двойной точности, а тут с элементарной операцией он справляется как-то странно
252
06 ноября 2012 года
koderAlex
1.4K / / 07.09.2005
возможно это вам поможет :Длинная_арифметика
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог