C — суммирование с двойной точностью

#c #double #precision

#c #двойное #точность

Вопрос:

У меня проблема с точностью двойного формата.


Примерный пример:

 double K=0, L=0, M=0;
scanf("%lf %lf %lf", amp;K, amp;L, amp;M);
if((K L) <= M) printf("Incorrect input");
else printf("Right, K=%f, L=%f, M=%f", K, L, M);
  

Мой тестовый ввод:

K = 0.1, L = 0.2, M = 0.3 -> Условие, но переходит к оператору ‘else’.


Как я могу исправить эту разницу? Есть ли какой-либо другой метод суммирования?

Комментарии:

1. Здесь мы снова идем… Пожалуйста, прочтите следующий документ, прежде чем пытаться программировать с плавающей запятой: download.oracle.com/docs/cd/E19957-01/806-3568 /…

2. @PaulR Мы могли бы попросить в meta добавить в качестве новой причины для закрытия «Не знает двоичный формат с плавающей запятой IEEE 754» 🙂

3. Реальный вопрос: почему вы ожидаете, что эта программа будет работать?

Ответ №1:

В мире двоичного формата с плавающей запятой двойной точности IEEE 754 (который используется на процессорах Intel и других процессорах) 0.1 0.2 == 0.30000000000000004 🙂 и 0.30000000000000004 != 0.3 (и обратите внимание, что в чудесном мире double s, 0.1, 0.2 и 0.3 не существуют как «точные» величины. Есть несколько двойных чисел, которые очень близки к ним, но если вы напечатаете их с полной точностью, они не будут равны 0,1, 0,2 и 0,3)

Чтобы немного посмеяться, попробуйте это: http://pages.cs.wisc.edu /~rkennedy/точное значение с плавающей запятой

Вставьте десятичное число и посмотрите на вторую и третью строки, это показывает, как число действительно представлено в памяти. Это для Delphi, но Double и Single одинаковы для Delphi и, вероятно, для всех компиляторов C для процессоров Intel (они называются double and float в C)

И если вы хотите попробовать сами, посмотрите на это http://ideone.com/WEL7h

 #include <stdio.h>

int main()
{
    double d1 = (0.1   0.2);
    double d2 = 0.3;

    printf("%.20en%.20e", d1, d2);

    return 0;
}
  

вывод:
3.00000000000000044409e-01
2.99999999999999988898e-01

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

Ответ №2:

В отличие от целых значений, значения с плавающей запятой не сохраняются точно так, как вы назначаете им значения. Давайте рассмотрим следующий код:

 int i = 1; // this is and always will be 1
float j = 0.03 // this gets stored at least on my machine as something like 0.029999999
  

Почему это так? Ну, сколько чисел с плавающей запятой существует в интервале от 0,1 до 0,2?
Бесконечное число! Итак, есть значения, которые будут сохранены так, как вы намеревались, но чертовски много значений, которые будут сохранены с небольшой ошибкой.

Это причина, по которой сравнение значений с плавающей запятой для равенства не является хорошей идеей. Вместо этого попробуйте что-то вроде этого:

 float a = 0.3f;
float b = 0.301f;
float threshold = 1e-6;

if( abs(a-b) < threshold )
  return true;
else
  return false;
  

Комментарии:

1. » Ну, сколько чисел с плавающей запятой существует в интервале от 0,1 до 0,2? Бесконечное число! » Совершенно не имеет значения. Существует бесконечно много натуральных чисел, но представление любого натурального числа не требует бесконечной памяти. Мы не хотим представлять «все» действительные числа.

2. Хотя верно, что каждое натуральное число может быть представлено с конечным объемом памяти, также верно, что для данного фиксированного объема памяти существует натуральное число, представление которого не помещается в указанную память. Ваша точка зрения о нежелании представлять все действительные числа заключается именно в этом: мы не можем представлять каждое действительное число, и, следовательно, каждая схема будет иметь проблемы того типа, о котором спрашивает исходный вопрос, а именно, существуют действительные числа, которые не могут быть представлены в данной схеме .

Ответ №3:

Существует бесконечно много действительных чисел между любыми двумя различными действительными числами. Если бы мы могли представить каждый из них, нам потребовалась бы бесконечная память. Поскольку у нас есть только конечная память, числа с плавающей запятой должны храниться только с конечной точностью. До этой конечной точности может быть неверно, что 0.1 0.2 <= 0.3.

Теперь вам действительно стоит прочитать, что находится на другом конце отличной ссылки, предоставленной Полом Р.

Комментарии:

1. Но его программа работала бы так, как ожидалось, если бы имела только double десятичное представление, так же, как мы обычно пишем числа на бумаге . «Бесконечная» (или неограниченная) память не требуется: нам не нужно бесконечное пространство для записи десятичного числа. Вы можете запрограммировать тип данных с десятичным представлением, если хотите. Вам придется использовать именованные функции, такие как dec_add , dec_mult … в C, в C вы можете использовать перегрузку операторов, чтобы сохранить обычный , - , * … инфиксный синтаксис.

2. Его конкретный пример сработал бы, но десятичное представление пострадало бы для других чисел. Как бы вы сохранили 1/3 точно с конечным числом членов в десятичном представлении? Вам действительно нужно будет хранить бесконечное количество данных.

3. Опять же, вам не нужно неограниченное пространство для записи «1/3». Если вам нужно сохранить 1/3 точно, опять же, сделайте это так же, как вы бы делали на бумаге: сохраните «1/3» , а не «0,333333333 ….». Это зависит от того, что вам нужно точно представить: опишите свои потребности, затем выберите (или реализуйте) соответствующий тип данных.

4. Сохраняя «1 на 3», вы не сохраняете десятичное представление 1/3. Вы, по сути, сохраняете дробное представление. Я согласен, что если вам нужны только очень ограниченные типы действительных чисел, вам может сойти с рук какая-то специальная схема, но в целом вам понадобится неограниченная память. Подводя итог примерам: IEEE 754 не может хранить 0.1 точно. Десятичное число не может хранить 1/3 точно. Соотношения целых чисел не могут точно хранить число пи. Схема отношения может хранить любое рациональное число ровно до тех пор, пока целые числа, описывающие отношения, не ограничены. Но они ограничены памятью. Бесплатного обеда не будет!

5. » Сохраняя «1 вместо 3″, вы не сохраняете десятичное представление 1/3». Точно. » Я согласен, что если вам нужны только очень ограниченные типы действительных чисел, вам может сойти с рук какая-то специальная схема, но в целом вам понадобится неограниченная память». Нет, ты этого не делаешь. » Отношения целых чисел не могут точно хранить число пи». Если вам нужно «сохранить pi точно», вы можете сохранить его как «pi» (или как что-то менее специальное ). Опять же, все зависит от того, что вам нужно сделать . » Бесплатного обеда не бывает! » Верно: с каждой схемой, разработанной для «точного представления» большего количества действительных чисел, возникает дополнительная сложность кода.