diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3418f60e..b0889369 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -527,6 +527,14 @@ jobs: cxxstd: "14,17,20,latest" addrmd: "64" os: windows-latest + - toolset: msvc-14.5 + cxxstd: "14,17,20,latest" + addrmd: "32" + os: windows-latest + - toolset: msvc-14.5 + cxxstd: "14,17,20,latest" + addrmd: "64" + os: windows-latest - toolset: clang-win cxxstd: "14,17,latest" addrmd: "32" diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 27492ba9..5934b755 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -1,4 +1,6 @@ * xref:overview.adoc[] +* xref:getting_started.adoc[] +* xref:printer.adoc[] * xref:examples.adoc[] ** xref:examples.adoc#examples_construction[Basic Construction] ** xref:examples.adoc#examples_arithmetic[Basic Arithmetic] @@ -9,9 +11,10 @@ ** xref:examples.adoc#examples_numeric_algorithms[`` support (Numeric Algorithms)] ** xref:examples.adoc#examples_checked[Checked Arithmetic] ** xref:examples.adoc#examples_mixed_sign[Mixed Signedness Arithmetic] -** xref:examples.adoc#examples_to_string[String Conversion (to_string)] ** xref:examples.adoc#examples_boost_math_random[Boost Math and Random Integration] ** xref:examples.adoc#examples_boost_charconv[Boost.Charconv Integration] +** xref:examples.adoc#examples_to_string[String Conversion (to_string)] +** xref:examples.adoc#examples_fmt_format[pass:[{fmt}] Integration] ** xref:examples.adoc#examples_cstdlib[`` support (Combined div and mod)] ** xref:examples.adoc#examples_cuda[Use of the library in a CUDA kernel] * xref:api_reference.adoc[] @@ -21,10 +24,11 @@ ** xref:api_reference.adoc#api_structs[Structs] ** xref:api_reference.adoc#api_functions[Functions] *** xref:api_reference.adoc#api_bit[``] -*** xref:api_reference.adoc#api_cstdlib[``] *** xref:api_reference.adoc#api_charconv[``] *** xref:api_reference.adoc#api_cmath[``] +*** xref:api_reference.adoc#api_cstdlib[``] *** xref:api_reference.adoc#api_functional[``] +*** xref:api_reference.adoc#api_formatting[Formatting] *** xref:api_reference.adoc#api_iostream[``] *** xref:api_reference.adoc#api_ios[``] *** xref:api_reference.adoc#api_numeric[``] @@ -34,6 +38,7 @@ *** xref:api_reference.adoc#api_macro_literals[Literals] *** xref:api_reference.adoc#api_macro_configuration[Configuration] ** xref:api_reference.adoc#api_headers[Headers] +* xref:file_structure.adoc[] * xref:uint128_t.adoc[] ** xref:uint128_t.adoc#u128_alignment[Alignment] ** xref:uint128_t.adoc#u128_operator_behavior[Operator Behavior] @@ -59,11 +64,16 @@ * xref:hash.adoc[] * xref:numeric.adoc[] * xref:string.adoc[] +* xref:format.adoc[] +** xref:format.adoc#std_format[``] +** xref:format.adoc#fmt_format[pass:[{fmt}]] * xref:utilities.adoc[] ** xref:utilities.adoc#powm[Modular Exponentiation] ** xref:utilities.adoc#ipow[Integer Power] ** xref:utilities.adoc#isqrt[Integer Square Root] ** xref:utilities.adoc#checked[Checked Arithmetic] +* xref:random.adoc[] +* xref:config.adoc[] * Benchmarks ** xref:u128_benchmarks.adoc[] *** xref:u128_benchmarks.adoc#u128_linux[Linux] @@ -74,5 +84,6 @@ *** xref:i128_benchmarks.adoc#i128_windows[Windows] *** xref:i128_benchmarks.adoc#i128_mac[macOS] * xref:comp_to_multiprecision.adoc[] +* xref:design.adoc[] * xref:reference.adoc[] * xref:copyright.adoc[] diff --git a/doc/modules/ROOT/pages/api_reference.adoc b/doc/modules/ROOT/pages/api_reference.adoc index 4a55dc25..ea9752ee 100644 --- a/doc/modules/ROOT/pages/api_reference.adoc +++ b/doc/modules/ROOT/pages/api_reference.adoc @@ -23,6 +23,9 @@ https://www.boost.org/LICENSE_1_0.txt | xref:charconv.adoc[`boost::charconv`] | `to_chars` and `from_chars` overloads for 128-bit integers (requires Boost.Charconv) + +| `boost::random::traits` +| Trait specializations enabling use with Boost.Random (requires Boost.Random) |=== [#api_types] @@ -233,6 +236,12 @@ Listed by analogous STL header. | xref:stream.adoc#ios[`std::nouppercase`] | Lowercase hex digits + +| xref:stream.adoc#ios[`std::showbase`] +| Adds a leading base prefix on output + +| xref:stream.adoc#ios[`std::noshowbase`] +| Omits the leading base prefix on output |=== [#api_numeric] @@ -353,6 +362,9 @@ Listed by analogous STL header. | xref:config.adoc#enable_cuda[`BOOST_INT128_ENABLE_CUDA`] | Enables CUDA support allowing the library types and functions to be run on both host and device + +| xref:config.adoc#build_module[`BOOST_INT128_BUILD_MODULE`] +| Builds the library as a pass:[C++20] named module (`import boost.int128`) |=== ==== Automatic Configuration @@ -370,6 +382,18 @@ Listed by analogous STL header. | xref:config.adoc#automatic_config[`BOOST_INT128_ENDIAN_BIG_BYTE`] | Defined on big-endian systems +| xref:config.adoc#automatic_config[`BOOST_INT128_HAS_MSVC_INT128`] +| Defined when the MSVC simulated 128-bit integer types are available + +| xref:config.adoc#automatic_config[`BOOST_INT128_HAS_SPACESHIP_OPERATOR`] +| Defined when pass:[C++20] three-way comparison is available + +| xref:config.adoc#automatic_config[`BOOST_INT128_HAS_FORMAT`] +| Defined when pass:[C++20] `` is available + +| xref:config.adoc#automatic_config[`BOOST_INT128_HAS_FMT_FORMAT`] +| Defined when the pass:[{fmt}] library is available + | xref:config.adoc#host_device[`BOOST_INT128_HOST_DEVICE`] | Expands to `pass:[__host__ __device__]` under NVCC for CUDA support |=== diff --git a/doc/modules/ROOT/pages/config.adoc b/doc/modules/ROOT/pages/config.adoc index 8a5cdb39..bb36ed59 100644 --- a/doc/modules/ROOT/pages/config.adoc +++ b/doc/modules/ROOT/pages/config.adoc @@ -8,10 +8,7 @@ https://www.boost.org/LICENSE_1_0.txt = Configuration Macros :idprefix: config_ -[source,c++] ----- -#include ----- +The configuration macros below are defined automatically once any library header (for example ``) is included; there is no separate configuration header to include directly. == User Configurable Macros @@ -28,6 +25,11 @@ Allowed functions have `BOOST_INT128_HOST_DEVICE` as part of their function sign - `BOOST_INT128_DISABLE_EXCEPTIONS`: Allows exceptions to be disabled. This macro will automatically be defined in the presence of `-fno-exceptions` or similar MSVC flags. +[#build_module] +- `BOOST_INT128_BUILD_MODULE`: Define this when building the library as a pass:[C++20] named module (consumed via `import boost.int128`). +It switches the headers into module mode by exporting the public interface and suppressing the includes that the module unit itself provides. +See xref:getting_started.adoc[Getting Started]. + [#automatic_config] == Automatic Configuration Macros @@ -37,6 +39,14 @@ This macro will automatically be defined in the presence of `-fno-exceptions` or - `BOOST_INT128_ENDIAN_BIG_BYTE`: This is defined to `1` when compiling on a big endian architecture, otherwise `0`. +- `BOOST_INT128_HAS_MSVC_INT128`: This is defined when the MSVC standard library provides the simulated 128-bit integer types in `pass:[<__msvc_int128.hpp>]` (MSVC in pass:[C++20] mode). When defined, the library uses those types as its native 128-bit backing. + +- `BOOST_INT128_HAS_SPACESHIP_OPERATOR`: This is defined when the compiler and standard library provide pass:[C++20] three-way comparison (`operator<=>` and ``). When defined, both types provide `operator<=>`. + +- `BOOST_INT128_HAS_FORMAT`: This is defined by `` when pass:[C++20] `` is available. When defined, that header provides `std::formatter` specializations for both types. + +- `BOOST_INT128_HAS_FMT_FORMAT`: This is defined by `` when the pass:[{fmt}] library is available. When defined, that header provides `fmt::formatter` specializations for both types. + [#host_device] - `BOOST_INT128_HOST_DEVICE`: This is defined to `pass:[__host__ __device__]` when compiling with NVCC (`pass:[__NVCC__]` is defined), and to nothing otherwise. -All public functions, constructors, operators, and conversion operators in the library are annotated with this macro, allowing `int128_t` and `uint128_t` to be used in CUDA device code without modification. +The core type operations (constructors, conversion operators, and the comparison, bitwise, arithmetic, and shift operators) and most free functions are annotated with this macro, allowing `int128_t` and `uint128_t` to be used in CUDA device code. Host-only integrations that depend on host facilities are not annotated: `std::hash`, the iostream operators, the `std::formatter` / pass:[{fmt}] support, and the `core::string_view` overloads of `from_chars`. diff --git a/doc/modules/ROOT/pages/design.adoc b/doc/modules/ROOT/pages/design.adoc new file mode 100644 index 00000000..71f536cf --- /dev/null +++ b/doc/modules/ROOT/pages/design.adoc @@ -0,0 +1,48 @@ +//// +Copyright 2025 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#design] += Design Decisions +:idprefix: design_ + +== Exactly 128 Bits on Every Platform + +The central design goal is that `sizeof(uint128_t)` and `sizeof(int128_t)` are exactly 16 bytes on every supported platform. +The compiler's `pass:[__int128]` extension is only available on some 64-bit targets and is absent on MSVC, while multiprecision types typically carry an additional word of bookkeeping. +By fixing the layout at two 64-bit words, the library provides a drop-in 128-bit integer whose size and alignment match a built-in type where one exists. +See xref:comp_to_multiprecision.adoc[Comparison to Boost.Multiprecision] for the tradeoffs relative to a multiprecision `number`. + +== Intrinsics with a Portable Fallback + +Where the compiler provides a native 128-bit integer or a suitable intrinsic, the operators forward to it for native performance. +Elsewhere (including MSVC and 32-bit targets) the library uses optimized software implementations over the two 64-bit words. +This keeps behavior and results identical across platforms while still taking advantage of hardware support when it is present. + +== pass:[C++14] and constexpr + +The library targets pass:[C++14] as its minimum standard. +Relaxed pass:[C++14] `constexpr` allows nearly all functionality to be evaluated at compile time, so the types can be used in constant expressions much like a built-in integer. +Meeting the pass:[C++14] bar also satisfies the conceptual requirements of libraries such as Boost.Math and Boost.Random, which this library integrates with directly. + +== Emulating a Built-in Integer + +To behave like a built-in integer in generic code, the types provide implicit conversions to and from the built-in integer and floating-point types, and implicit conversion between `int128_t` and `uint128_t`. +The single exception is `operator bool`, which is `explicit` to match the standard library convention and to avoid unintended participation in arithmetic and overload resolution. + +Mixed-sign and mixed-width operations follow the usual arithmetic conversions of the language. +In particular, an operation mixing a signed and an unsigned 128-bit operand produces a `uint128_t`, identical to the value the equivalent built-in `pass:[unsigned __int128]` operation would produce, with two's-complement wrap-around. +See xref:mixed_type_ops.adoc[Mixed Type Operations] for the complete result-type rules. + +== Layout and Alignment + +Each type is a struct of two 64-bit words, `low` and `high`, whose declaration order depends on the endianness of the target so that the in-memory representation matches a native 128-bit integer. +For `int128_t` the `high` word is signed. +When a native 128-bit type is available the struct is over-aligned to match it, except on 32-bit x86 where the native alignment is not forced. + +== GPU Support + +The types and many of the free functions are annotated with `BOOST_INT128_HOST_DEVICE` so they can be used unchanged in CUDA device code when the library is compiled with `BOOST_INT128_ENABLE_CUDA`. +See xref:config.adoc[Configuration Macros]. diff --git a/doc/modules/ROOT/pages/examples.adoc b/doc/modules/ROOT/pages/examples.adoc index 9f2b4fdb..ab880458 100644 --- a/doc/modules/ROOT/pages/examples.adoc +++ b/doc/modules/ROOT/pages/examples.adoc @@ -136,9 +136,9 @@ include::example$stream.cpp[] === Basic Streaming === Signed value: -42 Unsigned value (dec): 36893488147419103231 -Unsigned value (hex): 0x1ffffffffffffffff -Unsigned value (oct): 03777777777777777777777 -Upper unsigned value: 0X1FFFFFFFFFFFFFFFF +Unsigned value (hex): 1ffffffffffffffff +Unsigned value (oct): 3777777777777777777777 +Upper unsigned value: 1FFFFFFFFFFFFFFFF Lower unsigned value: 36893488147419103231 === Large Values (Beyond 64-bit) === @@ -427,11 +427,15 @@ int128_t to_string(INT64_MAX): 9223372036854775807 std::to_string(INT64_MAX): 9223372036854775807 Match: true -int128_t min: -170141183460469231731687303715884105728 +int128_t min with string literal: 0 + +int128_t min with INT128_C macro: -170141183460469231731687303715884105728 int128_t max: 170141183460469231731687303715884105727 ---- ==== +NOTE: The line `int128_t min with string literal: 0` is expected. The positive magnitude of the minimum `int128_t` value (2^127) is larger than the maximum `int128_t` value, so it cannot be written as a negated `_i128` literal. Construct it with `BOOST_INT128_INT128_C` or `(std::numeric_limits::min)()`, as the example shows. + [#examples_fmt_format] == \{fmt} Library Integration diff --git a/doc/modules/ROOT/pages/file_structure.adoc b/doc/modules/ROOT/pages/file_structure.adoc index 1abb1c4b..775865a6 100644 --- a/doc/modules/ROOT/pages/file_structure.adoc +++ b/doc/modules/ROOT/pages/file_structure.adoc @@ -27,9 +27,6 @@ The entire library can be consumed via ``, or by independently | xref:api_reference.adoc#api_macro_literals[``] | C-style limit macros (`BOOST_INT128_INT128_MAX`, etc.) -| xref:config.adoc[``] -| Configuration macros - | xref:cstdlib.adoc[``] | Division with quotient and remainder (`div`) @@ -62,4 +59,7 @@ The entire library can be consumed via ``, or by independently | xref:string.adoc[``] | `to_string` overloads + +| xref:utilities.adoc[``] +| Modular exponentiation, integer power, integer square root, and checked arithmetic |=== diff --git a/doc/modules/ROOT/pages/format.adoc b/doc/modules/ROOT/pages/format.adoc index c257cf58..f63ca391 100644 --- a/doc/modules/ROOT/pages/format.adoc +++ b/doc/modules/ROOT/pages/format.adoc @@ -51,11 +51,6 @@ There are three allowable signs first in the format string: | pass:[` `] (Space) | Adds a ` ` to positive values, and a `-` to negative values. Preserves alignment |=== -== Padding - -You can then add any number of padding characters to make sure that a formatted value takes up a specified width (5 means minimum width of 5 characters and so on). -Any integer value is accepted here. - == Prepend Prefix If you want to prepend the prefix to your number (if applicable) add `#` @@ -69,6 +64,11 @@ If you want to prepend the prefix to your number (if applicable) add `#` | Hex | `0x` or `0X` |=== +== Padding + +You can then add any number of padding characters to make sure that a formatted value takes up a specified width (5 means minimum width of 5 characters and so on). +Any integer value is accepted here. + == Output Base Modifiers The following type modifiers are the same as those used by built-in integer values. diff --git a/doc/modules/ROOT/pages/getting_started.adoc b/doc/modules/ROOT/pages/getting_started.adoc new file mode 100644 index 00000000..4fd9cf69 --- /dev/null +++ b/doc/modules/ROOT/pages/getting_started.adoc @@ -0,0 +1,92 @@ +//// +Copyright 2025 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#getting_started] += Getting Started +:idprefix: gs_ + +Boost.Int128 is header-only. The core types and functions have no required dependencies, so in most cases it is enough to add the library's `include` directory to your compiler's search path and include the umbrella header: + +[source, c++] +---- +#include +---- + +The umbrella header pulls in the two types and every feature that has no external dependency. The optional integration headers (xref:charconv.adoc[`charconv.hpp`], `random.hpp`, and the pass:[{fmt}] support in xref:format.adoc[`fmt_format.hpp`]) are not included by the umbrella and must be included explicitly. See xref:file_structure.adoc[File Structure] for the full header list. + +== B2 + +Run the following commands to clone the latest version of Boost, prepare the Boost.Build system, and build with pass:[C++14] as the default standard: + +[source, bash] +---- +git clone https://github.com/boostorg/boost +cd boost +git submodule update --init +./bootstrap +./b2 cxxstd=14 +---- + +To install the headers system-wide, run: + +[source, bash] +---- +sudo ./b2 install cxxstd=14 +---- + +The value of `cxxstd` must be at least 14. https://www.boost.org/doc/libs/release/tools/build/doc/html/index.html[See the b2 documentation] under `cxxstd` for all valid values. + +== CMake + +Boost.Int128 ships a CMake package and the `Boost::int128` target. When building against a Boost tree that contains this library, request it and link the interface target: + +[source, cmake] +---- +cmake .. -DBOOST_INCLUDE_LIBRARIES="int128" +---- + +[source, cmake] +---- +target_link_libraries(my_target PRIVATE Boost::int128) +---- + +The library can also be consumed on its own with `FetchContent`: + +[source, cmake] +---- +include(FetchContent) +FetchContent_Declare( + int128 + GIT_REPOSITORY https://github.com/boostorg/int128 + GIT_TAG develop +) +FetchContent_MakeAvailable(int128) + +target_link_libraries(my_target PRIVATE Boost::int128) +---- + +== pass:[C++20] Module + +When compiling with pass:[C++20] or newer, the library can be consumed as a named module instead of via header inclusion: + +[source, c++] +---- +import boost.int128; +---- + +Building the module requires defining `BOOST_INT128_BUILD_MODULE` and compiling `module/int128.cxx` with your toolchain's module support. See xref:config.adoc[Configuration Macros] for details. + +== CUDA Support + +The library types and many of the functions can run on both host and device under CUDA. Compile with a CUDA-aware toolchain and define `BOOST_INT128_ENABLE_CUDA`. Functions carrying `BOOST_INT128_HOST_DEVICE` in their signature run on both host and device; all others run on host only. See xref:config.adoc[Configuration Macros]. + +== Dependencies + +The core library (everything included by ``) has no required dependencies and needs only a conforming pass:[C++14] compiler. The optional integration headers depend on the following, and are only usable when those components are available: + +* xref:charconv.adoc[``] requires Boost.Charconv. +* `` requires Boost.Random. +* xref:format.adoc[``] requires the https://github.com/fmtlib/fmt[pass:[{fmt}]] library. diff --git a/doc/modules/ROOT/pages/i128_benchmarks.adoc b/doc/modules/ROOT/pages/i128_benchmarks.adoc index af9b1f3c..43d697d9 100644 --- a/doc/modules/ROOT/pages/i128_benchmarks.adoc +++ b/doc/modules/ROOT/pages/i128_benchmarks.adoc @@ -32,10 +32,6 @@ On MSVC platforms we use as reference `std::_Signed128` from the header `<__msvc | Modulo | 4803576 | 5257406 | 4988844 | 5081814 |=== -//// -image::i128_graphs/linux/x64_benchmarks.png[x64 Benchmark Results, width=100%] -//// - image::i128_graphs/linux/x64_relative_performance.png[x64 Relative Performance, width=100%] === ARM64 @@ -52,10 +48,6 @@ image::i128_graphs/linux/x64_relative_performance.png[x64 Relative Performance, | Modulo | 2235322 | 2159401 | 2535438 | 2321638 |=== -//// -image::i128_graphs/linux/ARM64_benchmarks.png[ARM64 Benchmark Results, width=100%] -//// - image::i128_graphs/linux/ARM64_relative_performance.png[ARM64 Relative Performance, width=100%] === S390x @@ -72,10 +64,6 @@ image::i128_graphs/linux/ARM64_relative_performance.png[ARM64 Relative Performan | Modulo | 4218409 | 2330114 | 3684163 | 3849849 |=== -//// -image::i128_graphs/linux/s390x_benchmarks.png[s390x Benchmark Results, width=100%] -//// - image::i128_graphs/linux/s390x_relative_performance.png[s390x Relative Performance, width=100%] === PPC64LE @@ -92,13 +80,8 @@ image::i128_graphs/linux/s390x_relative_performance.png[s390x Relative Performan | Modulo | 5623562 | 4750314 | 5641480 |=== -//// -image::i128_graphs/linux/ppc64le_benchmarks.png[ppc64le Benchmark Results, width=100%] -//// - image::i128_graphs/linux/ppc64le_relative_performance.png[ppc64le Relative Performance, width=100%] - === x86_32 NOTE: This platform has no hardware type so we compare relative to `boost::mp::int128_t` @@ -115,10 +98,6 @@ NOTE: This platform has no hardware type so we compare relative to `boost::mp::i | Modulo | 10851123 | 17348307 |=== -//// -image::i128_graphs/linux/x86_benchmarks.png[x86 Benchmark Results, width=100%] -//// - image::i128_graphs/linux/x86_relative_performance.png[x86 Relative Performance, width=100%] === ARM32 @@ -137,10 +116,6 @@ NOTE: This platform has no hardware type so we compare relative to `boost::mp::i | Modulo | 18064819 | 30144743 |=== -//// -image::i128_graphs/linux/ARM32_benchmarks.png[ARM32 Benchmark Results, width=100%] -//// - image::i128_graphs/linux/ARM32_relative_performance.png[ARM32 Relative Performance, width=100%] [#i128_windows] @@ -159,9 +134,6 @@ image::i128_graphs/linux/ARM32_relative_performance.png[ARM32 Relative Performan | Division | 1387560 | 1752869 | 6924406 | Modulo | 1616895 | 1908345 | 6397442 |=== -//// -image::i128_graphs/windows/x64_benchmarks.png[x64 Benchmark Results, width=100%] -//// image::i128_graphs/windows/x64_relative_performance.png[x64 Relative Performance, width=100%] @@ -178,9 +150,6 @@ image::i128_graphs/windows/x64_relative_performance.png[x64 Relative Performance | Division | 1128432 | 1107571 | 2472959 | Modulo | 1427629 | 1310481 | 2926904 |=== -//// -image::i128_graphs/windows/ARM64_benchmarks.png[ARM64 Benchmark Results, width=100%] -//// image::i128_graphs/windows/ARM64_relative_performance.png[ARM64 Relative Performance, width=100%] @@ -197,9 +166,6 @@ image::i128_graphs/windows/ARM64_relative_performance.png[ARM64 Relative Perform | Division | 7174578 | 7189000 | 9748526 | Modulo | 5528639 | 7028725 | 9205892 |=== -//// -image::i128_graphs/windows/x86_benchmarks.png[x86_32 Benchmark Results, width=100%] -//// image::i128_graphs/windows/x86_relative_performance.png[x86_32 Relative Performance, width=100%] @@ -220,8 +186,4 @@ image::i128_graphs/windows/x86_relative_performance.png[x86_32 Relative Performa | Modulo | 664356 | 662282 | 1026487 | 665474 |=== -//// -image::i128_graphs/macos/ARM64_benchmarks.png[ARM64 Benchmark Results, width=100%] -//// - image::i128_graphs/macos/ARM64_relative_performance.png[ARM64 Relative Performance, width=100%] diff --git a/doc/modules/ROOT/pages/int128_t.adoc b/doc/modules/ROOT/pages/int128_t.adoc index 70f7aa56..eb3fd323 100644 --- a/doc/modules/ROOT/pages/int128_t.adoc +++ b/doc/modules/ROOT/pages/int128_t.adoc @@ -50,14 +50,14 @@ The type provides: [#i128_alignment] == Alignment -If your platform has a native 128-bit signed integer, the struct is defined as so: +If your platform has a native 128-bit signed integer, the struct is over-aligned to match it, except on 32-bit x86 where the native alignment is not forced: [source, c++] ---- struct alignas(alignof(__int128)) int128_t ---- -Otherwise, it is left up to the compiler to decide. +Otherwise (including 32-bit x86), the alignment is left up to the compiler to decide. [#i128_operator_behavior] == Operator Behavior @@ -358,20 +358,24 @@ This operation is subject to mixed sign limitations discussed xref:int128_t.adoc template BOOST_INT128_HOST_DEVICE constexpr int128_t operator<<(const int128_t lhs, const Integer rhs) noexcept; -template ::value && (sizeof(Integer) * 8 > 16), bool> = true> -BOOST_INT128_HOST_DEVICE constexpr Integer operator<<(const Integer lhs, const int128_t rhs) noexcept; - template && (sizeof(SignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr int operator<<(const SignedInteger lhs, const int128_t rhs) noexcept; template && (sizeof(UnsignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr unsigned int operator<<(const UnsignedInteger lhs, const int128_t rhs) noexcept; +// When the compiler provides native 128-bit integers, a built-in 128-bit lhs +// shifted by an int128_t returns that built-in type. +#if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128) +BOOST_INT128_HOST_DEVICE constexpr detail::builtin_u128 operator<<(const detail::builtin_u128 lhs, const int128_t rhs) noexcept; +BOOST_INT128_HOST_DEVICE constexpr detail::builtin_i128 operator<<(const detail::builtin_i128 lhs, const int128_t rhs) noexcept; +#endif + BOOST_INT128_HOST_DEVICE constexpr int128_t operator<<(const int128_t lhs, const int128_t rhs) noexcept; ---- Returns the bitwise left shift of `lhs` without exception. -The return type is dependent on the type of `Integer` when it is the `lhs` value to match the promotion behavior of built-in integer types. +When a built-in integer is the `lhs` and an `int128_t` is the shift amount, the return type matches the promotion behavior of the built-in shift: integer types of 16 bits or fewer promote to `int` or `unsigned int`, and, where the compiler provides them, the native 128-bit types return their own type. This operation is subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above]. === Right Shift @@ -381,26 +385,42 @@ This operation is subject to mixed sign limitations discussed xref:int128_t.adoc template BOOST_INT128_HOST_DEVICE constexpr int128_t operator>>(const int128_t lhs, const Integer rhs) noexcept; -template ::value && (sizeof(Integer) * 8 > 16), bool> = true> -BOOST_INT128_HOST_DEVICE constexpr Integer operator>>(const Integer lhs, const int128_t rhs) noexcept; - template && (sizeof(SignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr int operator>>(const SignedInteger lhs, const int128_t rhs) noexcept; template && (sizeof(UnsignedInteger) * 8 <= 16), bool> = true> -BOOST_INT128_HOST_DEVICE constexpr unsigned operator>>(UnsignedInteger lhs, const int128_t rhs) noexcept; +BOOST_INT128_HOST_DEVICE constexpr unsigned int operator>>(const UnsignedInteger lhs, const int128_t rhs) noexcept; + +// When the compiler provides native 128-bit integers, a built-in 128-bit lhs +// shifted by an int128_t returns that built-in type. +#if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128) +BOOST_INT128_HOST_DEVICE constexpr detail::builtin_u128 operator>>(const detail::builtin_u128 lhs, const int128_t rhs) noexcept; +BOOST_INT128_HOST_DEVICE constexpr detail::builtin_i128 operator>>(const detail::builtin_i128 lhs, const int128_t rhs) noexcept; +#endif BOOST_INT128_HOST_DEVICE constexpr int128_t operator>>(const int128_t lhs, const int128_t rhs) noexcept; ---- Returns the bitwise right shift of `lhs` without exception. -The return type is dependent on the type of `Integer` when it is the `lhs` value to match the promotion behavior of built-in integer types. +When a built-in integer is the `lhs` and an `int128_t` is the shift amount, the return type matches the promotion behavior of the built-in shift: integer types of 16 bits or fewer promote to `int` or `unsigned int`, and, where the compiler provides them, the native 128-bit types return their own type. This operation is subject to mixed sign limitations discussed xref:int128_t.adoc#i128_operator_behavior[above]. [#i128_math_operators] == Arithmetic Operators +=== Unary Plus and Minus + +[source, c++] +---- +BOOST_INT128_HOST_DEVICE constexpr int128_t operator+(const int128_t value) noexcept; +BOOST_INT128_HOST_DEVICE constexpr int128_t operator-(const int128_t value) noexcept; +---- + +Unary `operator+` returns `value` unchanged. +Unary `operator-` returns the arithmetic negation of `value`. +Negating `BOOST_INT128_INT128_MIN` wraps back to itself, matching the behavior of built-in signed integers. + === Addition [source, c++] diff --git a/doc/modules/ROOT/pages/literals.adoc b/doc/modules/ROOT/pages/literals.adoc index 979c5644..47f5eb2b 100644 --- a/doc/modules/ROOT/pages/literals.adoc +++ b/doc/modules/ROOT/pages/literals.adoc @@ -45,6 +45,8 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t operator ""_I128(const char* str, st The macros at the end allow you to write out a 128-bit number like you would with say `UINT64_C` without having to add quotes. +NOTE: The minimum `int128_t` value cannot be written as a negated `_i128` literal, because its positive magnitude (2^127) exceeds the maximum `int128_t` value and so fails to parse (yielding `0`). Use `BOOST_INT128_INT128_C(-170141183460469231731687303715884105728)` or `(std::numeric_limits::min)()` instead. + See the xref:examples.adoc#examples_construction[construction examples] for usage demonstrations of both literals and macros. == Design Rationale diff --git a/doc/modules/ROOT/pages/mixed_type_ops.adoc b/doc/modules/ROOT/pages/mixed_type_ops.adoc index 8961ecdb..681e9dde 100644 --- a/doc/modules/ROOT/pages/mixed_type_ops.adoc +++ b/doc/modules/ROOT/pages/mixed_type_ops.adoc @@ -10,7 +10,7 @@ https://www.boost.org/LICENSE_1_0.txt == Conversions -The ability to convert between the two types via `static_cast` is available as documented in the above class descriptions. +Conversion between the two types is implicit: each type provides a non-explicit converting constructor from the other, so an `int128_t` converts to a `uint128_t` (and vice versa) wherever the target type is expected, as documented in the above class descriptions. == Operator Overloads Across Types diff --git a/doc/modules/ROOT/pages/numeric.adoc b/doc/modules/ROOT/pages/numeric.adoc index 432044c0..b7808510 100644 --- a/doc/modules/ROOT/pages/numeric.adoc +++ b/doc/modules/ROOT/pages/numeric.adoc @@ -33,7 +33,7 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t sub_sat(int128_t lhs, int128_t rhs) BOOST_INT128_HOST_DEVICE constexpr uint128_t mul_sat(uint128_t lhs, uint128_t rhs) noexcept; -BOOST_INT128_HOST_DEVICE constexpr int128_t mul_sat(int128_t lhs, int128_t rhs) noexcept; +BOOST_INT128_HOST_DEVICE constexpr int128_t mul_sat(const int128_t& lhs, const int128_t& rhs) noexcept; BOOST_INT128_HOST_DEVICE constexpr uint128_t div_sat(uint128_t lhs, uint128_t rhs) noexcept; @@ -47,8 +47,9 @@ BOOST_INT128_HOST_DEVICE constexpr int128_t div_sat(int128_t lhs, int128_t rhs) [#saturating_cast] == Saturating Cast -This function allows a `LibraryIntegerType` (i.e. `uint128_t` or `int128_t`) to be safely casted to another integer type to include built-in and hardware integer types (`TargetIntegerType`). -Should the `TargetIntegerType` not be able to represent the value of the `LibraryIntegerType` it will be set to either `std::numeric_limits::max()` or `std::numeric_limits::min()` depending on if the situation is overflow or underflow. +This function converts a `uint128_t` or `int128_t` to a `TargetType`, saturating rather than wrapping when the value is out of the target's range. +`TargetType` is constrained (via SFINAE) to the library's set of reduced integer types: the standard signed and unsigned integer types (excluding `bool` and plain `char`), `int128_t`, `uint128_t`, and the compiler's native 128-bit integer types where available. +Should the `TargetType` not be able to represent the value it is set to either `std::numeric_limits::max()` or `std::numeric_limits::min()` depending on whether the situation is overflow or underflow. [source, c++] ---- diff --git a/doc/modules/ROOT/pages/printer.adoc b/doc/modules/ROOT/pages/printer.adoc index 682651cb..3767c4ff 100644 --- a/doc/modules/ROOT/pages/printer.adoc +++ b/doc/modules/ROOT/pages/printer.adoc @@ -17,7 +17,7 @@ To use this, add the following line to your `~/.lldbinit` file: [source] ---- -command script import /path/to/int128/extra/int128_printer.py +command script import /path/to/int128/extra/int128_printer_lldb.py ---- If this is successful, you should see the following message in your debugger upon startup: diff --git a/doc/modules/ROOT/pages/random.adoc b/doc/modules/ROOT/pages/random.adoc new file mode 100644 index 00000000..c1b77bfe --- /dev/null +++ b/doc/modules/ROOT/pages/random.adoc @@ -0,0 +1,40 @@ +//// +Copyright 2026 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#random] += Boost.Random Support +:idprefix: random_ + +[source, c++] +---- +#include +---- + +Including this header specializes the traits that Boost.Random uses to reason about integer types, so that `int128_t` and `uint128_t` can be used directly with the Boost.Random engines and distributions (for example `boost::random::uniform_int_distribution`). +This header requires Boost.Random to be available. + +The following specializations are provided in namespace `boost::random::traits` for both `int128_t` and `uint128_t`: + +[cols="1,2", options="header"] +|=== +| Trait | Purpose + +| `is_integral` +| Reports both types as integral. + +| `is_signed` +| Reports `int128_t` as signed and `uint128_t` as unsigned. + +| `make_unsigned`, `make_unsigned_imp` +| Provide the unsigned type mapping Boost.Random uses when generating values. + +| `make_unsigned_or_unbounded`, `make_unsigned_or_unbounded_imp` +| Provide the unsigned-or-unbounded type mapping Boost.Random uses. +|=== + +NOTE: These specializations live in `boost::random::traits`, not `boost::int128`. + +An example combining Boost.Random with the library types is available xref:examples.adoc#examples_boost_math_random[on the examples page]. diff --git a/doc/modules/ROOT/pages/reference.adoc b/doc/modules/ROOT/pages/reference.adoc index a4351d67..54ad2695 100644 --- a/doc/modules/ROOT/pages/reference.adoc +++ b/doc/modules/ROOT/pages/reference.adoc @@ -8,8 +8,14 @@ https://www.boost.org/LICENSE_1_0.txt = References :idprefix: ref_ -The following books, papers and blog posts serve as the basis for the algorithms used in the library: +The following books, papers, and specifications inform the algorithms and interfaces used in the library: :linkattrs: -- Donald E. Knuth, _The Art of Computer Programming Volume 2 Seminumerical Algorithms_, 3rd edition, 1998 +- Donald E. Knuth, _The Art of Computer Programming Volume 2: Seminumerical Algorithms_, 3rd edition, 1998 (multiprecision multiplication and division). + +- Henry S. Warren Jr., _Hacker's Delight_, 2nd edition, 2013 (bit-manipulation algorithms: leading and trailing zero counts, population count, and rounding to powers of two). + +- ISO/IEC, _Programming Languages - C++_ (the ``, ``, ``, and `` interfaces mirrored by this library). + +- ISO/IEC 9899:2024 (C23), _Programming Languages - C_ (the `` checked-integer interface mirrored by `ckd_add`, `ckd_sub`, and `ckd_mul`). diff --git a/doc/modules/ROOT/pages/stream.adoc b/doc/modules/ROOT/pages/stream.adoc index 04403303..75ecfd49 100644 --- a/doc/modules/ROOT/pages/stream.adoc +++ b/doc/modules/ROOT/pages/stream.adoc @@ -39,14 +39,17 @@ std::basic_istream& operator>>(std::basic_istream& == Flags [#ios] -The following flags from `` are supported for both streaming directions: - -- `std::oct` - Octal Numbers -- `std::dec` - Decimal Numbers -- `std::hex` - Hexadecimal Numbers -- `std::uppercase` - Upper Case Formatting (e.g. FFFF) -- `std::nouppercase` - Lower Case Formatting (e.g. ffff) -- `std::showbase` - Adds a leading base for hex or oct numbers (e.g. 0xffff) -- `std::noshowbase` - Removes the leading base for hex or oct numbers (e.g. ffff) +The following flags from `` are honored. +The base flags (`std::oct`, `std::dec`, `std::hex`) affect both input and output; the remaining flags affect output only. + +- `std::oct` - Octal numbers (input and output) +- `std::dec` - Decimal numbers (input and output) +- `std::hex` - Hexadecimal numbers (input and output) +- `std::uppercase` - Uppercase hexadecimal digits on output (e.g. FFFF) +- `std::nouppercase` - Lowercase hexadecimal digits on output (e.g. ffff) +- `std::showbase` - Adds a leading base prefix for hex or oct numbers on output (e.g. 0xffff) +- `std::noshowbase` - Omits the leading base prefix on output (e.g. ffff) + +On extraction (`operator>>`) a leading `0x` or `0` base prefix is accepted and consumed for the active base regardless of `showbase`, and hexadecimal input is parsed case-insensitively. See the xref:examples.adoc#examples_io[IO streaming example] for usage demonstrations. diff --git a/doc/modules/ROOT/pages/string.adoc b/doc/modules/ROOT/pages/string.adoc index 83788eaf..f89c9b29 100644 --- a/doc/modules/ROOT/pages/string.adoc +++ b/doc/modules/ROOT/pages/string.adoc @@ -22,8 +22,10 @@ The following are functions analogous to those found in `` for builtin i namespace boost { namespace int128 { -inline std::string to_string(const int128_t& value); -inline std::string to_string(const uint128_t& value); +// Constrained to T being int128_t or uint128_t. +template +std::enable_if_t::value || std::is_same::value, std::string> +to_string(const T& value); } // namespace int128 } // namespace boost diff --git a/doc/modules/ROOT/pages/u128_benchmarks.adoc b/doc/modules/ROOT/pages/u128_benchmarks.adoc index ff2e0089..29342231 100644 --- a/doc/modules/ROOT/pages/u128_benchmarks.adoc +++ b/doc/modules/ROOT/pages/u128_benchmarks.adoc @@ -32,10 +32,6 @@ On MSVC platforms we use as reference `std::_Unsigned128` from the header `<__ms | Modulo | 3965562 | 4506879 | 4487023 | 4247367 |=== -//// -image::u128_graphs/linux/x64_benchmarks.png[x64 Benchmark Results, width=100%] -//// - image::u128_graphs/linux/x64_relative_performance.png[x64 Relative Performance, width=100%] === ARM64 @@ -52,10 +48,6 @@ image::u128_graphs/linux/x64_relative_performance.png[x64 Relative Performance, | Modulo | 2274294 | 1743274 | 2324356 | 2381378 |=== -//// -image::u128_graphs/linux/ARM64_benchmarks.png[ARM64 Benchmark Results, width=100%] -//// - image::u128_graphs/linux/ARM64_relative_performance.png[ARM64 Relative Performance, width=100%] === S390x @@ -72,10 +64,6 @@ image::u128_graphs/linux/ARM64_relative_performance.png[ARM64 Relative Performan | Modulo | 4156643 | 2133029 | 4398856 | 5011442 |=== -//// -image::u128_graphs/linux/s390x_benchmarks.png[s390x Benchmark Results, width=100%] -//// - image::u128_graphs/linux/s390x_relative_performance.png[s390x Relative Performance, width=100%] === PPC64LE @@ -92,13 +80,8 @@ image::u128_graphs/linux/s390x_relative_performance.png[s390x Relative Performan | Modulo | 4955570 | 4273487 | 5407877 |=== -//// -image::u128_graphs/linux/ppc64le_benchmarks.png[ppc64le Benchmark Results, width=100%] -//// - image::u128_graphs/linux/ppc64le_relative_performance.png[ppc64le Relative Performance, width=100%] - === x86_32 NOTE: This platform has no hardware type so we compare relative to `boost::mp::uint128_t` @@ -115,10 +98,6 @@ NOTE: This platform has no hardware type so we compare relative to `boost::mp::u | Modulo | 9932867 | 10242720 |=== -//// -image::u128_graphs/linux/x86_benchmarks.png[x86 Benchmark Results, width=100%] -//// - image::u128_graphs/linux/x86_relative_performance.png[x86 Relative Performance, width=100%] === ARM32 @@ -137,10 +116,6 @@ NOTE: This platform has no hardware type so we compare relative to `boost::mp::u | Modulo | 20332627 | 27238658 |=== -//// -image::u128_graphs/linux/ARM32_benchmarks.png[ARM32 Benchmark Results, width=100%] -//// - image::u128_graphs/linux/ARM32_relative_performance.png[ARM32 Relative Performance, width=100%] [#u128_windows] @@ -159,9 +134,6 @@ image::u128_graphs/linux/ARM32_relative_performance.png[ARM32 Relative Performan | Division | 1476783 | 1489919 | 4898388 | Modulo | 1421066 | 1411521 | 3793762 |=== -//// -image::u128_graphs/windows/x64_benchmarks.png[x64 Benchmark Results, width=100%] -//// image::u128_graphs/windows/x64_relative_performance.png[x64 Relative Performance, width=100%] @@ -178,9 +150,6 @@ image::u128_graphs/windows/x64_relative_performance.png[x64 Relative Performance | Division | 992963 | 790846 | 2035065 | Modulo | 1087702 | 861121 | 1702396 |=== -//// -image::u128_graphs/windows/ARM64_benchmarks.png[ARM64 Benchmark Results, width=100%] -//// image::u128_graphs/windows/ARM64_relative_performance.png[ARM64 Relative Performance, width=100%] @@ -197,9 +166,6 @@ image::u128_graphs/windows/ARM64_relative_performance.png[ARM64 Relative Perform | Division | 5596877 | 5629510 | 6756883 | Modulo | 4616488 | 5696116 | 6409969 |=== -//// -image::u128_graphs/windows/x86_benchmarks.png[x86_32 Benchmark Results, width=100%] -//// image::u128_graphs/windows/x86_relative_performance.png[x86_32 Relative Performance, width=100%] @@ -220,8 +186,4 @@ image::u128_graphs/windows/x86_relative_performance.png[x86_32 Relative Performa | Modulo | 733080 | 699666 | 951657 | 719500 |=== -//// -image::u128_graphs/macos/ARM64_benchmarks.png[ARM64 Benchmark Results, width=100%] -//// - image::u128_graphs/macos/ARM64_relative_performance.png[ARM64 Relative Performance, width=100%] diff --git a/doc/modules/ROOT/pages/uint128_t.adoc b/doc/modules/ROOT/pages/uint128_t.adoc index bd5e68c4..9004355f 100644 --- a/doc/modules/ROOT/pages/uint128_t.adoc +++ b/doc/modules/ROOT/pages/uint128_t.adoc @@ -50,14 +50,14 @@ The type provides: [#u128_alignment] == Alignment -If your platform has a native 128-bit unsigned integer, the struct is defined as so: +If your platform has a native 128-bit unsigned integer, the struct is over-aligned to match it, except on 32-bit x86 where the native alignment is not forced: [source, c++] ---- struct alignas(alignof(unsigned __int128)) uint128_t ---- -Otherwise, it is left up to the compiler to decide. +Otherwise (including 32-bit x86), the alignment is left up to the compiler to decide. [#u128_operator_behavior] == Operator Behavior @@ -359,20 +359,24 @@ This operation is subject to mixed sign limitations discussed xref:uint128_t.ado template BOOST_INT128_HOST_DEVICE constexpr uint128_t operator<<(const uint128_t lhs, const Integer rhs) noexcept; -template ::value && (sizeof(Integer) * 8 > 16), bool> = true> -BOOST_INT128_HOST_DEVICE constexpr Integer operator<<(const Integer lhs, const uint128_t rhs) noexcept; - template && (sizeof(SignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr int operator<<(const SignedInteger lhs, const uint128_t rhs) noexcept; template && (sizeof(UnsignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr unsigned int operator<<(const UnsignedInteger lhs, const uint128_t rhs) noexcept; +// When the compiler provides native 128-bit integers, a built-in 128-bit lhs +// shifted by a uint128_t returns that built-in type. +#if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128) +BOOST_INT128_HOST_DEVICE constexpr detail::builtin_u128 operator<<(const detail::builtin_u128 lhs, const uint128_t rhs) noexcept; +BOOST_INT128_HOST_DEVICE constexpr detail::builtin_i128 operator<<(const detail::builtin_i128 lhs, const uint128_t rhs) noexcept; +#endif + BOOST_INT128_HOST_DEVICE constexpr uint128_t operator<<(const uint128_t lhs, const uint128_t rhs) noexcept; ---- Returns the bitwise left shift of `lhs` without exception. -The return type is dependent on the type of `Integer` when it is the `lhs` value to match the promotion behavior of built-in integer types. +When a built-in integer is the `lhs` and a `uint128_t` is the shift amount, the return type matches the promotion behavior of the built-in shift: integer types of 16 bits or fewer promote to `int` or `unsigned int`, and, where the compiler provides them, the native 128-bit types return their own type. This operation is subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above]. === Right Shift @@ -382,26 +386,41 @@ This operation is subject to mixed sign limitations discussed xref:uint128_t.ado template BOOST_INT128_HOST_DEVICE constexpr uint128_t operator>>(const uint128_t lhs, const Integer rhs) noexcept; -template ::value && (sizeof(Integer) * 8 > 16), bool> = true> -BOOST_INT128_HOST_DEVICE constexpr Integer operator>>(const Integer lhs, const uint128_t rhs) noexcept; - template && (sizeof(SignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr int operator>>(const SignedInteger lhs, const uint128_t rhs) noexcept; template && (sizeof(UnsignedInteger) * 8 <= 16), bool> = true> BOOST_INT128_HOST_DEVICE constexpr unsigned int operator>>(const UnsignedInteger lhs, const uint128_t rhs) noexcept; +// When the compiler provides native 128-bit integers, a built-in 128-bit lhs +// shifted by a uint128_t returns that built-in type. +#if defined(BOOST_INT128_HAS_INT128) || defined(BOOST_INT128_HAS_MSVC_INT128) +BOOST_INT128_HOST_DEVICE constexpr detail::builtin_u128 operator>>(const detail::builtin_u128 lhs, const uint128_t rhs) noexcept; +BOOST_INT128_HOST_DEVICE constexpr detail::builtin_i128 operator>>(const detail::builtin_i128 lhs, const uint128_t rhs) noexcept; +#endif + BOOST_INT128_HOST_DEVICE constexpr uint128_t operator>>(const uint128_t lhs, const uint128_t rhs) noexcept; ---- Returns the bitwise right shift of `lhs` without exception. -The return type is dependent on the type of `Integer` when it is the `lhs` value to match the promotion behavior of built-in integer types. +When a built-in integer is the `lhs` and a `uint128_t` is the shift amount, the return type matches the promotion behavior of the built-in shift: integer types of 16 bits or fewer promote to `int` or `unsigned int`, and, where the compiler provides them, the native 128-bit types return their own type. This operation is subject to mixed sign limitations discussed xref:uint128_t.adoc#u128_operator_behavior[above]. [#u128_math_operators] == Arithmetic Operators +=== Unary Plus and Minus + +[source, c++] +---- +BOOST_INT128_HOST_DEVICE constexpr uint128_t operator+(const uint128_t value) noexcept; +BOOST_INT128_HOST_DEVICE constexpr uint128_t operator-(const uint128_t value) noexcept; +---- + +Unary `operator+` returns `value` unchanged. +Unary `operator-` returns the two's-complement negation of `value` (equivalent to `0 - value`), wrapping modulo `2^128`. + === Addition [source, c++] diff --git a/doc/modules/ROOT/pages/utilities.adoc b/doc/modules/ROOT/pages/utilities.adoc index 959c87bb..ec539f85 100644 --- a/doc/modules/ROOT/pages/utilities.adoc +++ b/doc/modules/ROOT/pages/utilities.adoc @@ -55,7 +55,7 @@ Negative bases are reduced before exponentiation; `(std::numeric_limits 1` (following the conventional definition `0^0 == 1`); the `m == 0` and `m == 1` rows above take precedence, so `powm(0, 0, 0)` and `powm(0, 0, 1)` return `0` | `base == 0` and `exp > 0` | `0` diff --git a/examples/to_string.cpp b/examples/to_string.cpp index a259f149..1da26c73 100644 --- a/examples/to_string.cpp +++ b/examples/to_string.cpp @@ -54,7 +54,10 @@ int main() std::cout << "std::to_string(INT64_MAX): " << s_large_std << std::endl; std::cout << "Match: " << (s_large_str == s_large_std) << std::endl; - // Values beyond 64-bit range + // Values beyond 64-bit range. + // The positive magnitude 2^127 exceeds INT128_MAX, so INT128_MIN cannot be + // written as a negated _i128 literal; this line therefore yields 0. Use the + // INT128_C macro (below) or std::numeric_limits::min() instead. const auto large_negative {-170141183460469231731687303715884105728_i128}; std::cout << "\nint128_t min with string literal: " << to_string(large_negative) << std::endl; diff --git a/include/boost/int128/fmt_format.hpp b/include/boost/int128/fmt_format.hpp index e328e638..e37f3634 100644 --- a/include/boost/int128/fmt_format.hpp +++ b/include/boost/int128/fmt_format.hpp @@ -58,8 +58,7 @@ constexpr auto parse_impl(ParseContext& ctx) int padding_digits = 0; auto sign = sign_option::negative; bool prefix = false; - bool write_as_character = false; - bool character_debug_format = false; + bool zero_pad = false; char fill_char = ' '; auto align = alignment::none; @@ -139,14 +138,14 @@ constexpr auto parse_impl(ParseContext& ctx) ++it; } - // Character presentation type - if (it != ctx.end() && (*it == '?' || *it == 'c')) + // Zero-pad flag (std::format places '0' before the width) + if (it != ctx.end() && *it == '0') { - character_debug_format = *it == '?'; + zero_pad = true; ++it; } - // Check for a padding character + // Parse the width while (it != ctx.end() && *it >= '0' && *it <= '9') { padding_digits = padding_digits * 10 + (*it - '0'); @@ -166,10 +165,6 @@ constexpr auto parse_impl(ParseContext& ctx) is_upper = true; break; - case 'c': - write_as_character = true; - break; - case 'o': base = 8; break; @@ -198,7 +193,7 @@ constexpr auto parse_impl(ParseContext& ctx) BOOST_INT128_THROW_EXCEPTION(std::logic_error("Expected '}' in format string")); // LCOV_EXCL_LINE } - return std::make_tuple(base, padding_digits, sign, is_upper, prefix, write_as_character, character_debug_format, fill_char, align, it); + return std::make_tuple(base, padding_digits, sign, is_upper, prefix, zero_pad, fill_char, align, it); } template @@ -218,8 +213,7 @@ struct formatter sign_option sign; bool is_upper; bool prefix; - bool write_as_character; - bool character_debug_format; + bool zero_pad; char fill_char; alignment align; @@ -228,8 +222,7 @@ struct formatter sign {sign_option::negative}, is_upper {false}, prefix {false}, - write_as_character {false}, - character_debug_format {false}, + zero_pad {false}, fill_char {' '}, align {alignment::none} {} @@ -243,12 +236,11 @@ struct formatter sign = std::get<2>(res); is_upper = std::get<3>(res); prefix = std::get<4>(res); - write_as_character = std::get<5>(res); - character_debug_format = std::get<6>(res); - fill_char = std::get<7>(res); - align = std::get<8>(res); + zero_pad = std::get<5>(res); + fill_char = std::get<6>(res); + align = std::get<7>(res); - return std::get<9>(res); + return std::get<8>(res); } template @@ -311,9 +303,9 @@ struct formatter sign_len = 1; } - // Zero-padding only applies when no explicit alignment is set - // Account for prefix and sign in the padding calculation - if (align == alignment::none && padding_digits > 0) + // Zero-padding applies only with the '0' flag and no explicit alignment. + // Account for prefix and sign in the padding calculation. + if (zero_pad && align == alignment::none && padding_digits > 0) { auto target_digit_width {static_cast(padding_digits)}; if (target_digit_width > prefix_len + sign_len) @@ -410,11 +402,15 @@ struct formatter s.erase(0, s.find_first_not_of('\0')); s.erase(s.find_last_not_of('\0') + 1); - // Apply alignment if specified - if (align != alignment::none && s.size() < static_cast(padding_digits)) + // Apply alignment. An explicit alignment uses fill_char; with no explicit + // alignment and no zero-padding, integer types default to right alignment + // with the fill character (matching std::format). + if (s.size() < static_cast(padding_digits) && + (align != alignment::none || !zero_pad)) { auto fill_count = static_cast(padding_digits) - s.size(); - switch (align) + const auto effective_align = (align == alignment::none) ? alignment::right : align; + switch (effective_align) { case alignment::left: s.append(fill_count, fill_char); diff --git a/include/boost/int128/format.hpp b/include/boost/int128/format.hpp index 16289415..417442c7 100644 --- a/include/boost/int128/format.hpp +++ b/include/boost/int128/format.hpp @@ -42,8 +42,7 @@ constexpr auto parse_impl(ParseContext& ctx) int padding_digits = 0; auto sign = sign_option::negative; bool prefix = false; - bool write_as_character = false; - bool character_debug_format = false; + bool zero_pad = false; char fill_char = ' '; auto align = alignment::none; @@ -123,14 +122,14 @@ constexpr auto parse_impl(ParseContext& ctx) ++it; } - // Character presentation type - if (it != ctx.end() && (*it == '?' || *it == 'c')) + // Zero-pad flag (std::format places '0' before the width) + if (it != ctx.end() && *it == '0') { - character_debug_format = *it == '?'; + zero_pad = true; ++it; } - // Check for a padding character + // Parse the width while (it != ctx.end() && *it >= '0' && *it <= '9') { padding_digits = padding_digits * 10 + (*it - '0'); @@ -150,10 +149,6 @@ constexpr auto parse_impl(ParseContext& ctx) is_upper = true; break; - case 'c': - write_as_character = true; - break; - case 'o': base = 8; break; @@ -180,7 +175,7 @@ constexpr auto parse_impl(ParseContext& ctx) BOOST_INT128_THROW_EXCEPTION(std::format_error("Expected '}' in format string")); // LCOV_EXCL_LINE } - return std::make_tuple(base, padding_digits, sign, is_upper, prefix, write_as_character, character_debug_format, fill_char, align, it); + return std::make_tuple(base, padding_digits, sign, is_upper, prefix, zero_pad, fill_char, align, it); } template @@ -207,8 +202,7 @@ struct formatter boost::int128::detail::sign_option sign; bool is_upper; bool prefix; - bool write_as_character; - bool character_debug_format; + bool zero_pad; char fill_char; boost::int128::detail::alignment align; @@ -217,8 +211,7 @@ struct formatter sign {boost::int128::detail::sign_option::negative}, is_upper {false}, prefix {false}, - write_as_character {false}, - character_debug_format {false}, + zero_pad {false}, fill_char {' '}, align {boost::int128::detail::alignment::none} {} @@ -232,12 +225,11 @@ struct formatter sign = std::get<2>(res); is_upper = std::get<3>(res); prefix = std::get<4>(res); - write_as_character = std::get<5>(res); - character_debug_format = std::get<6>(res); - fill_char = std::get<7>(res); - align = std::get<8>(res); + zero_pad = std::get<5>(res); + fill_char = std::get<6>(res); + align = std::get<7>(res); - return std::get<9>(res); + return std::get<8>(res); } template @@ -300,9 +292,9 @@ struct formatter sign_len = 1; } - // Zero-padding only applies when no explicit alignment is set - // Account for prefix and sign in the padding calculation - if (align == boost::int128::detail::alignment::none && padding_digits > 0) + // Zero-padding applies only with the '0' flag and no explicit alignment. + // Account for prefix and sign in the padding calculation. + if (zero_pad && align == boost::int128::detail::alignment::none && padding_digits > 0) { auto target_digit_width {static_cast(padding_digits)}; if (target_digit_width > prefix_len + sign_len) @@ -399,11 +391,17 @@ struct formatter s.erase(0, s.find_first_not_of('\0')); s.erase(s.find_last_not_of('\0') + 1); - // Apply alignment if specified - if (align != boost::int128::detail::alignment::none && s.size() < static_cast(padding_digits)) + // Apply alignment. An explicit alignment uses fill_char; with no explicit + // alignment and no zero-padding, integer types default to right alignment + // with the fill character (matching std::format). + if (s.size() < static_cast(padding_digits) && + (align != boost::int128::detail::alignment::none || !zero_pad)) { auto fill_count = static_cast(padding_digits) - s.size(); - switch (align) + const auto effective_align = (align == boost::int128::detail::alignment::none) + ? boost::int128::detail::alignment::right + : align; + switch (effective_align) { case boost::int128::detail::alignment::left: s.append(fill_count, fill_char); diff --git a/include/boost/int128/string.hpp b/include/boost/int128/string.hpp index eba64a0f..f7a4812b 100644 --- a/include/boost/int128/string.hpp +++ b/include/boost/int128/string.hpp @@ -22,7 +22,7 @@ auto to_string(const T& value) -> std::enable_if_t<(std::is_same::v { char buffer[64]; const auto last {detail::mini_to_chars(buffer, value, 10, false)}; - return std::string{last, buffer + sizeof(buffer)}; + return std::string{last, buffer + sizeof(buffer) - 1}; } } // namespace int128 diff --git a/test/test_fmt_format.cpp b/test/test_fmt_format.cpp index 6326042a..3115de79 100644 --- a/test/test_fmt_format.cpp +++ b/test/test_fmt_format.cpp @@ -47,8 +47,8 @@ void test_binary() BOOST_TEST_CSTR_EQ(fmt::format("{:#b}", T{5}).c_str(), "0b101"); BOOST_TEST_CSTR_EQ(fmt::format("{:#B}", T{5}).c_str(), "0B101"); - BOOST_TEST_CSTR_EQ(fmt::format("{:6b}", T{5}).c_str(), "000101"); - BOOST_TEST_CSTR_EQ(fmt::format("{:#6b}", T{5}).c_str(), "0b0101"); + BOOST_TEST_CSTR_EQ(fmt::format("{:6b}", T{5}).c_str(), " 101"); + BOOST_TEST_CSTR_EQ(fmt::format("{:#6b}", T{5}).c_str(), " 0b101"); BOOST_TEST_CSTR_EQ(fmt::format("{:#06B}", T{5}).c_str(), "0B0101"); BOOST_TEST_CSTR_EQ(fmt::format("{:#010b}", T{42}).c_str(), "0b00101010"); @@ -60,8 +60,8 @@ void test_octal() BOOST_TEST_CSTR_EQ(fmt::format("{:o}", T{42}).c_str(), "52"); BOOST_TEST_CSTR_EQ(fmt::format("{:#o}", T{42}).c_str(), "052"); - BOOST_TEST_CSTR_EQ(fmt::format("{:4o}", T{42}).c_str(), "0052"); - BOOST_TEST_CSTR_EQ(fmt::format("{:#4o}", T{42}).c_str(), "0052"); + BOOST_TEST_CSTR_EQ(fmt::format("{:4o}", T{42}).c_str(), " 52"); + BOOST_TEST_CSTR_EQ(fmt::format("{:#4o}", T{42}).c_str(), " 052"); } template @@ -79,8 +79,8 @@ void test_decimal() BOOST_TEST_CSTR_EQ(fmt::format("{:+3d}", T{42}).c_str(), "+42"); BOOST_TEST_CSTR_EQ(fmt::format("{:+#3d}", T{42}).c_str(), "+42"); - BOOST_TEST_CSTR_EQ(fmt::format("{:-3d}", T{42}).c_str(), "042"); - BOOST_TEST_CSTR_EQ(fmt::format("{:-#3d}", T{42}).c_str(), "042"); + BOOST_TEST_CSTR_EQ(fmt::format("{:-3d}", T{42}).c_str(), " 42"); + BOOST_TEST_CSTR_EQ(fmt::format("{:-#3d}", T{42}).c_str(), " 42"); } template @@ -111,10 +111,10 @@ void test_hex() BOOST_TEST_CSTR_EQ(fmt::format("{:+X}", T{42}).c_str(), "+2A"); BOOST_TEST_CSTR_EQ(fmt::format("{:+#X}", T{42}).c_str(), "+0X2A"); - BOOST_TEST_CSTR_EQ(fmt::format("{:5X}", T{42}).c_str(), "0002A"); - BOOST_TEST_CSTR_EQ(fmt::format("{:#5X}", T{42}).c_str(), "0X02A"); + BOOST_TEST_CSTR_EQ(fmt::format("{:5X}", T{42}).c_str(), " 2A"); + BOOST_TEST_CSTR_EQ(fmt::format("{:#5X}", T{42}).c_str(), " 0X2A"); - BOOST_TEST_CSTR_EQ(fmt::format("{: 5X}", T{42}).c_str(), " 002A"); + BOOST_TEST_CSTR_EQ(fmt::format("{: 5X}", T{42}).c_str(), " 2A"); BOOST_TEST_CSTR_EQ(fmt::format("{: #5X}", T{42}).c_str(), " 0X2A"); } diff --git a/test/test_format.cpp b/test/test_format.cpp index 59f6d7a6..ee806657 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -40,8 +40,8 @@ void test_binary() BOOST_TEST_CSTR_EQ(std::format("{:#b}", T{5}).c_str(), "0b101"); BOOST_TEST_CSTR_EQ(std::format("{:#B}", T{5}).c_str(), "0B101"); - BOOST_TEST_CSTR_EQ(std::format("{:6b}", T{5}).c_str(), "000101"); - BOOST_TEST_CSTR_EQ(std::format("{:#6b}", T{5}).c_str(), "0b0101"); + BOOST_TEST_CSTR_EQ(std::format("{:6b}", T{5}).c_str(), " 101"); + BOOST_TEST_CSTR_EQ(std::format("{:#6b}", T{5}).c_str(), " 0b101"); BOOST_TEST_CSTR_EQ(std::format("{:#06B}", T{5}).c_str(), "0B0101"); } @@ -51,8 +51,8 @@ void test_octal() BOOST_TEST_CSTR_EQ(std::format("{:o}", T{42}).c_str(), "52"); BOOST_TEST_CSTR_EQ(std::format("{:#o}", T{42}).c_str(), "052"); - BOOST_TEST_CSTR_EQ(std::format("{:4o}", T{42}).c_str(), "0052"); - BOOST_TEST_CSTR_EQ(std::format("{:#4o}", T{42}).c_str(), "0052"); + BOOST_TEST_CSTR_EQ(std::format("{:4o}", T{42}).c_str(), " 52"); + BOOST_TEST_CSTR_EQ(std::format("{:#4o}", T{42}).c_str(), " 052"); } template @@ -70,8 +70,8 @@ void test_decimal() BOOST_TEST_CSTR_EQ(std::format("{:+3d}", T{42}).c_str(), "+42"); BOOST_TEST_CSTR_EQ(std::format("{:+#3d}", T{42}).c_str(), "+42"); - BOOST_TEST_CSTR_EQ(std::format("{:-3d}", T{42}).c_str(), "042"); - BOOST_TEST_CSTR_EQ(std::format("{:-#3d}", T{42}).c_str(), "042"); + BOOST_TEST_CSTR_EQ(std::format("{:-3d}", T{42}).c_str(), " 42"); + BOOST_TEST_CSTR_EQ(std::format("{:-#3d}", T{42}).c_str(), " 42"); if constexpr (std::is_same_v) { @@ -86,8 +86,6 @@ void test_decimal() } BOOST_TEST_CSTR_EQ(std::format("{:+d}", T{42}).c_str(), "+42"); - BOOST_TEST_CSTR_EQ(std::format("{:+#?d}", T{42}).c_str(), "+42"); - BOOST_TEST_CSTR_EQ(std::format("{:+#c}", T{42}).c_str(), "+42"); } template @@ -111,10 +109,10 @@ void test_hex() BOOST_TEST_CSTR_EQ(std::format("{:-#X}", T{-42}).c_str(), "-0X2A"); } - BOOST_TEST_CSTR_EQ(std::format("{:5X}", T{42}).c_str(), "0002A"); - BOOST_TEST_CSTR_EQ(std::format("{:#5X}", T{42}).c_str(), "0X02A"); + BOOST_TEST_CSTR_EQ(std::format("{:5X}", T{42}).c_str(), " 2A"); + BOOST_TEST_CSTR_EQ(std::format("{:#5X}", T{42}).c_str(), " 0X2A"); - BOOST_TEST_CSTR_EQ(std::format("{: 5X}", T{42}).c_str(), " 002A"); + BOOST_TEST_CSTR_EQ(std::format("{: 5X}", T{42}).c_str(), " 2A"); BOOST_TEST_CSTR_EQ(std::format("{: #5X}", T{42}).c_str(), " 0X2A"); }