admin管理员组

文章数量:1122832

Packing and Unpacking 128-bit Integers using Boost Multiprecision

I'm compressing two 64-bit integers, i0 and i1, into a single 128-bit integer using the formula i = i0 * 2^64 + i1. The resulting multi-precision integer will undergo various arithmetic operations. However, I'm struggling to define the reverse operation to extract the original i0 and i1 values from the multi-precision integer.

The goal is to create the function unpack that is the inverse of the function pack is defined by

using int128_t = boost::multiprecision::int128_t;
// This packing is the definition of the desired multi-precision integer.
// Its definition is not negotiable.
auto pack = [](const std::array<int64_t, 2>& value) {
    return (((static_cast<int128_t>(1) << 64) * value[0]) + value[1]);
};

The following code fails to properly invert the packing with boost::multiprecision::int128_t : C++

auto unpack = [](const int128_t& value) {
    const auto lo = static_cast<int64_t>(value % (static_cast<int128_t>(1) << 64));
    return std::array<int64_t, 2>{
        static_cast<int64_t>((value - lo) / (static_cast<int128_t>(1) << 64)),
        lo
    };
};

The unpacking function doesn't work as expected. However, the same code works fine when using __int128 (a two's complement implementation). Does anyone know how to implement the unpack function to work correctly with Boost Multiprecision?

A version of the code that tests both boost's and native's int128 implementations is available by following the link:

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <array>
#include <iomanip>
#include <utility>
#include <algorithm>
#include "boost/multiprecision/cpp_int.hpp"

#pragma GCC diagnostic ignored "-Wpedantic"


template <typename OStream> OStream& operator<< (OStream& stream, const std::array<int64_t, 2>& value)
{
    stream << "{ " << value[0] << ", " << value[1] << " }";
    
    return stream;
}

template <typename OStream> OStream& operator<< (OStream& stream, __int128 value)
{
    if (value == 0) {
        stream << "0";
        return stream;
    }

    std::string result;
    const auto is_negative = (value < 0);

    if (is_negative) 
        value = -value;

    while (value > 0) {
        result = char('0' + (value % 10)) + result;
        value /= 10;
    }

    if (is_negative)
        result = "-" + result;

    stream << result;

    return stream;
}

int main()
{
#define USE_INT128
#ifdef USE_INT128
    using int128_t = __int128;
    constexpr auto ExtendedType = " Extended type __int128";
#else
    using int128_t = boost::multiprecision::int128_t;
    constexpr auto ExtendedType = " Extended type boost::multiprecision::int128_t";
#endif

    auto pack = [](const std::array<int64_t, 2>& value) {
        return (((static_cast<int128_t>(1) << 64) * value[0]) + value[1]);
    };
    
    auto unpack = [](const int128_t& value) {
        const auto lo = static_cast<int64_t>(value % (static_cast<int128_t>(1) << 64));
        return std::array<int64_t, 2>{
            static_cast<int64_t>((value - lo) / (static_cast<int128_t>(1) << 64)),
            lo
        };
    };

    for (auto hiLo : { 
        std::array<int64_t, 2> { static_cast<int64_t> (-2), (static_cast<int64_t> (1) << 63) },
        std::array<int64_t, 2> { static_cast<int64_t> (-1), 1 },
        std::array<int64_t, 2> { 1, -10 }, 
        std::array<int64_t, 2> { 1, (static_cast<int64_t> (1) << 63) } 
        })
    {
        std::cout << "====================================" << ExtendedType << std::endl;
        std::cout << "hiLo    = " << hiLo << std::endl;
        const auto extended = pack (hiLo);
        const auto backToHiLo = unpack (extended);

        std::cout << "extended = " << extended << std::endl;
        if (hiLo != backToHiLo)
         std::cout << "Failed to unpack - to_HiLo (to_ExtendedInteger (" << hiLo << ")) = " << backToHiLo << std::endl;
        std::cout << "eExtended   = " << pack (backToHiLo) << std::endl << std::endl;
     }

    return 0;
}

Packing and Unpacking 128-bit Integers using Boost Multiprecision

I'm compressing two 64-bit integers, i0 and i1, into a single 128-bit integer using the formula i = i0 * 2^64 + i1. The resulting multi-precision integer will undergo various arithmetic operations. However, I'm struggling to define the reverse operation to extract the original i0 and i1 values from the multi-precision integer.

The goal is to create the function unpack that is the inverse of the function pack is defined by

using int128_t = boost::multiprecision::int128_t;
// This packing is the definition of the desired multi-precision integer.
// Its definition is not negotiable.
auto pack = [](const std::array<int64_t, 2>& value) {
    return (((static_cast<int128_t>(1) << 64) * value[0]) + value[1]);
};

The following code fails to properly invert the packing with boost::multiprecision::int128_t : C++

auto unpack = [](const int128_t& value) {
    const auto lo = static_cast<int64_t>(value % (static_cast<int128_t>(1) << 64));
    return std::array<int64_t, 2>{
        static_cast<int64_t>((value - lo) / (static_cast<int128_t>(1) << 64)),
        lo
    };
};

The unpacking function doesn't work as expected. However, the same code works fine when using __int128 (a two's complement implementation). Does anyone know how to implement the unpack function to work correctly with Boost Multiprecision?

A version of the code that tests both boost's and native's int128 implementations is available by following the link:

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
#include <array>
#include <iomanip>
#include <utility>
#include <algorithm>
#include "boost/multiprecision/cpp_int.hpp"

#pragma GCC diagnostic ignored "-Wpedantic"


template <typename OStream> OStream& operator<< (OStream& stream, const std::array<int64_t, 2>& value)
{
    stream << "{ " << value[0] << ", " << value[1] << " }";
    
    return stream;
}

template <typename OStream> OStream& operator<< (OStream& stream, __int128 value)
{
    if (value == 0) {
        stream << "0";
        return stream;
    }

    std::string result;
    const auto is_negative = (value < 0);

    if (is_negative) 
        value = -value;

    while (value > 0) {
        result = char('0' + (value % 10)) + result;
        value /= 10;
    }

    if (is_negative)
        result = "-" + result;

    stream << result;

    return stream;
}

int main()
{
#define USE_INT128
#ifdef USE_INT128
    using int128_t = __int128;
    constexpr auto ExtendedType = " Extended type __int128";
#else
    using int128_t = boost::multiprecision::int128_t;
    constexpr auto ExtendedType = " Extended type boost::multiprecision::int128_t";
#endif

    auto pack = [](const std::array<int64_t, 2>& value) {
        return (((static_cast<int128_t>(1) << 64) * value[0]) + value[1]);
    };
    
    auto unpack = [](const int128_t& value) {
        const auto lo = static_cast<int64_t>(value % (static_cast<int128_t>(1) << 64));
        return std::array<int64_t, 2>{
            static_cast<int64_t>((value - lo) / (static_cast<int128_t>(1) << 64)),
            lo
        };
    };

    for (auto hiLo : { 
        std::array<int64_t, 2> { static_cast<int64_t> (-2), (static_cast<int64_t> (1) << 63) },
        std::array<int64_t, 2> { static_cast<int64_t> (-1), 1 },
        std::array<int64_t, 2> { 1, -10 }, 
        std::array<int64_t, 2> { 1, (static_cast<int64_t> (1) << 63) } 
        })
    {
        std::cout << "====================================" << ExtendedType << std::endl;
        std::cout << "hiLo    = " << hiLo << std::endl;
        const auto extended = pack (hiLo);
        const auto backToHiLo = unpack (extended);

        std::cout << "extended = " << extended << std::endl;
        if (hiLo != backToHiLo)
         std::cout << "Failed to unpack - to_HiLo (to_ExtendedInteger (" << hiLo << ")) = " << backToHiLo << std::endl;
        std::cout << "eExtended   = " << pack (backToHiLo) << std::endl << std::endl;
     }

    return 0;
}
Share Improve this question edited Nov 24, 2024 at 21:50 Catriel asked Nov 22, 2024 at 21:32 CatrielCatriel 6294 silver badges17 bronze badges 0
Add a comment  | 

3 Answers 3

Reset to default 0

The problem is from sign extension. Bit manipulation becomes a lot easier if you make the inert parts uint64_t. Is there a reason you can't do that?

Here's a demo, updated for the comments. We generalize Tuple (which can be array<int64_t, 2>, array<uint64_t, 2>, tuple<int64_t, uint64_t> etc.) as well as T (which can be Boost's or the compiler builtin 128 bit signed integer type).

template <typename Tuple> Tuple make(auto hi, auto lo) {
    return Tuple{
        std::tuple_element_t<0, Tuple>(hi),
        std::tuple_element_t<1, Tuple>(lo),
    };
};

template <typename T, typename Tuple> T single_test(Tuple const& hiLo) {
    auto pack = [](Tuple const& v) {
        static_assert(sizeof(v) == 2 * sizeof(int64_t));
        auto& [hi, lo] = v;
        return (T(static_cast<int64_t>(hi)) << 64) + static_cast<uint64_t>(lo);
    };
    auto unpack = [](T const& v) -> Tuple {
        static constexpr T scale64 = T(1) << 64;
        return make<Tuple>(                          //
            static_cast<int64_t>(v >> 64),           //
            static_cast<uint64_t>(v & (scale64 - 1)) //
        );
    };

    auto const extended  = pack(hiLo);
    auto const roundtrip = unpack(extended);

    if (hiLo != roundtrip)
        fmt::print("\n\n ****Failed*** to unpack {} - to_HiLo (to_ExtendedInteger ({})) = {}\n\n", //
                   Name<T>, hiLo, roundtrip);
    // fmt::print("eExtended   = {0}\n\n", pack(roundtrip));
    return extended;
}

template<typename Tuple> void run_tests() {
    fmt::print("\n==== {} ====\n", __PRETTY_FUNCTION__);
    for (auto hiLo : {
             make<Tuple>(0xFFFFFFFFFFFFFFFE, 0x8000000000000000),
             make<Tuple>(0xFFFFFFFFFFFFFFFF, 1),
             make<Tuple>(1, 0xFFFFFFFFFFFFFFF6),
             make<Tuple>(1, 0x8000000000000000),
         }) {
        // fmt::print("hiLo      = {}\n", hiLo);
        auto a     = single_test<__int128_t>(hiLo);
        auto b     = single_test<boost::multiprecision::int128_t>(hiLo);
        auto match = (a == b);
        fmt::print("Matching: {} ({} == {})\n", match, a, b);
        assert(match);
    }
}

int main() {
    run_tests<std::tuple<uint64_t, uint64_t>>();
    run_tests<std::tuple<uint64_t, int64_t>>();
    run_tests<std::tuple<int64_t, uint64_t>>();
    run_tests<std::tuple<int64_t, int64_t>>();
    run_tests<std::array<uint64_t, 2>>();
    run_tests<std::array<int64_t, 2>>();
}

Live On Coliru

Printing

==== void run_tests() [with Tuple = std::tuple<long unsigned int, long unsigned int>] ====
Matching: true (-27670116110564327424 == -27670116110564327424)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (36893488147419103222 == 36893488147419103222)
Matching: true (27670116110564327424 == 27670116110564327424)

==== void run_tests() [with Tuple = std::tuple<long unsigned int, long int>] ====
Matching: true (-27670116110564327424 == -27670116110564327424)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (36893488147419103222 == 36893488147419103222)
Matching: true (27670116110564327424 == 27670116110564327424)

==== void run_tests() [with Tuple = std::tuple<long int, long unsigned int>] ====
Matching: true (-27670116110564327424 == -27670116110564327424)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (36893488147419103222 == 36893488147419103222)
Matching: true (27670116110564327424 == 27670116110564327424)

==== void run_tests() [with Tuple = std::tuple<long int, long int>] ====
Matching: true (-27670116110564327424 == -27670116110564327424)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (36893488147419103222 == 36893488147419103222)
Matching: true (27670116110564327424 == 27670116110564327424)

==== void run_tests() [with Tuple = std::array<long unsigned int, 2>] ====
Matching: true (-27670116110564327424 == -27670116110564327424)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (36893488147419103222 == 36893488147419103222)
Matching: true (27670116110564327424 == 27670116110564327424)

==== void run_tests() [with Tuple = std::array<long int, 2>] ====
Matching: true (-27670116110564327424 == -27670116110564327424)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (36893488147419103222 == 36893488147419103222)
Matching: true (27670116110564327424 == 27670116110564327424)

BONUS

Beefed up with systematic edge-cases construction and roundtrip verification in all possible combination, as well as a million uniformly random HiLo samples for each run:

Live On Coliru

#include <boost/multiprecision/cpp_int.hpp>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
#include <fmt/std.h>
#include <random>

static constexpr size_t NUM_RANDOM_SAMPLES = 1'000'000;

template <> struct fmt::formatter<boost::multiprecision::int128_t> : fmt::ostream_formatter {};
#define ABORT_ASSERT(cond)                                                                                   \
    do {                                                                                                     \
        if (!(cond)) {                                                                                       \
            fmt::print("Assertion failed: {} in {}:{}\n", #cond, __FILE__, __LINE__);                        \
            ::exit(1);                                                                                       \
        }                                                                                                    \
    } while (false)

template <typename Tuple> Tuple make(auto hi, auto lo) {
    return Tuple{
        std::tuple_element_t<0, Tuple>(hi),
        std::tuple_element_t<1, Tuple>(lo),
    };
};

template <typename T, typename Tuple> struct Impl { // Helpers grouping template parameters
    static constexpr std::string_view Name() { return __PRETTY_FUNCTION__; }

    static auto pack(Tuple const& v) {
        static_assert(sizeof(v) == 2 * sizeof(int64_t));
        auto& [hi, lo] = v;
        return (T(static_cast<int64_t>(hi)) << 64) + static_cast<uint64_t>(lo);
    };
    static Tuple unpack(T const& v) {
        static constexpr T scale64 = T(1) << 64;
        return make<Tuple>(                          //
            static_cast<int64_t>(v >> 64),           //
            static_cast<uint64_t>(v & (scale64 - 1)) //
        );
    };
    static T run_single_test(Tuple const& hiLo) {
        auto const extended  = pack(hiLo);
        auto const roundtrip = unpack(extended);

        if (hiLo != roundtrip)
            fmt::print("\n\n ****Failed*** to unpack {} - to_HiLo (to_ExtendedInteger ({})) = {}\n\n", //
                       Name(), hiLo, roundtrip);
        else
            ABORT_ASSERT(hiLo == roundtrip);

        ABORT_ASSERT(pack(roundtrip) == extended);
        ABORT_ASSERT(unpack(pack(roundtrip)) == roundtrip); // etc... just driving the point home

        return extended;
    }
};

template <typename Tuple> void run_batch_tests(bool verbose, std::vector<Tuple> const& testCases) {
    using GCC   = Impl<__int128_t, Tuple>;
    using Boost = Impl<boost::multiprecision::int128_t, Tuple>;

    for (auto hiLo : testCases) {
        // fmt::print("hiLo      = {}\n", hiLo);
        auto a     = GCC::run_single_test(hiLo);
        auto b     = Boost::run_single_test(hiLo);
        auto match = (a == b);
        if (verbose)
            fmt::print("Matching: {} ({} == {})\n", match, a, b);
        ABORT_ASSERT(match);
    }
}

template <typename Tuple> auto generateRandomSamples(size_t seed, unsigned howMany) {
    auto gen = bind(std::uniform_int_distribution<uint64_t>{}, std::mt19937_64(seed));

    std::vector<Tuple> result;
    generate_n(back_inserter(result), howMany, [&] { return make<Tuple>(gen(), gen()); });
    return result;
}

template <typename Tuple> void driver(size_t seed) {
    enum : bool { quiet, verbose };
    run_batch_tests<Tuple>(quiet, generateRandomSamples<Tuple>(seed, NUM_RANDOM_SAMPLES));

    fmt::print("\n==== {} ====\n", __PRETTY_FUNCTION__);
    fmt::print("{:L} uniform random samples passed (seed: {})\n", NUM_RANDOM_SAMPLES, seed);

    using Hi    = std::tuple_element_t<0, Tuple>;
    using Lo    = std::tuple_element_t<1, Tuple>;
    using HiLim = std::numeric_limits<Hi>;
    using LoLim = std::numeric_limits<Lo>;

    std::vector<Tuple> edgeCases;
    for (Hi hi : {Hi{}, Hi{+1}, static_cast<Hi>(-1), //
                  HiLim::min(), HiLim::max(),        //
                  Hi(LoLim::min()), Hi(LoLim::max())})
        for (Lo lo : {Lo{}, Lo{+1}, static_cast<Lo>(-1), //
                      LoLim::min(), LoLim::max(),        //
                      Lo(HiLim::min()), Lo(HiLim::max())})
            edgeCases.push_back(Tuple{hi, lo});

    std::ranges::sort(edgeCases);
    edgeCases.erase(std::unique(edgeCases.begin(), edgeCases.end()), edgeCases.end());

    run_batch_tests<Tuple>(verbose, edgeCases);
}

int main() {
    size_t const seed = std::random_device{}();
    fmt::print("Seed: {}\n", seed);
    driver<std::tuple<uint64_t, uint64_t>>(seed);
    driver<std::tuple<uint64_t, int64_t>>(seed);
    driver<std::tuple<int64_t, uint64_t>>(seed);
    driver<std::tuple<int64_t, int64_t>>(seed);
    driver<std::array<uint64_t, 2>>(seed);
    driver<std::array<int64_t, 2>>(seed);
}

Printing


Seed: 675096274

==== void driver(size_t) [with Tuple = std::tuple<long unsigned int, long unsigned int>; size_t = long unsigned int] ====
1000000 uniform random samples passed (seed: 675096274)
Matching: true (0 == 0)
Matching: true (1 == 1)
Matching: true (18446744073709551615 == 18446744073709551615)
Matching: true (18446744073709551616 == 18446744073709551616)
Matching: true (18446744073709551617 == 18446744073709551617)
Matching: true (36893488147419103231 == 36893488147419103231)
Matching: true (-18446744073709551616 == -18446744073709551616)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (-1 == -1)

==== void driver(size_t) [with Tuple = std::tuple<long unsigned int, long int>; size_t = long unsigned int] ====
1000000 uniform random samples passed (seed: 675096274)
Matching: true (9223372036854775808 == 9223372036854775808)
Matching: true (18446744073709551615 == 18446744073709551615)
Matching: true (0 == 0)
Matching: true (1 == 1)
Matching: true (9223372036854775807 == 9223372036854775807)
Matching: true (27670116110564327424 == 27670116110564327424)
Matching: true (36893488147419103231 == 36893488147419103231)
Matching: true (18446744073709551616 == 18446744073709551616)
Matching: true (18446744073709551617 == 18446744073709551617)
Matching: true (27670116110564327423 == 27670116110564327423)
Matching: true (170141183460469231722463931679029329920 == 170141183460469231722463931679029329920)
Matching: true (170141183460469231731687303715884105727 == 170141183460469231731687303715884105727)
Matching: true (170141183460469231713240559642174554112 == 170141183460469231713240559642174554112)
Matching: true (170141183460469231713240559642174554113 == 170141183460469231713240559642174554113)
Matching: true (170141183460469231722463931679029329919 == 170141183460469231722463931679029329919)
Matching: true (-170141183460469231722463931679029329920 == -170141183460469231722463931679029329920)
Matching: true (-170141183460469231713240559642174554113 == -170141183460469231713240559642174554113)
Matching: true (-170141183460469231731687303715884105728 == -170141183460469231731687303715884105728)
Matching: true (-170141183460469231731687303715884105727 == -170141183460469231731687303715884105727)
Matching: true (-170141183460469231722463931679029329921 == -170141183460469231722463931679029329921)
Matching: true (-9223372036854775808 == -9223372036854775808)
Matching: true (-1 == -1)
Matching: true (-18446744073709551616 == -18446744073709551616)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (-9223372036854775809 == -9223372036854775809)

==== void driver(size_t) [with Tuple = std::tuple<long int, long unsigned int>; size_t = long unsigned int] ====
1000000 uniform random samples passed (seed: 675096274)
Matching: true (-170141183460469231731687303715884105728 == -170141183460469231731687303715884105728)
Matching: true (-170141183460469231731687303715884105727 == -170141183460469231731687303715884105727)
Matching: true (-170141183460469231722463931679029329921 == -170141183460469231722463931679029329921)
Matching: true (-170141183460469231722463931679029329920 == -170141183460469231722463931679029329920)
Matching: true (-170141183460469231713240559642174554113 == -170141183460469231713240559642174554113)
Matching: true (-18446744073709551616 == -18446744073709551616)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (-9223372036854775809 == -9223372036854775809)
Matching: true (-9223372036854775808 == -9223372036854775808)
Matching: true (-1 == -1)
Matching: true (0 == 0)
Matching: true (1 == 1)
Matching: true (9223372036854775807 == 9223372036854775807)
Matching: true (9223372036854775808 == 9223372036854775808)
Matching: true (18446744073709551615 == 18446744073709551615)
Matching: true (18446744073709551616 == 18446744073709551616)
Matching: true (18446744073709551617 == 18446744073709551617)
Matching: true (27670116110564327423 == 27670116110564327423)
Matching: true (27670116110564327424 == 27670116110564327424)
Matching: true (36893488147419103231 == 36893488147419103231)
Matching: true (170141183460469231713240559642174554112 == 170141183460469231713240559642174554112)
Matching: true (170141183460469231713240559642174554113 == 170141183460469231713240559642174554113)
Matching: true (170141183460469231722463931679029329919 == 170141183460469231722463931679029329919)
Matching: true (170141183460469231722463931679029329920 == 170141183460469231722463931679029329920)
Matching: true (170141183460469231731687303715884105727 == 170141183460469231731687303715884105727)

==== void driver(size_t) [with Tuple = std::tuple<long int, long int>; size_t = long unsigned int] ====
1000000 uniform random samples passed (seed: 675096274)
Matching: true (-170141183460469231722463931679029329920 == -170141183460469231722463931679029329920)
Matching: true (-170141183460469231713240559642174554113 == -170141183460469231713240559642174554113)
Matching: true (-170141183460469231731687303715884105728 == -170141183460469231731687303715884105728)
Matching: true (-170141183460469231731687303715884105727 == -170141183460469231731687303715884105727)
Matching: true (-170141183460469231722463931679029329921 == -170141183460469231722463931679029329921)
Matching: true (-9223372036854775808 == -9223372036854775808)
Matching: true (-1 == -1)
Matching: true (-18446744073709551616 == -18446744073709551616)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (-9223372036854775809 == -9223372036854775809)
Matching: true (9223372036854775808 == 9223372036854775808)
Matching: true (18446744073709551615 == 18446744073709551615)
Matching: true (0 == 0)
Matching: true (1 == 1)
Matching: true (9223372036854775807 == 9223372036854775807)
Matching: true (27670116110564327424 == 27670116110564327424)
Matching: true (36893488147419103231 == 36893488147419103231)
Matching: true (18446744073709551616 == 18446744073709551616)
Matching: true (18446744073709551617 == 18446744073709551617)
Matching: true (27670116110564327423 == 27670116110564327423)
Matching: true (170141183460469231722463931679029329920 == 170141183460469231722463931679029329920)
Matching: true (170141183460469231731687303715884105727 == 170141183460469231731687303715884105727)
Matching: true (170141183460469231713240559642174554112 == 170141183460469231713240559642174554112)
Matching: true (170141183460469231713240559642174554113 == 170141183460469231713240559642174554113)
Matching: true (170141183460469231722463931679029329919 == 170141183460469231722463931679029329919)

==== void driver(size_t) [with Tuple = std::array<long unsigned int, 2>; size_t = long unsigned int] ====
1000000 uniform random samples passed (seed: 675096274)
Matching: true (0 == 0)
Matching: true (1 == 1)
Matching: true (18446744073709551615 == 18446744073709551615)
Matching: true (18446744073709551616 == 18446744073709551616)
Matching: true (18446744073709551617 == 18446744073709551617)
Matching: true (36893488147419103231 == 36893488147419103231)
Matching: true (-18446744073709551616 == -18446744073709551616)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (-1 == -1)

==== void driver(size_t) [with Tuple = std::array<long int, 2>; size_t = long unsigned int] ====
1000000 uniform random samples passed (seed: 675096274)
Matching: true (-170141183460469231722463931679029329920 == -170141183460469231722463931679029329920)
Matching: true (-170141183460469231713240559642174554113 == -170141183460469231713240559642174554113)
Matching: true (-170141183460469231731687303715884105728 == -170141183460469231731687303715884105728)
Matching: true (-170141183460469231731687303715884105727 == -170141183460469231731687303715884105727)
Matching: true (-170141183460469231722463931679029329921 == -170141183460469231722463931679029329921)
Matching: true (-9223372036854775808 == -9223372036854775808)
Matching: true (-1 == -1)
Matching: true (-18446744073709551616 == -18446744073709551616)
Matching: true (-18446744073709551615 == -18446744073709551615)
Matching: true (-9223372036854775809 == -9223372036854775809)
Matching: true (9223372036854775808 == 9223372036854775808)
Matching: true (18446744073709551615 == 18446744073709551615)
Matching: true (0 == 0)
Matching: true (1 == 1)
Matching: true (9223372036854775807 == 9223372036854775807)
Matching: true (27670116110564327424 == 27670116110564327424)
Matching: true (36893488147419103231 == 36893488147419103231)
Matching: true (18446744073709551616 == 18446744073709551616)
Matching: true (18446744073709551617 == 18446744073709551617)
Matching: true (27670116110564327423 == 27670116110564327423)
Matching: true (170141183460469231722463931679029329920 == 170141183460469231722463931679029329920)
Matching: true (170141183460469231731687303715884105727 == 170141183460469231731687303715884105727)
Matching: true (170141183460469231713240559642174554112 == 170141183460469231713240559642174554112)
Matching: true (170141183460469231713240559642174554113 == 170141183460469231713240559642174554113)
Matching: true (170141183460469231722463931679029329919 == 170141183460469231722463931679029329919)

I found an implementation of the function to unpack a boost::multiprecision::int128_t into a std::array<int64_t, 2>, but I'm not confident about its robustness across all possible cases.

After studying the Boost multi-precision code and experimenting, I came up with:

inline std::array<int64_t, 2> unpack_int128_t (const boost::multiprecision::int128_t& value) 
{
    constexpr auto TwoPowerNBits = boost::multiprecision::int128_t (1) << 64;
    const auto lo = (value % TwoPowerNBits);
    const auto hi = (value - static_cast<int64_t> (lo)) / TwoPowerNBits;
    
    if (lo != (static_cast<int64_t> (lo)))
    {
        return ((lo > 0) ?
            std::array { static_cast<int64_t>(hi + 1), static_cast<int64_t> (lo - TwoPowerNBits) } :
            std::array { static_cast<int64_t>(hi - 1), static_cast<int64_t> (lo + TwoPowerNBits) });
    }

    return std::array { static_cast<int64_t>(hi), static_cast<int64_t> (lo) };
}

Concerns and Suggestions

While this function works for my test cases (available on Coliru), I'm not confident in its generality. It would be beneficial if the Boost library provided a function to split a multi-precision object into n integers satisfying:

Here is the rendered mathematical expression:

This function could work similarly to convert_to<...>, returning a vector with the minimal set of integers. The function could be named unpack_to_vector that would take, for example, a boost::multiprecision::int128_t as input, returning a std::vector<int64_t>.

std::vector<int64_t> unpack_to_vector(const boost::multiprecision::int128_t& value);

In general, the function would depend on the size of the multi-precision integer. This would provide a more robust and general solution for unpacking multi-precision integers given that boost's implementation does not adhere to the most common two's complement implementation used by our compilers.

JZMaddock's reply to this issue

I came up with:

std::array<int64_t, 2> unpack_int128_t(const boost::multiprecision::int128_t& value)
{
   constexpr auto TwoPowerNBits = boost::multiprecision::int128_t(1) << 64;

   boost::multiprecision::int128_t high, low;

   boost::multiprecision::divide_qr(value, TwoPowerNBits, high, low);

   if (boost::multiprecision::bit_test(value, 63))
   {
      low -= TwoPowerNBits;
      ++high;
   }

   std::array<int64_t, 2> result = { static_cast<int64_t>(high), static_cast<int64_t>(low) };

   assert(boost::multiprecision::int128_t(result[0]) * TwoPowerNBits + result[1] == value);

   return result;
}

Which I hope is a little easier to see what's going on.

It would be nice to avoid divide_qr which is more efficient than separate / and %, but still horribly inefficient compared to bit-fiddling, but I don't see a good bit-fiddling solution at present!

本文标签: How can I split a boostmultiprecisionint128t into a hi and lo int64t portionsStack Overflow