аномалия относительно арифметики с плавающей точкой в c при вычитании числа из себя [дубликата]

0

Рассмотрим следующий фрагмент кода:

float f = 0.01 ;
printf("%f\n",f - 0.01);
if (f - 0.01 == 0)
{
   printf("%f\n",f - 0.01);    
}

Когда я запускаю этот код, для второй строки я получаю вывод -0.000000, а условие if не выполняется.

В чем причина -0.000000?

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

Я использую clang для компиляции моего кода, если это имеет значение.

  • 0
    0.01 не может быть точно представлен в двоичной форме с плавающей точкой.
  • 2
    значения с float не сохраняются как дополнение к 1. Читайте здесь: floatingpoint-gui.de обо всем, что вам нужно знать о значениях с плавающей запятой.
Показать ещё 11 комментариев
Теги:
floating-point

4 ответа

1
Лучший ответ

0.01 является double не float (у вас, вероятно, есть предупреждения об этом при компиляции кода.)

Таким образом, вы в основном конвертируете "0.01" назад и вперед между поплавками и удвоениями, что и является причиной ваших расхождений.

Поэтому решите, хотите ли вы использовать поплавки (например, 0.01f) или парные, и придерживайтесь одной версии на всем протяжении.

Однако, как указывали другие ответы, вы никогда не получите "точного" значения при выполнении арифметики с плавающей запятой - это просто не работает.

Для справки, обе эти версии дадут ответ, который вы ожидаете

float f = 0.01f ;
printf("%f\n",f - 0.01f);
if (f - 0.01f == 0)
{
   printf("%f\n",f - 0.01f);    
}

или

double f = 0.01 ;
printf("%f\n",f - 0.01);
if (f - 0.01 == 0)
{
   printf("%f\n",f - 0.01);    
}

оба печатают

0.000000
0.000000
  • 0
    так как я могу использовать такие числа в сравнениях?
  • 0
    На самом деле, вы получите точный ответ здесь, если все будет float . Хотя значение 0.01F не равно 0,01, оно должно быть везде одинаковым.
Показать ещё 6 комментариев
4

У вас есть две проблемы:

  • 0.01 не может быть точно представлена как двоичное значение с плавающей запятой
  • f имеет тип float а 0.01 имеет тип double

Для вашего расчета требуется преобразование из double в float и обратно, которое (по-видимому) не дает точно того же значения, с которого оно начиналось.

Возможно, вы сможете исправить этот конкретный пример, придерживаясь одного типа (float или double) для всех значений; но у вас все еще будут проблемы, если вы хотите сравнить результаты более сложных вычислений для точного равенства.

0

У вас два двойных 0,01 - один конвертирован в поплавок. Из-за потери точности double (float (0.01))! = Double (0.01)

Даже без очевидной потери точности вы можете столкнуться с проблемами, используя только double (s). Компилятор может хранить один в виде расширенного двойника в регистре и извлекать другой из памяти (сохраняемый как двойной)

  • 0
    Это возможно, но в этом случае все значения являются непосредственными результатами литерала с плавающей запятой, и с точки зрения QoI я бы ожидал, что компилятор сгенерирует целевую точность, и ничего более. (Если он получил их, выполнив 1.0 / 10.0 , с другой стороны, все ставки отключены, и такие вещи, как double d1 = 1.0, d2 = 1.0; d1 /= 10.0; d2 /= 10.0; if ( d1 != d2 ) могут привести к неожиданным результатам. В зависимости от архитектуры и параметров компилятора.
  • 0
    @JamesKanze В C99 и C11 при FLT_EVAL_METHOD> 0 литералы с плавающей точкой могут иметь значение, которое не может быть представлено в их типе. («1 - оценивать операции и константы типа float и double для диапазона и точности типа double…» C99 5.2.4.2.2: 8). Смотрите также результаты для последних версий GCC, скомпилированных с -std=c99 программы, на blog.frama-c.com/index.php?post/2013/07/24/… , особенно для r4 оценкой 0.
0

Причина в том, что 0.01 не может быть правильно отображено в двоичном плавающем числе. Это можно понять на примере: 10/3 дает результат 3.333333333333333....., Т.е. он не может быть правильно отображен в десятичном значении. Аналогичный случай с 0.01. Каждое десятичное число с плавающей запятой не может быть правильно представлено в бинарном эквиваленте с плавающей запятой.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню