Search code examples
c++boosthexboost-multiprecision

Convert two's complement hex number to boost::multiprecision::int256_t


I'm trying to get boost::multiprecision to parse a negative (two's complement) hex number:

#include <iostream>
#include <boost/multiprecision/cpp_int.hpp>

int main(int argc, char* argv[]) {
  std::string hex_str = "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2e";
  boost::multiprecision::int256_t signed_int(hex_str);

  std::cout << signed_int << std::endl;

}

I expect this to output -1234. Unfortunately, it outputs 115792089237316195423570985008687907853269984665640564039457584007913129638702

Parsing an unsigned hex number works fine.

How can I get int256_t to interpret a hex number as a negative two's complement?


Solution

  • The hex conversion constructor simply doesn't parse a sign. Likewise, std::cout will refuse to print hex numbers if they're negative, and indeed .str(..., std::ios::hex) will throw an runtime_error exception ("Base 8 or 16 printing of negative numbers is not supported").

    So you have to make it work manually, like

    UInt uval(str);
    Int  val;
    
    if (/*signbit =*/uval >> 255) {
        // manual two's complement
        val = ~uval + 1;
        val.backend().sign(true);
    } else {
        val = uval.convert_to<boost::multiprecision::int256_t>();
    }
    

    Here's a test program verifying all the edge cases:

    Live On Coliru

    #include <boost/multiprecision/cpp_int.hpp>
    #include <iomanip>
    #include <iostream>
    
    using namespace std::string_literals;
    using UInt = boost::multiprecision::uint256_t;
    using Int  = boost::multiprecision::int256_t;
    
    int main()
    {
        static const auto min = -Int(UInt(1) << 255);
        static const auto max = Int(UInt(1) << 255 - 1);
        static const auto dec = std::ios::dec;
        static const auto hex = std::ios::hex | std::ios::showbase;
    
        struct {
            std::string_view caption;
            Int              expected;
            std::string      str;
        } testcases[] = {
            // clang-format off
            {"zero", 0, Int{0}.str(0, dec),},
            {"zero", 0, Int{0}.str(0, hex),},
            //
            {"one", 1, Int{1}.str(0, dec),},
            {"one", 1, Int{1}.str(0, hex),},
            //
            {"negone", -1, Int{-1}.str(0, dec),},
            {"negone", -1, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"s,},
            //
            {"min", min, (min).str(0, dec),},
            {"min", min, "0x8000000000000000000000000000000000000000000000000000000000000000"s,},
            //
            {"min+1", min+1, (min + 1).str(0, dec),},
            {"min+1", min+1, "0x8000000000000000000000000000000000000000000000000000000000000001"s,},
            //
            {"max-1", max-1, (max - 1).str(0, dec),},
            {"max-1", max-1, (max - 1).str(0, hex),},
            //
            {"max", max, (max).str(0, dec),},
            {"max", max, (max).str(0, hex),},
            // question case
            {"question", -1234, "-1234"s,},
            {"question", -1234, "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2e"s,},
            // clang-format on
        };
    
        for (auto [caption, expected, str] : testcases) {
            std::cout << " ---- " << caption << ":\t\t" << str << "\n";
    
            UInt uval(str);
            Int  val;
    
            if (/*signbit =*/uval >> 255) {
                // manual two's complement
                val = ~uval + 1;
                val.backend().sign(true);
            } else {
                val = uval.convert_to<boost::multiprecision::int256_t>();
            }
    
            std::cout << "Expected? " << std::boolalpha
                      << (expected == val ? "SUCCESS" : "FAILED") << "\t";
    
            std::cout << std::dec << val;
            if (val.sign() >= 0) // negative no hex
                std::cout << " (" << std::hex << std::showbase << val << ")";
            std::cout << "\n";
        }
    }
    

    Prints

     ---- zero:     0
    Expected? SUCCESS   0 (0x0)
     ---- zero:     0x0
    Expected? SUCCESS   0 (0x0)
     ---- one:      1
    Expected? SUCCESS   1 (0x1)
     ---- one:      0x1
    Expected? SUCCESS   1 (0x1)
     ---- negone:       -1
    Expected? SUCCESS   -1
     ---- negone:       0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    Expected? SUCCESS   -1
     ---- min:      -57896044618658097711785492504343953926634992332820282019728792003956564819968
    Expected? SUCCESS   -57896044618658097711785492504343953926634992332820282019728792003956564819968
     ---- min:      0x8000000000000000000000000000000000000000000000000000000000000000
    Expected? SUCCESS   -57896044618658097711785492504343953926634992332820282019728792003956564819968
     ---- min+1:        -57896044618658097711785492504343953926634992332820282019728792003956564819967
    Expected? SUCCESS   -57896044618658097711785492504343953926634992332820282019728792003956564819967
     ---- min+1:        0x8000000000000000000000000000000000000000000000000000000000000001
    Expected? SUCCESS   -57896044618658097711785492504343953926634992332820282019728792003956564819967
     ---- max-1:        28948022309329048855892746252171976963317496166410141009864396001978282409983
    Expected? SUCCESS   28948022309329048855892746252171976963317496166410141009864396001978282409983 (0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
     ---- max-1:        0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    Expected? SUCCESS   28948022309329048855892746252171976963317496166410141009864396001978282409983 (0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
     ---- max:      28948022309329048855892746252171976963317496166410141009864396001978282409984
    Expected? SUCCESS   28948022309329048855892746252171976963317496166410141009864396001978282409984 (0x4000000000000000000000000000000000000000000000000000000000000000)
     ---- max:      0x4000000000000000000000000000000000000000000000000000000000000000
    Expected? SUCCESS   28948022309329048855892746252171976963317496166410141009864396001978282409984 (0x4000000000000000000000000000000000000000000000000000000000000000)
     ---- question:     -1234
    Expected? SUCCESS   -1234
     ---- question:     0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb2e
    Expected? SUCCESS   -1234
    

    PS

    As a side-note, apparently numeric limits cannot be trusted, so I sidestepped them above.

    assert(std::numeric_limits<UInt>::max() != std::numeric_limits<Int>::max());
    assert(std::numeric_limits<UInt>::min() != std::numeric_limits<Int>::min());