2010-05-07 4 views
7

извините, если немой, но не смог найти ответ.C++ Числовая ошибка усечения

#include <iostream> 

using namespace std; 

int main() 
{ 
double a(0); 
double b(0.001); 
cout << a - 0.0 << endl; 
for (;a<1.0;a+=b); 
cout << a - 1.0 << endl; 
for (;a<10.0;a+=b); 
cout << a - 10.0 << endl; 
cout << a - 10.0-b << endl; 
return 0; 
} 

Выход:
6.66134e-16
0,001
-1.03583e-13

Пробовал компиляции с MSVC9, MSVC10, Borland C++ 2010. Все из них приходят в конец ошибки около 1e-13. Нормально ли иметь такое значительное накопление погрешности только с шагом 1000, 10000?

+3

http://docs.sun.com/source/806-3568/ncg_goldberg.html – Anycorn

+0

http://home.comcast.net/~tom_forsyth/blog. wiki.html # [[A% 20matter% 20of% 20precision]] (не я, хех) –

ответ

13

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

What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

Спасибо, я понимаю ошибку усечения и все остальное дерьмо. Сделал много теоретической работы по численным методам, но я просто никогда не проверял ее сам и был очень удивлен, узнав, насколько это велико ... – Andrew

+1

Ну, двойной дает вам примерно 16 десятичных цифр точности, это примерно так. Когда вы зацикливаете 1000 раз, вы получаете до 13 цифр точности. – WhirlWind

+1

Помните, что это особенно болезненно только с иррациональными (в базе 2) номерами. Если вы используете силу два, все будет в порядке. Например. если сумматор равен 0,5, 0,25, 0,125, 0,0625 и т. д., они в конечном итоге будут составлять * точно * до 1,0, так как эти значения в базе 2 равны 0,1, 0,01, 0,001 и 0,0001. –

1

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

При сравнении двух чисел, вы не можете просто сказать a == b, потому что один может быть 0 и другие -1.03583e-13 из-за потери точности вдоль операций с плавающей запятой применяется, чтобы добраться до a и b. Вы должны выбрать произвольный допуск, например: fabs(a,b) < 1e-8.

При печати номера часто необходимо ограничить количество напечатанных цифр. Если вы используете printf, вы можете сказать printf("%g\n", a);, который не будет печатать такие вещи, как -1.03583e-13. Я не знаю, есть ли iostream аналог %g; здесь?

2

Поэтому при использовании ошибки с плавающей точкой, вы никогда не должны делать:

if(foo == 0.0){ 
    //code here 
} 

и вместо того, чтобы сделать

bool checkFloat(float _input, float _compare, float _epsilon){ 
    return (_input + _epsilon > _compare) && (_input - _epsilon < _compare); 
} 
+1

Проверка на отсутствие штрафа во многих случаях, поскольку его можно точно представить в аппаратном обеспечении, но, конечно, это зависит от того, что вы делаете с ней ... Начиная с 1 и вычитания .1 десять раз не приведет к 0, конечно. –

+1

Это очень верно, но в большинстве случаев это не очень хорошая практика. Мне нравится заблуждаться на стороне осторожности. – wheaties

+0

заблуждался на стороне осторожности ... смешно;) Или, может быть, заблуждался под размером эпсилон. – WhirlWind

2

думать об этом. каждая операция вводит небольшую ошибку, но следующая операция использует слегка ошибочный результат. учитывая достаточно итераций, вы будете отклоняться от истинного результата. если хотите, напишите ваши выражения в форме t0 = (t + y + e), t1 = (t0 + y +e) и выясните условия с epsilon. с их терминов вы можете оценить приблизительную ошибку.

есть также второй источник ошибки: в какой-то момент вы объединяете относительно небольшие и относительно большие числа, к концу. если вы вспомните определение точности машины, 1 + e = 1, в какой-то момент операции будут терять значимые бит.

Надеется, что это помогает прояснить в терминах мирян

Смежные вопросы