У меня есть boost :: multiprecision :: cpp_int с большим порядком байтов, и я должен изменить его на младший порядок байтов. Как я могу это сделать? Я попытался с Boost :: Endian :: преобразования, но это не сработало.

boost::multiprecision::cpp_int bigEndianInt("0xe35fa931a0000*);
boost::multiprecision::cpp_int littleEndianInt;

littleEndianIn = boost::endian::endian_reverse(m_cppInt);
2
Bumblebee 29 Апр 2020 в 14:39

2 ответа

Лучший ответ

Расположение в памяти типов повышенной точности является деталью реализации. Так что в любом случае вы не можете много предполагать об этом (они не должны быть побитовыми сериализуемыми).

Просто прочитайте случайный раздел документации:

MinBits

Определяет количество битов для хранения непосредственно в объекте, прежде чем прибегать к динамическому выделению памяти. Когда ноль, это поле определяется автоматически на основе того, сколько битов может быть сохранено в объединении с заголовком динамического хранилища: установка большего значения может улучшить производительность, так как большие целые значения будут храниться внутри, прежде чем потребуется выделение памяти.

Не сразу ясно, что у вас есть шанс на каком-то уровне «нормального поведения int» в макете памяти. Единственное исключение будет, когда MinBits == MaxBits.

Действительно, мы можем static_assert, что размер cpp_int с такими внутренними конфигурациями совпадает с соответствующими байтовыми размерами.

Оказывается, в базовом классе бэкэнда есть даже многообещающий тег для обозначения «тривиальности» (это действительно многообещающе): trivial_tag, так что давайте использовать его:

Live On Coliru

#include <boost/multiprecision/cpp_int.hpp>
namespace mp = boost::multiprecision;

template <int bits> using simple_be =
    mp::cpp_int_backend<bits, bits, mp::unsigned_magnitude>;
template <int bits> using my_int =
    mp::number<simple_be<bits>, mp::et_off>;

using my_int8_t = my_int<8>;
using my_int16_t = my_int<16>;
using my_int32_t = my_int<32>;
using my_int64_t = my_int<64>;
using my_int128_t = my_int<128>;
using my_int192_t = my_int<192>;
using my_int256_t = my_int<256>;

template <typename Num>
    constexpr bool is_trivial_v = Num::backend_type::trivial_tag::value;

int main() {
    static_assert(sizeof(my_int8_t) == 1);
    static_assert(sizeof(my_int16_t) == 2);
    static_assert(sizeof(my_int32_t) == 4);
    static_assert(sizeof(my_int64_t) == 8);
    static_assert(sizeof(my_int128_t) == 16);

    static_assert(is_trivial_v<my_int8_t>);
    static_assert(is_trivial_v<my_int16_t>);
    static_assert(is_trivial_v<my_int32_t>);
    static_assert(is_trivial_v<my_int64_t>);
    static_assert(is_trivial_v<my_int128_t>);

    // however it doesn't scale
    static_assert(sizeof(my_int192_t) != 24);
    static_assert(sizeof(my_int256_t) != 32);
    static_assert(not is_trivial_v<my_int192_t>);
    static_assert(not is_trivial_v<my_int256_t>);
}

Вывод: вы можете иметь тривиальное представление int до определенной точки , после чего вы получаете реализацию динамического лимба на основе распределения независимо от того, что ,

  • Обратите внимание, что использование unsigned_packed вместо unsigned_magnitude представления никогда не приводит к тривиальной внутренней реализации.

  • Обратите внимание, что тривиальность может зависеть от выбора компилятора / платформы (вероятно, cpp_128_t использует некоторую встроенную поддержку компилятора / стандартной библиотеки в GCC, например)


Учитывая это, вы МОЖЕТЕ быть в состоянии извлечь из того, что вы хотели сделать с помощью хаков, ЕСЛИ , свою тривиальность поддержки конфигурации бэкэнда. К сожалению, я думаю, что вам нужно вручную перегрузить endian_reverse для 128-битного случая, потому что встроенные функции GCC не имеют __builtin_bswap128, а Boost Endian не определяет вещи.

Я бы посоветовал отработать эту информацию здесь builtins "> Как заставить GCC генерировать инструкцию bswap для магазина с прямым порядком байтов без встроенных функций?

Финальная демоверсия (не полная)

#include <boost/multiprecision/cpp_int.hpp>
#include <boost/endian/buffers.hpp>
namespace mp = boost::multiprecision;
namespace be = boost::endian;

template <int bits> void check() {
    using T = mp::number<mp::cpp_int_backend<bits, bits, mp::unsigned_magnitude>, mp::et_off>;

    static_assert(sizeof(T) == bits/8);
    static_assert(T::backend_type::trivial_tag::value);

    be::endian_buffer<be::order::big, T, bits, be::align::no> buf;
    buf = T("0x0102030405060708090a0b0c0d0e0f00");

    std::cout << std::hex << buf.value() << "\n";
}

int main() {
    check<128>();
}

(Изменение be::order::big на be::order::native, очевидно, делает его компилируемым. Другой способ завершить его - это иметь доступную ADL перегрузку для endian_reverse для вашего типа int.)

1
sehe 29 Апр 2020 в 16:31

Это и тривиально, и в общем случае неопровержимо, позвольте мне объяснить:

  • Для общего N-разрядного целого числа, где N - большое число, вряд ли будет какой-либо четко определенный порядок байтов, в действительности даже для 64- и 128-разрядных целых чисел используется более 2 возможных порядков: https://en.wikipedia.org/wiki/Endianness#Middle-endian.
  • На любой платформе с любым собственным порядком байтов вы всегда можете извлечь байты cpp_int, первый пример здесь: https://www.boost.org/doc/libs/1_73_0/libs/multiprecision/doc/tm_mml /tut/import_export.html#boost_multiprecision.tut.import_export.examples показывает, как это сделать. При экспорте таких байтов они всегда являются самыми старшими байтами, так что вы можете впоследствии переставить их по своему желанию. Однако не следует переставлять их и загружать обратно в cpp_int, так как класс не будет знать, что делать с результатом!
  • Если вы знаете, что значение достаточно мало, чтобы вписаться в собственный целочисленный тип, то вы можете просто привести к собственному целочисленному типу и использовать системный API для результата. Как и в endian_reverse(static_cast<int64_t>(my_cpp_int)). Опять же, не присваивайте результат обратно в cpp_int, так как для этого требуется собственный порядок байтов.
  • Если вы хотите проверить, является ли значение достаточно маленьким, чтобы поместиться в N-битное целое число для подхода, описанного выше, вы можете использовать функцию msb, которая возвращает индекс старшего значащего бита в cpp_int, добавьте единицу чтобы получить количество используемых битов и отфильтровать нулевой регистр, и код выглядит так:

    unsigned bits_used = my_cpp_int.is_zero ()? 0: msb (my_cpp_int) + 1;

Обратите внимание, что все вышеперечисленное использует полностью переносимый код - взлом базовой реализации не требуется.

0
John Maddock 30 Апр 2020 в 09:56