Некоторые языки (например, Rust, Zig, GLSL, HLSL) имеют встроенные методы для создания плавающего типа из битов, представленных в виде целого числа без знака. Однако C и C++ не имеют для этого стандартных функций.
С C99 мы можем использовать анонимные объединения с инициализацией члена для реализации макроса каламбура типа с тем же эффектом:
#define FLOAT_FROM_BITS(U,F,b) (((union{U u; F f;}){.u=(b)}).f)
#define FLOAT32_FROM_BITS(i) FLOAT_FROM_BITS(uint32_t, float, i)
#define FLOAT64_FROM_BITS(i) FLOAT_FROM_BITS(uint64_t, double, i)
который впоследствии можно использовать для инициализации const/static с помощью. Какой самый элегантный способ сделать это на С++, чтобы его также можно было использовать для статической инициализации?
@NathanOliver: C++20 на самом деле является моим текущим целевым профилем. Я не знал о существовании std::bit_cast
. Однако ради людей, которые еще не могут его использовать, я хотел бы увидеть другие подходы, если они возможны.
Вы должны иметь возможность просто написать инициализацию как constinit
, вызывая при этом только функцию constexpr
, и компилятор вставит для вас правильный битовый шаблон. Возможно, вам придется реализовать некоторые математические функции самостоятельно, если компилятор не считает их constexpr, стандарт немного отстает.
Элегантный способ — использовать std::bit_cast
:
std::uint64_t i = example_value();
auto d = std::bit_cast<double>(i);
Если вы можете использовать С++ 20 или выше, используйте std::bit_cast
, например
auto myvar = std::bit_cast<type_to_cast_to>(value_to_cast);
Если вы хотите поддерживать более старые версии, вы можете сделать то же самое, используя std::memcpy
для копирования байтов из одного типа в другой. Это даст вам такую функцию, как
template <class To, class From>
To bit_cast(const From& src)
{
To dst;
std::memcpy(&dst, &src, sizeof(To));
return dst;
}
Но работает ли memcpy со статической инициализацией? Например. если бы я хотел написать что-то вроде float const my_magic_constant = FLOAT_FROM_BITS(…);
в идеале, когда компилятор просто выдавал необработанную константу в сгенерированном коде.
Что ж, мне нужна инициализация constexpr
.
@datenwolf В этом случае это будет работать, но, скорее всего, функция будет работать во время выполнения, поэтому в ваш двоичный файл не запекается константа.
@datenwolf В таком случае вам нужно std::bit_cast
. Возможно, вы сможете сделать свой собственный, но вам нужно будет изучить, какие встроенные функции доступны в вашей реализации, и вы сможете создать свою собственную версию, используя их.
Ну, это облом; рассматриваемая константа будет применяться в самом внутреннем цикле некоторой функции преобразования данных DSP. Я как бы не хочу загружать эту константу из памяти (хотя, я думаю, когда она находится в регистре, это не имеет большого значения).
Не должно быть слишком сложно сравнить их, чтобы увидеть, действительно ли это имеет значение для производительности.
@datenwolf Как инициализация constexpr влияет на «загрузку из памяти»?
@eerorika: значение, которое я хочу применить, представляет собой фиксированную константу с определенным битовым шаблоном, который известен во время компиляции. Строго говоря, во время генерации кода компилятор может просто вставить этот конкретный битовый шаблон в сгенерированный код в качестве операнда с постоянным значением. Что избавило бы от загрузки одного слова из памяти. Теперь, поскольку эта константа используется во всем самом внутреннем цикле, который происходит только один раз. Но представьте, что алгоритм состоит из большого количества таких констант (больше, чем доступных регистров), тогда вы рискуете столкнуться с проблемами задержки памяти (в случае промахов кеша L1).
@eerorika: Для практического примера таких «магических» констант найдите алгоритм быстрый обратный квадратный корень (иногда ошибочно приписываемый Кармаку, но он намного старше, и мы не знаем, кто именно был безумным гением, который нашел эту магическую константу в первую очередь) ).
@datenwolf Я провел несколько экспериментов, и оказалось, что GCC никогда не использует mov eax, CONSTANT
с поплавками. Он использует его только с целыми числами. Даже со значением constexpr он использует movss xmm0, DWORD PTR .LCx[rip]
для загрузки значения в регистр. Единственная разница с использованием std::memcpy
заключается в том, что оно копирует значение в память при запуске (с использованием того же трюка mov DWORD PTR c1[rip], CONSTANT
) вместо использования сегмента только для чтения.
@eerorika: я могу подтвердить ваши результаты для x86[_64] для GCC, CLang и MSVC. Однако для AArch64 и GCC, и Clang будут выдавать немедленные значения для float32, а Clang также для float64, но не для GCC: godbolt.org/z/Y4GTreaqb
Какую версию C++ вы можете использовать? C++20 поставляется с
std::bit_cast