The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

форумы  помощь  поиск  регистрация  майллист  ВХОД  слежка  RSS
"Помогите! Как избавится от ошибок вычислений процессора?"
Вариант для распечатки  
Пред. тема | След. тема 
Форумы Программирование под UNIX (Public)
Изначальное сообщение [Проследить за развитием треда]

"Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от AlexRoot email(ok) on 03-Мрт-05, 15:36 
Возникла огромная проблемма. При выполнении математических вычислений, например, деления одного числа на другое, процессоры допускают ошибки. Например:
double a=1.00000;
double b=2.00000;
double c=0.00000;
c=a/b;
c=?
Часто с=0.50000000000, как и должно быть. Но часто и не равно 0.5000000, а c=0.500000001 или c=0.4999999999999. В этом и есть страшная проблемма. Пробовал писать функции для округления - не получается! В этих функциях при тоже допускаются ошибки!
Далее при сравнении этой переменной "с" с константами программа работает не корректно. Например:
...
if (c>0.50000)
    a=b;
else
    a=c;
....
Считаем, что с=0.5, и вроде все должно быть нормально. А в реальности (при работе с дебагером) с не всегда равно 0.5 а равно или 0.500000001 или 0.499999999! В случае, если с=0.49999999 программа работает не так как хотелось бы. Ужас!
Пожалуйста, подскажите как с этим справлсяться?

С уважением, Александр.

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

 Оглавление

Сообщения по теме [Сортировка по времени, UBB]


1. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от NewComer email on 03-Мрт-05, 16:10 
>Возникла огромная проблемма. При выполнении математических вычислений, например, деления одного числа на
>другое, процессоры допускают ошибки. Например:
>double a=1.00000;
>double b=2.00000;
>double c=0.00000;
>c=a/b;
>c=?
>Часто с=0.50000000000, как и должно быть. Но часто и не равно 0.5000000,
>а c=0.500000001 или c=0.4999999999999. В этом и есть страшная проблемма.

Вы знаете, с этой проблемой все люди живут и даже умудряются вести прецизионные расчеты на ЭВМ :))

Это есть особенность представления действительных чисел в ЭВМ.

Поэтому вы не имеете права сравнивать 2 дабла или флоата влоб, типа : double a,b; if (a == b) do_something(), т.к. не можете полагаться за результат такого сравнения.  

Сравнивать действительные числа можно лишь с точностью до некого эпсилон (машинной ошибки): if (EPS > fabs(a-b)) ...

Это одно из правил хорошего тона в программировании, которому меня научили (спасибо хорошим преподам) на заре туманной юности, когда машины были большими, а языком программирования - фортран 4.

Удачи.

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

2. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от AlexRoot email(ok) on 06-Мрт-05, 05:34 

>Вы знаете, с этой проблемой все люди живут и даже умудряются вести
>прецизионные расчеты на ЭВМ :))
>
>Это есть особенность представления действительных чисел в ЭВМ.
>
>Поэтому вы не имеете права сравнивать 2 дабла или флоата влоб, типа
>: double a,b; if (a == b) do_something(), т.к. не можете
>полагаться за результат такого сравнения.
>
>Сравнивать действительные числа можно лишь с точностью до некого эпсилон (машинной ошибки): if (EPS > fabs(a-b)) ...
>
>Это одно из правил хорошего тона в программировании, которому меня научили (спасибо
>хорошим преподам) на заре туманной юности, когда машины были большими, а
>языком программирования - фортран 4.
>
>Удачи.
>
>

Я, всеже, пытался написать функцию, которая округляла бы число непосредственно перед выполнением логических сравнений. Идея заключалась в том, чтобы представить double в char и округлять до некоторой точности.
Сначала эта функция меня устраивала и нормально работала. Но, спустя месяц я был ошарашен. Всеравно после округления doublе'в этой функцией получался результат "не точный" (0.499999999 или 0.50000000001). Я практически отчаялся что-либо еще писать, но всетаки пытался.
На днях сходил к разработчикам некого серьезного ПО, которым повседневно пользуюсь. Побеседовал с главным конструктором проекта. Он мне подарил небольшую функцию, которая всеже округляет double! Я был удивлен, но она работает! И, кстати, в функции присутствует деление (последняя операция), но деление на 10 (и умножение на 10).
Если Вас заинтересует такая функция, я обязательно Вам ее вышлю. Мне будет интересно Ваше мнение о ней.
Функция вида:
void Okryglenie::Okryglit (double& NumOkr, int tochnost)
{
....
if (...)
    NumOkr*=10;
else
    NumOkr/=10;
}

С уважением, Александр.

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

3. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от ACCA (ok) on 06-Мрт-05, 09:34 
Тезис: у тебя ошибка в семантике, а ты пытаешься исправлять малозначительные погрешности.

Предыдущее утверждение сложно для понимания, тебе придётся много подумать о том, что такое "действительное число", "принятие решения" и "событие".


>или 0.500000001 или 0.499999999! В случае, если с=0.49999999 программа работает не
>так как хотелось бы. Ужас!
>Пожалуйста, подскажите как с этим справлсяться?

Переписать программу, чтобы она не была чувствительна к ошибке вычисления. Это единственный способ решения проблемы.

Допустим путём подбора хитрой функции округления для некоторых операций ты сможешь получать точные 0.5 на своём 32-разрядном однопроцессорном компьютере.

Нет никакой уверенности, что функция станет работать на 8 разрядных машинах без сопроцессора и со слабенькой математической библиотекой, равно как нет уверенности, что это будет работать на 128-разрядном суперкомпьютере.

Как только ты стал считать с плавающей точкой, забудь про строгие равенства и чёткие границы. Если 0.5 настолько критично, считай в целых числах:

int a = 1;
int b = 2;

if (a*2 > b) {
...
}

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


P.S.

Пример с калькулятором. Берём TI-30X, берём квадратный корень из 2.

Возводим его в квадрат = 2, ещё раз = 4, ещё раз = 16, ещё раз = 256, ещё раз = 65535.99999

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

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

4. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от AlexRoot email(ok) on 07-Мрт-05, 04:12 
Спасибо за Ваши рекомендации и замечания. Но, всеже, с посторонней помощью была Нами написана функция округления double до заданной точностью. Ошибок в этой функции не обнаружено. Очень хотелось бы знать, как Вы считаете, возможно ли пользоваться этой функцией, или нет, и почему? Повторюсь, функция РАБОТЕТ КОРРЕКТНО. ОШИБОК Я НЕ ЗАМЕЧАЛ.
Спасибо за ваши комментарии.
С уважением, Александр.

Ниже приводится сама функция.

#include "StdAfx.h"
#include "okryglenie.h"

#ifndef MATH
#define MATH
#include "math.h"
#endif

#define MAX_NUM_DOUBLE_DIGITS 7
#define MIN_DIGIT 1.e-11

Okryglenie::Okryglenie()
{
}

Okryglenie::~Okryglenie()
{
}

void Okryglenie::Okryglit(
    double& DblNum,    // Число, которое необходимо округлить
    int        NumDigits    // Количество знаков после запятой, цифры после которых необходимо округлить
    )
{
    try
    {
        // Если округляемое число меньше заданного количества знаков после запятой, возвращаем 0.0
        if(fabs(DblNum) < MIN_DIGIT)
        {
            DblNum=0.0f;
            throw exExit();
        }

        // Если задали количество знаков бОльшее предельно допустимого, возвращаем double без изменений
        if(NumDigits > MAX_NUM_DOUBLE_DIGITS)
            throw exExit();
        //return DblNum;

        int Order = 0;    // Порядок десятичного числа
        double Quotient, DblNumE6;
        long int NearestInt64; // Эту строку в 7-м вижуал си поменять на "INT64 NearestInt64"

        Quotient = DblNum;

        // Если число находится в пределах от 0.1 до 1.0 или равно 0.1 или 1.0, тогда это число
        // нулевого порядка. Order остается равным нулю.
        if(fabs(DblNum) < 0.1)
        {
            Quotient *= 10.;
            Order--;
            while(fabs(Quotient) < 0.1)
            {
                Quotient *= 10.;
                Order--;
            }
        }
        else if(fabs(DblNum) > 1.)
        {
            Quotient /= 10.;
            Order++;
            while(fabs(Quotient) > 1.)
            {
                Quotient /= 10.;
                Order++;
            }
        }

        DblNumE6 = Quotient * pow(10., (double)NumDigits);   // Quotient * (10.**NumDigids)

        int Sign = 1;
        if(DblNum < 0.0)
            Sign = -1;

        NearestInt64 = (long int)(DblNumE6 + Sign * 0.5); // В 7-м вижуал си поменять эту строку
        // на "NearestInt64 = (INT64)(DblNumE6 + Sign * 0.5);"

        Quotient = NearestInt64 / pow(10., (double)NumDigits); // NearestInt / (10.**NumDigids)

        DblNum = Quotient;

        if( Order <= -1*NumDigits )
        {
            DblNum = 0.0f;
            throw exExit();
            //return DblRound;
        }

        if( Order > 0 )
        {
            while(Order > 0)
            {
                Quotient = Quotient * 10.;
                Order--;
            }
            DblNum = Quotient;
        }
        else if( Order < 0 )
        {
            while(Order < 0)
            {
                Quotient = Quotient / 10.;
                Order++;
            }
            DblNum = Quotient;
        }
    }

    catch (Okryglenie::exExit)
    {
    }
    //return DblRound;
}

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

5. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от ACCA (ok) on 07-Мрт-05, 07:17 
>функции не обнаружено. Очень хотелось бы знать, как Вы считаете, возможно
>ли пользоваться этой функцией, или нет, и почему? Повторюсь, функция

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

Предположим, что мы бросаем монетку и нам нужно точно сказать, чего выпало больше - орлов или решек. Мы бросили монетку 2e+508 раз. Сможет ли этот алгоритм округления решить поставленную задачу, если тупо считать, сколько чего выпало?

А вот если считать отклонение

int dev = 0;

if (tail) dev++;
else      dev--;

то скорее всего нам хватит 8-разрядного целого числа и результат будет _точным_.

Ещё раз про семантику - не стоит сравнивать вещественные числа, если разница между ними лежит в пределах ошибки вычисления. Мы не знаем, то ли это было два одинаковых числа, то ли два разных, но округлённых до одинакового.

Предположим, что мы ищем минимум параболы. Значение Y(X1) очень близко к значению Y(X2) и мы можем навычислять, что они совпадают, из чего можно сделать вывод что X1 и X2 принадлежат к одному множеству. Однако для второй производной значение dY(X1) - отрицательное, а dY(X2) - положительное. Они принципиально отличаются.

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

6. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от AlexRoot email(ok) on 08-Мрт-05, 03:53 
Хочу здесь немного подытожить выше сказанное.

1. Если я правильно понял, Вы считаете, что подобрать функцию округления - невозможно!
2. Сравнивать double можно только до какой-то точности.
Пример сравнения.

double a=10.0;
double b=2.0;
double c=0.0;
const double KRITERIY=5.0; // какая-то величина, с которой сравниваем
const double TOCHNOST=5;   // знаков после запятой

if ( a/b+1/(10*TOCHNOST) > KRITERIY || a/b-1/(10*TOCHNOST) > KRITERIY )
// do something
else
// do something

Так чтоли? Я правильно понял?

Если я ошибаюсь, поправте пожалуйста. И, если не сложно, объясните не сложно :)

С уважением, Александр

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

7. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от ACCA (ok) on 09-Мрт-05, 13:33 
>1. Если я правильно понял, Вы считаете, что подобрать функцию округления -
>невозможно!

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


>2. Сравнивать double можно только до какой-то точности.
>Пример сравнения.
>
>double a=10.0;
>double b=2.0;
>double c=0.0;
>const double KRITERIY=5.0; // какая-то величина, с которой сравниваем
>const double TOCHNOST=5;   // знаков после запятой
>
>if ( a/b+1/(10*TOCHNOST) > KRITERIY || a/b-1/(10*TOCHNOST) > KRITERIY )
>// do something
>else
>// do something

Немного не так, хотя это мелочи:

#define TOCHNOST 0.02

if (a/b > KRITERIY-TOCHNOST && a/b < KRITERIY+TOCHNOST)

Произнеси это вслух - ты хочешь сказать, что если a/b попадает в интервал [4.98 ... 5.02], то ты считаешь, что у тебя появилось 5. Вопрос только в том, правильно ли это с точки зрения задачи. И нужно быть осторожным с высокими значениями точности - run-time библиотека/процессор могут не справиться.

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

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

8. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от AlexRoot email(ok) on 09-Мрт-05, 17:53 
Уважаемый АССА, спасибо за Ваши комментарии.

Прийдется все переписать...  :(

С уважением, Александр.

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

9. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от mirya email on 09-Мрт-05, 21:17 
Как я понял, у человека 1.0/2.0 дает разные результаты в зависимости от погоды. Но у нас все-таки цифровой FPU, а не аналоговый - откуда же берется случайность?

Причем как раз деление 1.0/2.0 по идее должно давать ровно 0.5, т.к. в двоичном представлении это рациональные числа

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

10. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от ACCA (ok) on 10-Мрт-05, 02:33 
>Как я понял, у человека 1.0/2.0 дает разные результаты в зависимости от
>погоды. Но у нас все-таки цифровой FPU, а не аналоговый -
>откуда же берется случайность?

У него 1.0 и 2.0 не константы, сунутые компилятору, а результаты предыдущих вычислений. То есть 1.0 может быть равно и 1.0000000000000001 и 0.999999999999999.

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

11. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от MaximKuznetsov on 10-Мрт-05, 10:02 
>>Как я понял, у человека 1.0/2.0 дает разные результаты в зависимости от
>>погоды. Но у нас все-таки цифровой FPU, а не аналоговый -
>>откуда же берется случайность?
>
>У него 1.0 и 2.0 не константы, сунутые компилятору, а результаты предыдущих
>вычислений. То есть 1.0 может быть равно и 1.0000000000000001 и 0.999999999999999.
>
раз уж пошла такая тема - www.xsc.de - C библиотека для интервальных вычислений и диалект паскаля для тех-же целей.


Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

12. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от AlexRoot email(ok) on 12-Мрт-05, 11:51 
Господа, хочу задать Вам встречный вопрос. Следующий код будет давать точный результат?

// Объявляем и инициализируем
double a=1.0;              // Это число делим и умножаем
__int16 b=10;              // На это умножаем и делим
__int64 temp=0;            // Временная переменная
double result=0.0;         // Результат

// Вопрос вот в чем, при делении и умножении переменной "а" в последующем
// коде, всеже, будем получать точный результат или нет, ведь судя по
// высказыванию mirya при делении или умножении на 10 получаем точный
// результат?

temp=(__int64) a*b;
result=(double) temp/b;

// Переменная result точна или нет?

Если точный, тогда функция, приведенная мною выше ВСЕГДА будет давать точный результат. Приделите пожалуйста внимание, пока она действительно дает ТОЧНЫЙ результат :)

С уважением, Александр.

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

13. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от ACCA (ok) on 12-Мрт-05, 12:08 
Я тут немного соптимизировал код под задачу...

char a = 1,
     b = 10,
     temp,
     result;

temp = a*b;
result = temp/b;

Вроде бы результат должен быть точный?

:)

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

14. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от mirya email on 12-Мрт-05, 13:34 
Повторюсь - у нас двоичная машинка, т.е. умножения и деления только на СТЕПЕНИ ДВОЙКИ будут давать точные результаты, насколько помню, { ln<sub>2</sub> 10 } != 0 :) - соотв. будет бесконечный периодический двоичный результат
Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

16. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от rgo on 15-Мрт-05, 05:36 
1. Если на FPU выполнять один и тот же набор операций над одними о теми же данными результат _всегда_ будет одинаковый. Но если вы возьмёте другой FPU (к другому процессору приложеный, может не от intel), или попробуете пользовать эиуляцию, то результат может оказаться слегка другим, например, потому, что при округлении (в пятнадцатом или каком там знаке) он округляет иначе.
2. 5.0000000 и 4.9999999 это одно и тоже. Даже в математике 5 == 4.(9) (девятка в периоде). Это проблемы записи.
3. очень рекомендую читать Д.Кнута, там написано почему не стоит использовать оператор `==' в программе, и откуда берутся погрешности.
4. А вообще возми C++ и переопредели operator==(double, double) в каком-нибудь хедере примерно так:

static inline bool operator== (double a, double b) {
return fabs (a - b) < .0005
}

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

15. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от ZOD (??) on 14-Мрт-05, 17:54 
ИМХО Писать свои float 128 или там 1024 в зависимости от того какие ошибки при вычислениях Вас устраивают. Вы в любом случае получите  эффект потери значащих чисел. Так как точка плавающая то результат одной и той же операции будет зависеть от количества знаков в мантисе и экспоненте, то есть будет разным для разных чисел. При желании можно сунуться в учебник и всё подсчитать.
Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

17. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от AlexRoot email(ok) on 16-Мрт-05, 14:58 
Благодарю Вас за рекомендации.
Хотелось бы услышать побольше мнений, особенно людей, которые решали данную проблему, и как Они ее решили (практически).

з.ы. Д.Кнута пока что не читал :( может кто подскажеть где она лежит? (возможно в формате pdf)

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

18. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от sml (??) on 18-Мрт-05, 16:25 
>Благодарю Вас за рекомендации.
>Хотелось бы услышать побольше мнений, особенно людей, которые решали данную проблему, и
>как Они ее решили (практически).
>
>з.ы. Д.Кнута пока что не читал :( может кто подскажеть где она
>лежит? (возможно в формате pdf)


Ну вот я, к примеру, решал похожую проблему.
В одной большой и серьезной системе длительное время денежные величины
представлялись в виде float. Да, даже не double, а float.
Это нехорошо, но денежные расчеты для этой системы не являются основной функцией, поэтому долгое время все всех устраивало, хотя иногда и вылезали неприятные артефакты. Ну, например, складываем несколько чисел типа float, а потом вычитаем их же их полученной суммы. Так вот, в результате ноль получался не всегда. Поэтому приходилось сравнивать результат не с нулем, а с маленьким по модулю числом, заведомо меньшим, чем минимальное реальное значение в данном приложении. Ну, такие приемы всем известны и описаны наверное в книгах, которые я давно уже не читал...

Потом все стало хуже. Изучение проблемы привело к выводу, что в рамках float, double, long double и т.д. в принципе нельзя делать точных денежных вычислений потому, что эти числа являются ДВОИЧНЫМИ ЧИСЛАМИ С ПЛАВАЮЩЕЙ ТОЧКОЙ. Да никто так и не делает, если серьезно.

Например, двоичное число 0.1 - это десятичное 0.5
А вот если, к примеру, десятичное 0.2 перевести в двоичный вид, то получите бесконечную периодическую двоичную дробь. Попробуйте.

Но в комьютере все числа конечные, поэтому В КОМПЬЮТЕРЕ НЕВОЗМОЖНО ТОЧНО ПРЕДСТАВИТЬ ЧИСЛО 0.2 В ФОРМАТЕ С ПЛАВАЮЩЕЙ ТОЧКОЙ.
Просто невозможно и все.
И так обстоит дело с большинством чисел, которые являются конечными десятичными дробями.

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

Если Вам нужна точность, то Вы должны использовать целочисленную арифметику. Числа с плавающей точкой - они не для этого.

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

19. "Помогите! Как избавится от ошибок вычислений процессора?"  
Сообщение от Polushkin Pavel on 27-Сен-06, 11:22 
>На этом изучение проблемы было прекращено, а для денежных величин были сделаны
>собственные классы, которые внутри работают только с целыми числами.

Не подскажите, существуют ли такие готовые классы, например, для CBuilder ?

Правка | Высказать мнение | Ответить | Cообщить модератору | Наверх

Архив | Удалить

Индекс форумов | Темы | Пред. тема | След. тема
Оцените тред (1=ужас, 5=супер)? [ 1 | 2 | 3 | 4 | 5 ] [Рекомендовать для помещения в FAQ]




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру