В Статья MSDN, упоминается, когда включен режим fp: fast, такие операции, как аддитивная идентификация (a±0.0 = a
, 0.0-a = -a
) небезопасны. Есть ли пример, который a+0 != a
под такой режим?
РЕДАКТИРОВАТЬ: Как кто-то упоминал ниже, этот тип проблемы обычно возникает при сравнении. Моя проблема из сравнения, psedocode выглядит следующим образом:
for(i=0;i<v.len;i++)
{
sum+=v[i];
if( sum >= threshold) break;
}
Это ломается после добавления значения 0
(v[i]
). v[i]
не из расчета, он назначен. Я понимаю, если мой v[i]
это из расчета, то округление может вступить в игру, но почему, хотя я даю v[i]
нулевое значение, у меня все еще есть это sum < threshold
но sum + v[i] >= threshold
?
упоминается, когда включен режим fp: fast, такие операции, как аддитивная идентификация (a ± 0.0 = a, 0.0-a = -a), небезопасны.
О чем говорит эта статья
Любое из следующих (небезопасных) алгебраических правил может использоваться оптимизатором, когда включен режим fp: fast:
И тогда он перечисляет ± 0,0 = а и 0,0-а = -а
Это не говорит о том, что эти идентификационные данные небезопасны, если включен fp: fast. Это говорит о том, что эти идентификаторы не верны для арифметики IEEE 754, но что / fp: fast оптимизирует, как если бы они были истинными.
Я не уверен в примере, который показывает, что a + 0.0 == a
быть ложным (за исключением NaN, очевидно), но IEEE 754 имеет много тонкостей, например, когда промежуточные значения должны быть усечены. Одна возможность состоит в том, что если у вас есть какое-то выражение, которое включает в себя + 0.0
, это может привести к требованию в соответствии с IEEE 754 выполнять усечение для промежуточного значения, но при этом / fp: fast будет генерировать код, который не выполняет усечение, и, следовательно, более поздние результаты могут отличаться от того, что строго требуется в IEEE 754.
Используя информацию Паскаля Куока, вот программа, которая производит различный вывод на основе / fp: fast
#include <cmath>
#include <iostream>
int main() {
volatile double a = -0.0;
if (_copysign(1.0, a + 0.0) == _copysign(1.0, 0.0)) {
std::cout << "correct IEEE 754 results\n";
} else {
std::cout << "result not IEEE 754 conformant\n";
}
}
При сборке с / fp: fast программа выдает «результат не соответствует IEEE 754», а при сборке с / fp: strict программа выводит «правильные результаты IEEE 754».
Причина, по которой он «небезопасен», заключается в том, что то, что компилятор предполагает равным нулю, в действительности может не оказаться нулевым из-за ошибок округления.
Возьмем этот пример, который добавляет два числа с плавающей точкой на грани точности, которую допускают 32-разрядные числа с плавающей точкой:
float a = 33554430, b = 16777215;
float x = a + b;
float y = x - a - b;
float z = 1;
z = z + y;
При использовании fp: fast компилятор говорит: «поскольку x = a + b, y = x — a — b = 0, поэтому« z + y »- это просто z». Тем не менее, из-за ошибок округления, y на самом деле заканчивается -1, а не 0. Таким образом, вы получите другой результат без fp: fast.
Он не говорит что-то «фиксированное», например: «если вы установите / fp: fast, а переменная a окажется 3.12345, то + 0 может не быть». Он говорит, что когда вы устанавливаете / fp: fast, компилятор будет использовать ярлыки, которые означают, что если вы вычислите + 0, а затем сравните это с тем, что вы сохранили для a, то нет гарантии, что они будут одинаковыми.
Здесь есть отличная статья об этом классе проблем (которые свойственны вычислениям с плавающей запятой на компьютерах) здесь: http://www.parashift.com/c++-faq-lite/floating-point-arith2.html
Если a
является -0.0, затем a + 0.0
+0,0.