Сравнение значений с плавающей запятой, преобразованных из строк с литералами
Это не дубликат знаменитой Неисправна математика с плавающей запятой, даже если она выглядит как на первый взгляд.
Я читаю 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.
Или спросил иначе: преобразует ли строка из строки в двойной результат в уникальное двоичное представление или могут быть небольшие различия, зависящие от реализации?
Живая демонстрация
Ответы
Ответ 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, и все было в порядке.