Здравствуйте, K13, Вы писали:
K13>Похоже, оптимизатор там перекрутили в хлам:
K13>https://godbolt.org/z/xMKEhhhfd
K13>функция из Quake, быстрое вычисление 1/sqrt(number) c небольшой погрешностью
Судя по всему, это все из за индус-стайла (копипаста и в прод). Замени long на что нибудь соразмерное float-y и все станет нормально.
Правда, в продакшин я бы такие хаки пихать все равно не стал.
Токое можно использовать только когда диапазон величин гарантированно укладывается в допустимый, и не страшна погрешность.
Собственно, пихнул я эту функцию на godbolt из любопытства -- будет ли компилятор входной float просто интерпретировать как целочисленный в регистре или полезет через память. и тут вдруг clang 14 выдал мне один ret.
Здравствуйте, K13, Вы писали:
V>>Судя по всему, это все из за индус-стайла (копипаста и в прод). Замени long на что нибудь соразмерное float-y и все станет нормально.
K13>Точно: https://godbolt.org/z/M5bnnv1s4
Вообще этот код тоже UB. Должен быть memcpy или bit_cast (c++20)
чтобы strict aliasing не нарушался.
Здравствуйте, K13, Вы писали:
K13>Похоже, оптимизатор там перекрутили в хлам:
Я давно заметил, что этот компилятор не вменяемый.
Но почему нельзя было написать так:
float Q_rsqrt( float number )
{
union { long i; float y; };
float x2;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed return y;
}
float Q_rsqrt( float number )
{
union { long i; float y; }; // никто не активен
y = number; // теперь активен y
i = 0x5f3759df - ( i >> 1 ); // читаем i, но он не активен, то есть он вне lifetime, это и есть UB. (Затем результат выражения присваивается в i, это делает его активным вместо y, но это уже не важно)
}
Здравствуйте, vsb, Вы писали:
vsb>Здравствуйте, Zhendos, Вы писали:
_>>>Но почему нельзя было написать так:
_>>>
_>>> union { long i; float y; };
_>>>
Z>>Так использование неактивного члена union это тоже UB в C++
vsb>Компилятор на все эти UB хоть ворнинги пишет?
Ну про первоначальный код g++ (с "-O2 -Wall -Wextra") сообщает
"dereferencing type-punned pointer will break strict-aliasing rules".
А вот про "union" не умеет.
V>float Q_rsqrt( float number )
V>{
V> union { long i; float y; }; // никто не активен
V> y = number; // теперь активен y
V> i = 0x5f3759df - ( i >> 1 ); // читаем i, но он не активен, то есть он вне lifetime, это и есть UB. (Затем результат выражения присваивается в i, это делает его активным вместо y, но это уже не важно)
V>}
V>
Здравствуйте, kov_serg, Вы писали: _>Не такой C++ нам не нужен.
Таким C++ был издревна.
Для переносимого кода без неопределённого поведения нужно как указали memcpy, bit_cast.
Здравствуйте, _NN_, Вы писали:
_NN>Для переносимого кода без неопределённого поведения нужно как указали memcpy, bit_cast.
Мне кажется, memcpy int-а и переносимый код это оксюморон. C++ даже поведение при переполнении знакового числа не гарантирует, а тут внутреннее представление.
Здравствуйте, vsb, Вы писали:
vsb>Здравствуйте, _NN_, Вы писали:
_NN>>Для переносимого кода без неопределённого поведения нужно как указали memcpy, bit_cast.
vsb>Мне кажется, memcpy int-а и переносимый код это оксюморон. C++ даже поведение при переполнении знакового числа не гарантирует, а тут внутреннее представление.
Я не про конверсию int-float на уровне битов.
Это в общем про strict aliasing.
Как например здесь нужен memcpy и нельзя reinterpret_cast
// Simple operation just return the value backint foo( unsigned int x ) { return x ; }
// Assume len is a multiple of sizeof(unsigned int) int bar( unsigned char *p, size_t len ) {
int result = 0;
for( size_t index = 0; index < len; index += sizeof(unsigned int) ) {
unsigned int ui = 0;
std::memcpy( &ui, &p[index], sizeof(unsigned int) );
result += foo( ui ) ;
}
return result;
}
Здравствуйте, _NN_, Вы писали:
_NN>Это в общем про strict aliasing.
_NN>Как например здесь нужен memcpy и нельзя reinterpret_cast
_NN> std::memcpy( &ui, &p[index], sizeof(unsigned int) );
Было бы интересно узнать для каких процессоров/платформ необходим этот трюк с memcpy. Т.к. для x86 x64 бинарные данные прекрасно записываются на диск и обратно считываются и кастятся к структурам без этого трюка (даже при #pragma pack(1)). Также и сетевые данные не нуждаются в такой конверсии на этих процессорах. Допускаю, что есть не очень продвинутые процессоры и компиляторы C++ для них(не умеющие заменить присваивание, как в данном случае при reinterpret_cast на memcpy, если надо). Есть ли список таких процессоров/платформ/устройств?
Здравствуйте, _NN_, Вы писали:
_NN>Таким C++ был издревна. https://www.linux.org.ru/forum/development/22475
Нет. Таким его сделали.
_NN>Для переносимого кода без неопределённого поведения нужно как указали memcpy, bit_cast.
Шли бы они в жопу с такими правилами. Мы уж лучше укажем пару ключей -fno-strict-aliasing -fwrapv
Здравствуйте, K13, Вы писали:
K13>Похоже, оптимизатор там перекрутили в хлам: K13>https://godbolt.org/z/xMKEhhhfd K13>функция из Quake, быстрое вычисление 1/sqrt(number) c небольшой погрешностью
Формально компилятор прав. Тут устроено UB которое проявляется при добавлении -Wcast-align.
Возникает оно на 64-битной платформе, из-за разного выравнивания float и long.
Напоминаю сразу, что размер long в windows и linux -- разный
(https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models)
В связи с чем, при добавлении ключика -m32, при сборке на 32-битную платформу проблема
автомагически исключается.