Подтвердить что ты не робот

Сравнение значений с плавающей запятой, преобразованных из строк с литералами

Это не дубликат знаменитой Неисправна математика с плавающей запятой, даже если она выглядит как на первый взгляд.

Я читаю double из текстового файла с помощью fscanf(file, "%lf", &value); и сравнивая его с оператором == с двойным литералом. Если строка совпадает с литералом, будет ли сравнение с использованием == be true во всех случаях?

Пример

Содержимое текстового файла:

7.7

Фрагмент кода:

double value;
fscanf(file, "%lf", &value);     // reading "7.7" from file into value

if (value == 7.7)
   printf("strictly equal\n");

Ожидаемый и фактический результат

strictly equal

Но это предполагает, что компилятор преобразует двойной литерал 7.7 в double точно так же, как и функция fscanf, но компилятор может использовать или не использовать одну и ту же библиотеку для преобразования строк в double.

Или спросил иначе: преобразует ли строка из строки в двойной результат в уникальное двоичное представление или могут быть небольшие различия, зависящие от реализации?

Живая демонстрация

4b9b3361

Ответ 1

О С++, из cppreference можно прочитать:

[lex.fcon] (§6.4.4.2)

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

Поскольку представление плавающего литерала не указано, я думаю, вы не можете сделать вывод о его сравнении с результатом scanf.


О C11 (стандарт ISO/IEC 9899: 2011):

[lex.fcon] (§6.4.4.2)

Рекомендуемая практика

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

Так ясно, что для C11 это не гарантируется.

Ответ 2

Из стандарта С++:

[lex.fcon]

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

внимание мое.

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

Ответ 3

Если строка совпадает с буквой, будет ли сравнение с использованием == истинным во всех случаях?

Общепринятое соображение еще не изучено: FLT_EVAL_METHOD

#include <float.h>
...
printf("%d\n", FLT_EVAL_METHOD);

2 оценивают все операции и константы в диапазоне и точности long double.

Если это возвращает 2, то математика, используемая в value == 7.7, равна long double и 7.7 рассматривается как 7.7L. В случае OP это может быть значение false.

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

scanf(file, "%lf", &value);
double seven_seven = 7.7;
if (value == seven_seven)
  printf("strictly equal\n");

IMO, это более вероятная проблема, чем варианты вариантов округления или вариации в преобразованиях библиотек/компиляторов.


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

float value;
fscanf(file, "%f", &value);
if (value == 7.7)
   printf("strictly equal\n");

Демонстрация

#include <stdio.h>
#include <float.h>
int main() {
  printf("%d\n", FLT_EVAL_METHOD);
  double value;
  sscanf("7.7", "%lf", &value);
  double seven_seven = 7.7;
  if (value == seven_seven) {
    printf("value == seven_seven\n");
  } else {
    printf("value != seven_seven\n");
  }
  if (value == 7.7) {
    printf("value == 7.7\n");
  } else {
    printf("value != 7.7\n");
  }
  return 0;
}

Выход

2
value == seven_seven
value != 7.7

Альтернативный Сравнить

Чтобы сравнить 2 double, которые "близки" друг к другу, нам нужно определение "близко". Полезный подход - рассмотреть все конечные значения double, отсортированные по восходящей последовательности, а затем сравнить их порядковые номера друг от друга. double_distance(x, nextafter(x, 2*x) → 1

Следующий код делает различные предположения о макете и размере double.

#include <assert.h>

unsigned long long double_order(double x) {
  union {
    double d;
    unsigned long long ull;
  } u;
  assert(sizeof(double) == sizeof(unsigned long long));
  u.d = x;
  if (u.ull & 0x8000000000000000) {
    u.ull ^= 0x8000000000000000;
    return 0x8000000000000000 - u.ull;
  }
  return u.ull + 0x8000000000000000;
}

unsigned long long double_distance(double x, double y) {
  unsigned long long ullx = double_order(x);
  unsigned long long ully = double_order(y);
  if (x > y) return ullx - ully;
  return ully - ullx;
}

....
printf("%llu\n", double_distance(value, 7.7));                       // 0
printf("%llu\n", double_distance(value, nextafter(value,value*2)));  // 1
printf("%llu\n", double_distance(value, nextafter(value,value/2)));  // 1

Или просто используйте

if (nextafter(7.7, -INF) <= value && value <= nextafter(7.7, +INF)) {
  puts("Close enough");
}

Ответ 4

Нет никакой гарантии.

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

Также возможно, что оба используют тот же самый алгоритм (например, компилятор преобразует литерал, помещая символы в массив char и вызывая sscanf.

BTW. У меня была одна ошибка, вызванная тем, что компилятор точно не конвертировал буква 999999999.5. Заменили его 9999999995/10.0, и все было в порядке.