Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Для новичков > Точность чисел с плавающей запятой


Автор: IKM2007 14.5.2008, 16:51
Добрый день всем,
ответьте вот на такой вопрос. Как выполняется прибавление или убавление чисел типа double.
Вот на примере.
Код

#include <iostream>
using namespace std;
void main()
{
cout<<(4.2-3.2==2.2-1.2)<<endl;//   0
cout<<(4.2-3.2<2.2-1.2)<<endl;//     1
cout<<(4.2-3.2>2.2-1.2)<<endl;//     0
}

то есть условие (4.2-3.2<2.2-1.2) верно.
Но вот...
Код

#include <iostream>
using namespace std;
void main()
{
cout<<(4.2-3.2==2.2-1.2)<<endl;//       0
cout<<(44.2-33.2<22.2-11.2)<<endl;//       0
cout<<(4.2-3.2>2.2-1.2)<<endl;//       0
}

то есть ни одно условие не верно.
Может кто нибудь обяснит мне, что здесь творится?

Автор: rrrFer 14.5.2008, 17:09
ИМХО парадокс  smile
интересно что:
Код

#include <iostream>
using namespace std;
bool f(double a,double b,double c,double d);
void main(){
    cout<<(4.2-3.2==2.2-1.2)<<endl;
    cout<<f(4.2,3.2,2.2,1.2)<<endl;
    cout<<(4.2-3.2>2.2-1.2)<<endl;
    getchar();
}
bool f(double a,double b,double c,double d){
    if(a-b<c-d)
        return 1;
    return 0;
}
смотрю что передано в функцию f:
    a    4.2000000000000002    double
    b    3.2000000000000002    double
    c    2.2000000000000002    double
    d    1.2000000000000000    double
вообще погрешности большие почему-то:
если написать
Код

cout<<f(1.2,0.2,1.3,0.3)<<endl;

то в функцию f передано:
    a    1.2000000000000000    double
    b    0.20000000000000001    double
    c    1.3000000000000000    double
    d    0.29999999999999999    double

Автор: anatox91 14.5.2008, 17:20
мдаа.. попробовал - действительно...

Автор: rrrFer 14.5.2008, 17:49
а еще вот что получилось:
Код

#include <iostream>
using namespace std;
void main(){
    float d;
    scanf("%f",&d);
    printf("To-4To BBeJ|:\n%.20f\n\nkoHcTaHTa:%.20f\n",d,1.2);
    system("pause");
}

при вводе числа 2.5 все нормально, погрешности не видно вообще, а вот если ввести 1.2....

Автор: Gradov 14.5.2008, 18:32
Цитата(IKM2007 @  14.5.2008,  16:51 Найти цитируемый пост)
то есть ни одно условие не верно.Может кто нибудь обяснит мне, что здесь творится?


Числа с плавающей запятой необходимо сравнивать только предварительно переведя их в строковую переменную. И уже после этого сравнивать строки а не числа с плавающей запятой!

Данная операция необходима для исключения погрешностей, которые возникают из-за особенностей самого формата хранения чисел с плавающей запятой.

Автор: JackYF 14.5.2008, 18:58
Цитата(Gradov @  14.5.2008,  17:32 Найти цитируемый пост)
Числа с плавающей запятой необходимо сравнивать только предварительно переведя их в строковую переменную. И уже после этого сравнивать строки а не числа с плавающей запятой!

Феерическая фигня. Перевод в строковую форму занимает туеву хучу процессорного времени.

Сравнение чисел с плавающей точкой можно сравнивать только с учётом допуска некой малой величины E. То есть, к примеру:
Код

const double E = 1E-5
double x, y;
//...
if (x + E < y)
{
  // x is really less than y...
}

Само собой, E выбирается в зависимости от нужной точности и алгоритма.

Автор: nickless 14.5.2008, 19:17
Цитата(Gradov @  14.5.2008,  17:32 Найти цитируемый пост)
Числа с плавающей запятой необходимо сравнивать только предварительно переведя их в строковую переменную.

Код

if (isDone.toString().length() == 4) {
    // do stuff
}

 smile 

Просто сравнивать надо с погрешностью эпсилон, например так:
Код

const double eps = 1.0e-10;
if (1.2 - eps < f && f < 1.2 + eps) { // f == 1.2
    // code
}

Просто числа хранятся в двоичном виде, по этому число 1.2 = 1 + 1/5 точно представить невозможно, можно только приближенно.
Например в double точность мантиссы 52 бит, т.е. погрешность будет где-то +- 0.5 * 1/(2^52) ~ 1.110223e-16

Автор: archimed7592 14.5.2008, 19:25
Ещё, варианты:
1. Написать класс-обёртку над double(в идеале, над произвольным типом T), который будет проделывать операции сравнения с заданной точностью автоматически.
2. Использовать арифметику с бесконечной точностью(т.н. exact types): GMP, boost::rational, CGAL::Quotient, CGAL::Gmpzf(обёртка над gmp_mpf_class) и пр.
3. Использовать арифметику с неограниченной, но, задаваемой точностью(преимущество в том, что в отличии от exact types с ними можно проделывать такие операции, по определению невозможные для exact types как то sin, cos, sqrt, etc.): MPFR, mpfr::Real(С++ обёртка из MPFR_CPP).

Ссылки на все вышеупомянутые классы/библиотеки можно нарыть в гугле.

ЗЫ. Помню, как лет 5 назад я матерился на Delphi, когда обнаружил в нём "ошибку" из-за которой иногда получается отрицательный ноль из-за чего не работала вычислительная часть одного из моих проектов smile.
На самом деле - это всего лишь отрицательное бесконечно малое, которое, кстати тоже является почвой для долгих поисков мифического бага smile.

Автор: IKM2007 14.5.2008, 20:14
Всем Большое Спасибо, все понятно. smile 

Автор: xvr 15.5.2008, 11:39
Цитата(archimed7592 @ 14.5.2008,  19:25)
ЗЫ. Помню, как лет 5 назад я матерился на Delphi, когда обнаружил в нём "ошибку" из-за которой иногда получается отрицательный ноль из-за чего не работала вычислительная часть одного из моих проектов smile.
На самом деле - это всего лишь отрицательное бесконечно малое, которое, кстати тоже является почвой для долгих поисков мифического бага smile.

Сугубо точности ради  smile В floating point формате знак числа хранится отдельно, так что вполне возможен и реальный отрицательный ноль (float - 0x80000000).

Автор: archimed7592 15.5.2008, 12:44
Цитата(xvr @  15.5.2008,  11:39 Найти цитируемый пост)
Сугубо точности ради  smile В floating point формате знак числа хранится отдельно, так что вполне возможен и реальный отрицательный ноль (float - 0x80000000).

Если мне не изменяет память, этот самый отрицательный ноль и интерпретируется как отрицательное бесконечно малое... Разве нет?

Автор: xvr 15.5.2008, 13:25
Цитата(archimed7592 @ 15.5.2008,  12:44)
Цитата(xvr @  15.5.2008,  11:39 Найти цитируемый пост)
Сугубо точности ради  smile В floating point формате знак числа хранится отдельно, так что вполне возможен и реальный отрицательный ноль (float - 0x80000000).

Если мне не изменяет память, этот самый отрицательный ноль и интерпретируется как отрицательное бесконечно малое... Разве нет?

Нет.
Код

Class             Sign Biased Exponent    Significand
                                       Integer  Fraction
Negative -Zero      1     00..00           0     00..00
         -Denormals 1     00..00           0     00..01
                         .
                         .
                    1     00..00           0     11..11

Integer - implied (not stored)

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)