Прежде всего, я должен сказать, что поднятая здесь проблема для меня решена, и мне интересно:
Я хочу вычислить норму двумерного вектора, координаты которого вычисляются только в этот момент. Допустим, я хочу рассчитать || (x0, y0) — (x1, y1) || по формуле sqrt ((x0-x1) ** 2 + (y0-y1) ** 2). Только результат должен быть сохранен.
По соображениям производительности квадрат выполняется путем умножения, и я хочу, чтобы вычитания и доступ к переменным выполнялись только один раз. Я хочу, чтобы общее число было эффективным во время выполнения и каким-то образом элегантно закодировано. Я подумал о трех возможностях:
x0 - x1
а также y0 - y1
и надеюсь, что шаг оптимизации компилятора обнаружит повторение,Я решил попробовать последний вариант.
Теперь рассмотрим следующий код:
#include <cmath>
#include <stdio.h>
int main (void)
{
float x0 (-1), x1 (2), y0 (13), y1 (9), result, tmp;
result = std::sqrt ((tmp = x0 - x1, tmp * tmp) + (tmp = y0 - y1, tmp * tmp));
printf ("%f\n", result);
}
Я знаю, что должен получить 5.000000
, но я получаю 5.656854
, который является sqrt ((y0-y1) ** 2 + ((y0-y1) ** 2)).
Я могу получить желаемый результат с:
#include <cmath>
#include <stdio.h>
int main (void)
{
float x0 (-1), x1 (2), y0 (13), y1 (9), result, tmp, tmp2;
result = std::sqrt ((tmp = x0 - x1, tmp * tmp) + (tmp2 = y0 - y1, tmp2 * tmp2));
printf ("%f\n", result);
}
Это похоже на то, что первые части последовательных операторов оцениваются сначала, игнорируя скобки и возвращаемое значение первого последовательного оператора. Кажется немного неловко; Есть ли что-то в определении C ++, что я пропустил здесь?
Примечание: включение или выключение оптимизации ничего не меняет во время теста.
Обе стороны operator +
может оцениваться с чередованием. В частности, каждое из двух назначений в скобках должно произойти перед умножением до его непосредственного права (в правильном коде, т. Е. Ваш первый вариант не является), но не должно происходить перед другим назначением. Кроме того, другое назначение может происходить между одним назначением и соответствующим умножением.
Таким образом, ваш первый вариант вызывает неопределенное поведение, потому что содержит две неупорядоченные модификации tmp
, Таким образом, буквально каждый результат является законным, в том числе сбой или NaN.
В общем, имейте в виду, что «ваш код умный» не является комплиментом. Проще говоря, если вам действительно нужно оптимизировать вычитания (скорее всего, нет):
auto dx = x0 - x1;
auto dy = y0 - y1;
auto result = std::sqrt(dx * dx + dy * dy);
Эта строка:
result = std::sqrt ((tmp = x0 - x1, tmp * tmp) + (tmp = y0 - y1, tmp * tmp));
Вы должны избегать изменения значения, которое вы используете в другом месте в том же выражении. Большинству операторов не гарантируется оценка их операндов в каком-либо конкретном порядке (исключения &&, ||,?: и оператор запятой). В этом случае операнды оператора + в этом выражении могут оцениваться в любом порядке и не обязательно сразу, поэтому ваш код имеет неопределенное поведение.
Кроме того, если вы не профилировали свой код и не знаете, что вам нужно строго оптимизировать конкретное утверждение, вы должны предпочесть ясность, а не хитрые уловки.