diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f2168c0..2a945c9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -298,9 +298,6 @@ jobs: source_keys: - "https://apt.llvm.org/llvm-snapshot.gpg.key" - - toolset: clang - cxxstd: "03,11,14,17,20,2b" - os: macos-13 - toolset: clang cxxstd: "03,11,14,17,20,2b" os: macos-14 @@ -623,7 +620,6 @@ jobs: include: - os: ubuntu-22.04 - os: ubuntu-24.04 - - os: macos-13 - os: macos-14 - os: macos-15 @@ -670,7 +666,6 @@ jobs: fail-fast: false matrix: include: - - os: macos-13 - os: macos-14 - os: macos-15 @@ -729,7 +724,6 @@ jobs: include: - os: ubuntu-22.04 - os: ubuntu-24.04 - - os: macos-13 - os: macos-14 - os: macos-15 @@ -786,7 +780,6 @@ jobs: include: - os: ubuntu-22.04 - os: ubuntu-24.04 - - os: macos-13 - os: macos-14 - os: macos-15 @@ -959,7 +952,6 @@ jobs: matrix: include: - os: ubuntu-24.04 - - os: macos-13 - os: macos-14 - os: macos-15 diff --git a/doc/int128.adoc b/doc/int128.adoc index 30806c28..b8128044 100644 --- a/doc/int128.adoc +++ b/doc/int128.adoc @@ -20,6 +20,8 @@ Matt Borland include::int128/overview.adoc[] +include::int128/printer.adoc[] + include::int128/api_reference.adoc[] include::int128/file_structure.adoc[] diff --git a/doc/int128/printer.adoc b/doc/int128/printer.adoc new file mode 100644 index 00000000..9b8df8e1 --- /dev/null +++ b/doc/int128/printer.adoc @@ -0,0 +1,21 @@ +//// +Copyright 2025 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#pretty_printer] += Pretty Printers +:idprefix: pretty_printers_ + +The library contains a pretty printer for LLDB in the `extra/` folder. +To use this, add the following line to your `~/.lldbinit` file: + +[source] +---- +command script import /path/to/int128/extra/int128_printer.py +---- + +If this is successful, you should see the following message in your debugger upon startup: + +"int128_t and uint128_t pretty printers loaded successfully" diff --git a/extra/int128_printer_gdb.py b/extra/int128_printer_gdb.py new file mode 100644 index 00000000..360990a9 --- /dev/null +++ b/extra/int128_printer_gdb.py @@ -0,0 +1,116 @@ +# Copyright 2025 Matt Borland +# Distributed under the Boost Software License, Version 1.0. +# https://www.boost.org/LICENSE_1_0.txt +# +# Struct definitions: +# struct uint128_t { std::uint64_t low; std::uint64_t high; }; +# struct int128_t { std::uint64_t low; std::int64_t high; }; +# +# On big endian machines the word order is reversed +# +# Usage: source int128_printer.py + +import gdb +import gdb.printing +import re + +class Uint128Printer: + """Pretty printer for uint128_t type""" + + def __init__(self, val): + self.val = val + + def to_string(self): + try: + high = int(self.val["high"]) & 0xFFFFFFFFFFFFFFFF # Treat as unsigned + low = int(self.val["low"]) & 0xFFFFFFFFFFFFFFFF + + value = (high << 64) | low + return f"{value:,}" + except Exception as e: + return f"" + + def children(self): + yield "low", self.val["low"] + yield "high", self.val["high"] + + def display_hint(self): + return None + + +class Int128Printer: + """Pretty printer for int128_t type""" + + def __init__(self, val): + self.val = val + + def to_string(self): + try: + # high is std::int64_t (signed) + high = int(self.val["high"]) + # Ensure high is treated as signed 64-bit + if high >= 0x8000000000000000: + high -= 0x10000000000000000 + + # low is std::uint64_t (unsigned) + low = int(self.val["low"]) & 0xFFFFFFFFFFFFFFFF + + value = (high << 64) + low + return f"{value:,}" + except Exception as e: + return f"" + + def children(self): + yield "low", self.val["low"] + yield "high", self.val["high"] + + def display_hint(self): + return None + + +def lookup_int128_type(val): + """ + Lookup function to detect if a type should use our pretty printers. + Returns the appropriate printer or None. + """ + # Get the basic type name, stripping references and const qualifiers + type_obj = val.type + + # Handle references and pointers + if type_obj.code == gdb.TYPE_CODE_REF: + type_obj = type_obj.target() + if type_obj.code == gdb.TYPE_CODE_PTR: + return None # Don't handle pointers directly + + # Strip const/volatile qualifiers + type_obj = type_obj.unqualified() + + type_name = str(type_obj) + + # Patterns to match uint128_t and int128_t types + uint128_pattern = re.compile( + r"^(boost::int128::uint128_t|(\w+::)*uint128_t|uint128_t)$" + ) + int128_pattern = re.compile( + r"^(boost::int128::int128_t|(\w+::)*int128_t|int128_t)$" + ) + + if uint128_pattern.match(type_name): + return Uint128Printer(val) + if int128_pattern.match(type_name): + return Int128Printer(val) + + return None + + +def register_int128_printers(objfile=None): + """Register the int128 pretty printers.""" + if objfile is None: + objfile = gdb + + objfile.pretty_printers.append(lookup_int128_type) + + +# Auto-register when the script is sourced +register_int128_printers() +print("int128_t and uint128_t pretty printers loaded successfully") diff --git a/extra/int128_printer_lldb.py b/extra/int128_printer_lldb.py new file mode 100644 index 00000000..40954e84 --- /dev/null +++ b/extra/int128_printer_lldb.py @@ -0,0 +1,117 @@ +# Copyright 2025 Matt Borland +# Distributed under the Boost Software License, Version 1.0. +# https://www.boost.org/LICENSE_1_0.txt +# +# Struct definitions: +# struct uint128_t { std::uint64_t low; std::uint64_t high; }; +# struct int128_t { std::uint64_t low; std::int64_t high; }; +# +# On big endian machines the word order is reversed + +import lldb + +def uint128_summary(valobj, internal_dict): + """ + Custom summary for uint128_t type (unsigned). + Displays as decimal (base 10). + """ + try: + val = valobj.GetNonSyntheticValue() + high = val.GetChildMemberWithName("high").GetValueAsUnsigned() + low = val.GetChildMemberWithName("low").GetValueAsUnsigned() + + value = (high << 64) | low + return f"{value:,}" + except Exception as e: + return f"" + +def int128_summary(valobj, internal_dict): + """ + Custom summary for int128_t type (signed). + Displays as decimal (base 10). + """ + try: + val = valobj.GetNonSyntheticValue() + # high is std::int64_t, so use GetValueAsSigned() + high = val.GetChildMemberWithName("high").GetValueAsSigned() + # low is std::uint64_t, so use GetValueAsUnsigned() + low = val.GetChildMemberWithName("low").GetValueAsUnsigned() + + value = (high << 64) + low + return f"{value:,}" + except Exception as e: + return f"" + +def __lldb_init_module(debugger, internal_dict): + uint128_pattern = r"^(const )?(boost::int128::uint128_t|(\w+::)*uint128_t)( &| \*)?$" + int128_pattern = r"^(const )?(boost::int128::int128_t|(\w+::)*int128_t)( &| \*)?$" + + debugger.HandleCommand( + f'type summary add -x "{uint128_pattern}" -e -F int128_printer_lldb.uint128_summary' + ) + debugger.HandleCommand( + f'type synthetic add -x "{uint128_pattern}" -l int128_printer_lldb.Uint128SyntheticProvider' + ) + + debugger.HandleCommand( + f'type summary add -x "{int128_pattern}" -e -F int128_printer_lldb.int128_summary' + ) + debugger.HandleCommand( + f'type synthetic add -x "{int128_pattern}" -l int128_printer_lldb.Int128SyntheticProvider' + ) + + print("int128_t and uint128_t pretty printers loaded successfully") + +class Uint128SyntheticProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + + def num_children(self): + return 2 + + def get_child_index(self, name): + if name == "low": + return 0 + elif name == "high": + return 1 + return -1 + + def get_child_at_index(self, index): + if index == 0: + return self.valobj.GetChildMemberWithName("low") + elif index == 1: + return self.valobj.GetChildMemberWithName("high") + return None + + def update(self): + pass + + def has_children(self): + return True + +class Int128SyntheticProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + + def num_children(self): + return 2 + + def get_child_index(self, name): + if name == "low": + return 0 + elif name == "high": + return 1 + return -1 + + def get_child_at_index(self, index): + if index == 0: + return self.valobj.GetChildMemberWithName("low") + elif index == 1: + return self.valobj.GetChildMemberWithName("high") + return None + + def update(self): + pass + + def has_children(self): + return True diff --git a/include/boost/int128/detail/uint128_imp.hpp b/include/boost/int128/detail/uint128_imp.hpp index a0df7831..6a555819 100644 --- a/include/boost/int128/detail/uint128_imp.hpp +++ b/include/boost/int128/detail/uint128_imp.hpp @@ -677,8 +677,10 @@ BOOST_INT128_EXPORT constexpr bool operator<(const uint128_t lhs, const uint128_ } else { - const uint32_t* l = reinterpret_cast(&lhs); - const uint32_t* r = reinterpret_cast(&rhs); + std::uint32_t l[4] {}; + std::uint32_t r[4] {}; + std::memcpy(l, &lhs, sizeof(lhs)); + std::memcpy(r, &rhs, sizeof(rhs)); if (l[3] != r[3]) { @@ -830,8 +832,10 @@ BOOST_INT128_EXPORT constexpr bool operator<=(const uint128_t lhs, const uint128 } else { - const uint32_t* l = reinterpret_cast(&lhs); - const uint32_t* r = reinterpret_cast(&rhs); + std::uint32_t l[4] {}; + std::uint32_t r[4] {}; + std::memcpy(l, &lhs, sizeof(lhs)); + std::memcpy(r, &rhs, sizeof(rhs)); if (l[3] != r[3]) { @@ -983,8 +987,10 @@ BOOST_INT128_EXPORT constexpr bool operator>(const uint128_t lhs, const uint128_ } else { - const uint32_t* l = reinterpret_cast(&lhs); - const uint32_t* r = reinterpret_cast(&rhs); + std::uint32_t l[4] {}; + std::uint32_t r[4] {}; + std::memcpy(l, &lhs, sizeof(lhs)); + std::memcpy(r, &rhs, sizeof(rhs)); if (l[3] != r[3]) { @@ -1136,8 +1142,10 @@ BOOST_INT128_EXPORT constexpr bool operator>=(const uint128_t lhs, const uint128 } else { - const uint32_t* l = reinterpret_cast(&lhs); - const uint32_t* r = reinterpret_cast(&rhs); + std::uint32_t l[4] {}; + std::uint32_t r[4] {}; + std::memcpy(l, &lhs, sizeof(lhs)); + std::memcpy(r, &rhs, sizeof(rhs)); if (l[3] != r[3]) { diff --git a/test/Jamfile b/test/Jamfile index 307a099d..015a42e3 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -96,6 +96,7 @@ run ../examples/charconv.cpp ; run limits_link_1.cpp limits_link_2.cpp limits_link_3.cpp ; # Github Issues +run decimal_github_issue_1260.cpp ; run github_issue_207.cpp ; run github_issue_210.cpp ; run github_issue_221.cpp ; diff --git a/test/decimal_github_issue_1260.cpp b/test/decimal_github_issue_1260.cpp new file mode 100644 index 00000000..43825836 --- /dev/null +++ b/test/decimal_github_issue_1260.cpp @@ -0,0 +1,44 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt +// +// This only failed with GCC 12+ on 32-bit platforms in release mode +// Debug configuration was fine, as was all other platforms + +#include +#include + +using namespace boost::int128; + +int main() +{ + uint128_t lhs_sig {UINT64_C(54210108624275), UINT64_C(4089650035136921600)}; + uint128_t rhs_sig {UINT64_C(276471553983803), UINT64_C(11633843142343524352)}; + + lhs_sig *= 1000U; + BOOST_TEST_EQ(lhs_sig, uint128_t(UINT64_C(54210108624275221), UINT64_C(12919594847110692864))); + + // 10^31 + rhs_sig /= BOOST_INT128_UINT128_C(10000000000000000000000000000000); + BOOST_TEST_EQ(rhs_sig, 510U); + + rhs_sig /= 10U; + BOOST_TEST_EQ(rhs_sig, 51U); + + auto signed_lhs {static_cast(lhs_sig)}; + auto signed_rhs {static_cast(rhs_sig)}; + signed_rhs = -signed_rhs; + + BOOST_TEST_EQ(signed_rhs, int128_t(INT64_C(-1), UINT64_C(18446744073709551565))); + + auto res_sig {static_cast(signed_lhs + signed_rhs)}; + BOOST_TEST_EQ(res_sig, uint128_t(UINT64_C(54210108624275221), UINT64_C(12919594847110692813))); + + res_sig /= UINT64_C(10); + BOOST_TEST_EQ(res_sig, uint128_t(UINT64_C(5421010862427522), UINT64_C(3136633892082024442))); + + res_sig /= UINT64_C(10); + BOOST_TEST_EQ(res_sig, uint128_t(UINT64_C(542101086242752), UINT64_C(4003012203950112767))); + + return boost::report_errors(); +}