2011-02-01 2 views
10

я реализую алгоритм для вычисления натуральных бревен в C.Двойного равен 0 проблемы в С

double taylor_ln(int z) { 
    double sum = 0.0; 
    double tmp = 1.0; 

    int i = 1; 
    while(tmp != 0.0) { 
     tmp = (1.0/i) * (pow(((z - 1.0)/(z + 1.0)), i)); 
     printf("(1.0/%d) * (pow(((%d - 1.0)/(%d + 1.0)), %d)) = %f\n", i, z, z, i, tmp); 
     sum += tmp; 
     i += 2; 
    } 

    return sum * 2; 
} 

Как показан в инструкции печати, TMP делает равный 0,0 в конце концов, однако, цикл продолжается. Что может быть причиной этого?

Я на Fedora 14 amd64 и компиляции с:

clang -lm -o taylor_ln taylor_ln.c 

Пример:

$ ./taylor_ln 2 
(1.0/1) * (pow(((2 - 1.0)/(2 + 1.0)), 1)) = 0.333333 
(1.0/3) * (pow(((2 - 1.0)/(2 + 1.0)), 3)) = 0.
(1.0/5) * (pow(((2 - 1.0)/(2 + 1.0)), 5)) = 0.000823 
(1.0/7) * (pow(((2 - 1.0)/(2 + 1.0)), 7)) = 0.000065 
(1.0/9) * (pow(((2 - 1.0)/(2 + 1.0)), 9)) = 0.000006 
(1.0/11) * (pow(((2 - 1.0)/(2 + 1.0)), 11)) = 0.000001 
(1.0/13) * (pow(((2 - 1.0)/(2 + 1.0)), 13)) = 0.000000 
(1.0/15) * (pow(((2 - 1.0)/(2 + 1.0)), 15)) = 0.000000 
(1.0/17) * (pow(((2 - 1.0)/(2 + 1.0)), 17)) = 0.000000 
(1.0/19) * (pow(((2 - 1.0)/(2 + 1.0)), 19)) = 0.000000 
(1.0/21) * (pow(((2 - 1.0)/(2 + 1.0)), 21)) = 0.000000 
and so on... 
+0

Ничего себе, четыре человека с одинаковым ответом в то же время. – mgiuca

+0

Взгляните на: http://stackoverflow.com/questions/4664662/understanding-floating-point-problems/4664784. Числа с плавающей запятой могут быть очень сложными, если вы не знаете их хорошо. –

+0

@mgiuca: тогда он _must_ будет прав :-) – paxdiablo

ответ

9

Сравнение с плавающей точкой является точной, так 10^-10 не то же самое, как 0.0.

В принципе, вы должны быть сравнения с некоторой допускаемой разницы, скажем 10^-7 на основе количества знаков после запятой вы выписывать, что может быть достигнуто, как:

while(fabs(tmp) > 10e-7) 
+5

И вот обязательная ссылка: [Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой] (http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=86013D0FEFFA6CD1A626176C5D4EF9E2?doi=10.1.1.102.244&rep= rep1 & type = pdf) – chrisaycock

+0

Две вещи: abs - целочисленная операция; используйте 'fabs'. И вы хотите, чтобы * больше * определенного порога, не меньше. – mgiuca

+0

@chrisaycock: Вы должны были ответить на этот вопрос ... это (или упрощенная версия) является правильным ответом на большинство вопросов, связанных с плавающей запятой, здесь. –

1

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

Вы увидите это, если используете %.50f вместо %f в строках формата. Последний использует разумное значение по умолчанию для десятичных знаков (6 в вашем случае), но первый явно указывает, что вы хотите многого.

Для безопасности используйте дельту, чтобы проверить, если это достаточно близко, такие как:

if (fabs (val) < 0.0001) { 
    // close enough. 
} 

Очевидно, что дельта полностью зависит от ваших потребностей. Если вы говорите деньги, 10 -5 может быть много. Если вы физик, вы, вероятно, должны выбрать меньшее значение.

Конечно, если вы математик, не неточность не достаточно мал :-)

0

Просто потому, что ряд проявлений, как «0,000000 «не означает, что он равен 0.0. Десятичное отображение чисел имеет меньшую точность, чем двойной может хранить.

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

В общем, вы не должны сравнивать числа с плавающей запятой с == и !=. Вы всегда должны проверять, находятся ли они в пределах небольшого диапазона (обычно называемого epsilon). Например:

while(fabs(tmp) >= 0.0001) 

Тогда он остановится, когда он получает достаточно близко к 0.

0

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

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

0

Много обсуждения причина, но вот альтернативное решение:

double taylor_ln(int z) 
{ 
    double sum = 0.0; 
    double tmp, old_sum; 
    int i = 1; 
    do 
    { 
     old_sum = sum; 
     tmp = (1.0/i) * (pow(((z - 1.0)/(z + 1.0)), i)); 
     printf("(1.0/%d) * (pow(((%d - 1.0)/(%d + 1.0)), %d)) = %f\n", 
       i, z, z, i, tmp); 
     sum += tmp; 
     i += 2; 
    } while (sum != old_sum); 
    return sum * 2; 
} 

Этот подход ориентирован на делает ли каждый уменьшая значение TMP ощутимую разницу подводить. Это проще, чем выработать некоторый порог от 0, при котором tmp становится несущественным и, вероятно, заканчивается раньше, не изменяя результат.

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

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

  • Нет связанных вопросов^_^