Сравнение значения double с литералом в C дает разные результаты на 32-битных машинах.

Может кто-нибудь объяснить, почему:

double d = 1.0e+300;
printf("%d\n", d == 1.0e+300);

Печатает «1», как и ожидалось, на 64-битной машине, но «0» на 32-битной машине? (Я получил это, используя GCC 6.3 на Fedora 25)

Насколько мне известно, литералы с плавающей запятой имеют тип double, и преобразование типов не происходит.

Обновление: это происходит только при использовании флага -std=c99.


person PJK    schedule 31.12.2016    source источник
comment
Используете ли вы какие-либо необычные флаги компилятора?   -  person user2357112 supports Monica    schedule 31.12.2016
comment
@user2357112 user2357112 Нет, ничего не связано с арифметикой. Но удаление -std=c99 приводит к ожидаемому поведению примера, что странно.   -  person PJK    schedule 31.12.2016
comment
Я предполагаю, что второй 1.0e+300 хранится в расширенной точности x87 без перехода через двойную точность, что делает его немного ближе к математически точному значению 10 ^ 300, чем d, и приводит к сбою сравнения. Не мешало бы посмотреть сборку. (Я считаю, что стандарт разрешает это.)   -  person user2357112 supports Monica    schedule 31.12.2016
comment
Возможный дубликат stackoverflow.com/questions/ 18142348/ ?   -  person Barmar    schedule 31.12.2016
comment
Меняется ли что-нибудь с уровнями оптимизации (-O0, -O1, -O2, -O3)? Если вас это утешит, в macOS Sierra 10.12.2 с использованием самодельного GCC 6.3.0 все уровни оптимизации, а также 32-битные и 64-битные компиляции дают результат 0. Насколько я могу судить, при во всяком случае, на более высоких уровнях оптимизации printf() модифицируется, чтобы просто печатать 0 на ассемблере — выражение равенства оценивается во время компиляции, а не во время выполнения. С -O0 (без оптимизации) генерируется гораздо больше кода для выполнения во время выполнения.   -  person Jonathan Leffler    schedule 01.01.2017
comment
Вы должны знать, что никогда не проводите сравнения == для значений float или double, поскольку представление многих/большинства чисел не является точным.   -  person user3629249    schedule 01.01.2017
comment
@user3629249 user3629249 одно не подразумевает другое. Есть много хороших применений для == между значениями с плавающей запятой. С таким же успехом можно сказать: «Никогда не делайте + или * между значениями float и double, поскольку представление многих/большинства чисел неточно».   -  person Pascal Cuoq    schedule 01.01.2017
comment
@PascalCuoq, программист НИКОГДА не может ожидать, что сравнение == между числами с плавающей запятой или двойными числами даст ожидаемые результаты.   -  person user3629249    schedule 03.01.2017
comment
@user3629249 user3629249 Нет, вы не можете ожидать, что == даст ожидаемые результаты. Так что не используйте его. Например, программа в этом посте работает (на платформах IEEE 754, которые правильно определяют FLT_EVAL_METHOD как 0), потому что == дает ожидаемые результаты: blog.frama-c.com/index.php?post/2013/05/01/ . Это только один пример, я использую == все время, как и многие программисты. Ты просто не из их числа.   -  person Pascal Cuoq    schedule 03.01.2017
comment
@PascalCuoq, на stackoverflow.com (и связанных с ним страницах) много вопросов, где программист использовал ==, но это не сработало. Во всех случаях, которые я читал (а их много), обычно это приводит к ссылке на документ stackoverflow, в котором подробно объясняется, почему == не следует использовать при сравнении значений float и double. Я поддерживаю эти пункты. В любом случае, пожалуйста, посмотрите на другой ответ на этот вопрос.   -  person user3629249    schedule 05.01.2017
comment
@user3629249 user3629249 да, но эти вопросы разные (например, программист ожидает 0.1 + 0.2 == 0.3. Это не работает в условиях моего предыдущего комментария и можно ожидать, что оно не сработает, даже если эти условия неверны). Также есть много вопросов, когда программист получил ошибку сегментации из-за использования *p. Вы делаете вывод, что указатели никогда не должны использоваться? Вы ссылаетесь на «документ stackoverflow», не говоря, какой именно. поэтому невозможно объяснить, почему этот документ неверен или вы неправильно его интерпретируете.   -  person Pascal Cuoq    schedule 07.01.2017
comment
@user3629249 user3629249 На этот вопрос есть только один ответ, это не «другой ответ» (ваш комментарий не является ответом, это немного суеверия в комментарии). Ответ не противоречит ничему из того, что я сказал. Это также не оправдывает ваше заявление о том, что «вы должны знать, что никогда не делайте == сравнений для значений с плавающей запятой или двойных значений».   -  person Pascal Cuoq    schedule 07.01.2017
comment
вот цитата из: ‹randomascii. wordpress.com/2012/02/25/› Математика с плавающей запятой не является точной. Простые значения, такие как 0,1, не могут быть точно представлены с помощью двоичных чисел с плавающей запятой, а ограниченная точность чисел с плавающей запятой означает, что небольшие изменения в порядке операций или точности промежуточных значений могут изменить результат. Это означает, что сравнение двух чисел с плавающей запятой, чтобы увидеть, равны ли они, обычно не то, что вам нужно. В GCC даже есть предупреждение на этот счет: «предупреждение: сравнение с плавающей запятой с == или != небезопасно».   -  person user3629249    schedule 08.01.2017
comment
Я предлагаю вам прочитать полную ссылку ‹randomascii .wordpress.com/2012/02/25/  -  person user3629249    schedule 08.01.2017
comment
@PascalCuoq, когда я писал комментарий, было два ответа.   -  person user3629249    schedule 09.01.2017
comment
@ user3629249 Уверяю вас, что «второго ответа» никогда не было (это может подтвердить любой пользователь с репутацией 10000, так как удаленные ответы видны тем). Вы заметили, что слова «обычно нет» в цитируемой вами статье превратились в «никогда» в вашем комментарии?   -  person Pascal Cuoq    schedule 09.01.2017


Ответы (1)


Стандарт C позволяет незаметно распространять константу с плавающей запятой на long double точность в некоторых выражениях (обратите внимание: точность, а не тип). Соответствующий макрос — FLT_EVAL_METHOD, определенный в <float.h> начиная с C99.

Как и в C11 (N1570), §5.2.4.2.2, семантика значения 2 такова:

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

С технической точки зрения, на архитектуре x86 (32-разрядная) GCC компилирует данный код в инструкции FPU, используя x87 с 80-разрядными регистрами стека, а для архитектуры x86-64 (64-разрядная) предпочитает модуль SSE (как скаляры в XMM). регистры).

Текущая реализация была представлена ​​в GCC 4.5 вместе с опцией -fexcess-precision=standard. Из примечаний к выпуску GCC 4.5:

GCC теперь поддерживает обработку избыточной точности с плавающей запятой, возникающую из-за использования модуля x87 с плавающей запятой, в соответствии с ISO C99. Это включается с помощью -fexcess-precision=standard и опций соответствия стандартам, таких как -std=c99, и может быть отключено с помощью -fexcess-precision=fast.

person Grzegorz Szpetkowski    schedule 31.12.2016
comment
Это не вопрос 32-битной и 64-битной версии, а вопрос реализации gcc для x86/amd64. На других архитектурах наблюдения могут отличаться. Сказал, что фактическая причина в том, что типы с плавающей запятой не могут точно представлять практически любую дробь. - person too honest for this site; 01.01.2017
comment
Действительно сборка подтверждает, что это так. Спасибо! - person PJK; 01.01.2017
comment
@Olaf Когда FLT_EVAL_METHOD правильно определяется компилятором как 0 и с другими обычными деталями, определяемыми реализацией, d == 1.0e+300 задает вопрос: «равно ли double, ближайшее к 10 ^ 300, double, ближайшее к 10 ^ 300?». Тот факт, что большинство рациональных чисел не может быть точно представлено в double, не имеет отношения к этому вопросу: ответ «да». Действительно, ответ будет «да», если вы замените double на float и 1.0e+300 на 1.0e+300f, хотя 10^300 находится далеко за пределами диапазона значений, представляемых как конечные числа с плавающей запятой, потому что применяется то же рассуждение. - person Pascal Cuoq; 01.01.2017
comment
Чтобы быть исчерпывающим, когда -fexcess-precision=standard устанавливается напрямую или через -std=c99, поведение сгенерированного 32-битного кода принудительно. Когда -fexcess-precision=fast используется для генерации 32-битного кода, может произойти что угодно, как описано в hal. archives-ouvertes.fr/hal-00128124/document . С -fexcess-precision=fast двойной d может быть равен самому себе или не равен самому себе, в зависимости от окружающего кода и уровня оптимизации. d может отличаться от самого себя в строке 10 и быть равным самому себе в строке 12, даже если строка 11 в исходном коде не меняет d. - person Pascal Cuoq; 01.01.2017