Ответ 1
Вы должны использовать memcpy
. AFAIK это единственный стандартный способ, и компиляторы достаточно умны, чтобы заменить вызов одной инструкцией по перемещению слов. См. этот вопрос для обоснования этих утверждений.
Из того, что я понял о правиле строгого aliasing, этот код для быстрый обратный квадратный корень приведет к поведению undefined в С++:
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // type punning
i = 0x5f3759df - ( i >> 1 );
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) );
return y;
}
Действительно ли этот код вызывает UB? Если да, то как это можно переопределить стандартным образом? Если нет, почему бы и нет?
Предположения: перед вызовом этой функции мы как-то проверили, что поплавки находятся в 32-битном формате IEEE 754, sizeof(long)==sizeof(float)
, а платформа малозначительна.
Вы должны использовать memcpy
. AFAIK это единственный стандартный способ, и компиляторы достаточно умны, чтобы заменить вызов одной инструкцией по перемещению слов. См. этот вопрос для обоснования этих утверждений.
Стандартным образом будет std::memcpy
. Это должно быть достаточно стандартно в соответствии с вашими предположениями. Любой разумный компилятор превратит это в кучу перемещений регистра, если это возможно. Кроме того, мы можем также облегчить (или, по крайней мере, проверить) некоторые ваши сделанные предположения, используя С++ 11 static_assert
и фиксированные ширины целочисленных типов из <cstdint>
. В любом случае, Endianness не имеет значения, так как мы не работаем над любыми массивами здесь, если целочисленный тип малозначен, тип с плавающей запятой также.
float Q_rsqrt( float number )
{
static_assert(std::numeric_limits<float>::is_iec559,
"fast inverse square root requires IEEE-comliant 'float'");
static_assert(sizeof(float)==sizeof(std::uint32_t),
"fast inverse square root requires 'float' to be 32-bit");
float x2 = number * 0.5F, y = number;
std::uint32_t i;
std::memcpy(&i, &y, sizeof(float));
i = 0x5f3759df - ( i >> 1 );
std::memcpy(&y, &i, sizeof(float));
return y * ( 1.5F - ( x2 * y * y ) );
}
Я не думаю, что вы можете сделать это без UB в строгом стандартном смысле, просто потому, что он полагается на такие вещи, как конкретное представление значений float
и long
. Стандарт не указывает эти (специально) и дает реализациям свободу делать то, что они считают подходящим в этом отношении, в частности применяет ярлык "UB" ко всему поведению, которое будет зависеть от таких вещей.
Это не означает, что это будет тип "Heisenbug" или "носовые демоны" UB; скорее всего, реализация даст определения для этого поведения. Пока эти определения удовлетворяют предварительным условиям кода, это прекрасно. Предпосылками здесь являются:
sizeof(float) == sizeof(long)
.float
таково, что если биты интерпретируются как long
, 0x5f3759df
и >> 1
имеют значение, необходимое для работы этого хака.Однако невозможно перезаписать это полностью переносимым способом (т.е. без UB) - что бы вы сделали для платформ, которые используют, например, другую реализацию арифметики с плавающей запятой. Пока вы полагаетесь на специфику конкретной платформы, вы полагаетесь на поведение undefined в стандарте, но определенное платформой.
Не. Алгоритм начинается с предположения о макете IEEE754, который не является допустимым стандартным допуском. Конкретная константа 0x5f3759df
была бы совершенно неправильной для любого другого макета. Даже предположение о существовании такой константы является ошибочным. Я думаю, что он разбивается, как только вы, например, измените порядок полей внутри float.
Я также не уверен, что Q_rsqrt(-0.0f)
работает правильно, даже если IEEE754. Оба нуля (положительные и отрицательные) должны быть равны и sqrt(x)
является только undefined для x<-0.0f