Git Product home page Git Product logo

wide-integer's Introduction

Wide-integer

Build Status Issues CodeQL Coverity Scan Quality Gate Status code coverage Boost Software License 1.0 GitHub commit activity GitHub code size in bytes

Wide-integer implements a generic C++ template for extended width unsigned and signed integral types.

This C++ template header-only library implements drop-in big integer types such as uint128_t, uint256_t, uint384_t, uint512_t, uint1024_t, uint1536_t, etc. These can be used essentially like regular built-in integers. Corresponding signed integer types such as int128_t, int256_t, and the like can also be used.

The big integer class is called math::wide_integer::uintwide_t (i.e., uintwide_t residing in the namespace math::wide_integer), as shown in greater detail below.

Wide-integer supports both unsigned as well as signed integral types having width of $1 {\ldots} 63 {\times} 2^N$ while being $16$, $24$, $32$ or larger. In addition, small integer types such as software-synthesized versions of uint24_t, uint48_t, uint64_t, uint96_t, uint128_t, etc. (or signed counterparts of these) can also be created with wide-integer.

We also emphasize here that less-common types (i.e., those less common than, say, uint128_t, uint256_t, uint512_t, etc.) can also be synthesized. Types such as uint80_t made from five 16-bit limbs, or uint96_t composed of three 32-bit limbs, or other similar types can be readily synthesized with wide-integer.

Wide-integer also features basic realizations of several elementary and number theoretical functions such as root finding, random distribution, Miller-Rabin primality testing, greatest common denominator (GCD), least common multiplier (LCM), integer division (i.e., divmod()) and more.

Inclusion of a single C++14 header file is all that is needed for using wide-integer, as shown in the examples.

Implementation goals

  • Signed and unsigned versions of uintwide_t should behave as closely as possible to the behaviors of signed and unsigned versions of built-in int.
  • Relatively wide precision range from $24$, $32$, $64$ bits up to tens of thousands of bits.
  • Moderately good efficiency over the entire wide precision range.
  • Clean header-only C++14 design.
  • Seamless portability to any modern C++14, 17, 20, 23 compiler and beyond.
  • Scalability with small memory footprint and efficiency suitable for both PC/workstation systems as well as bare-metal embedded systems.
  • C++14, 17, 20, 23 and beyond constexpr-ness.

Quick start

When working in your own project with wide-integer, using the uintwide_t.h header is straightforward. Identify the header within its directory. Include this header path to the compiler's set of include paths or in your project. Then simply #include <uintwide_t.h> the normal C++ way.

Easy application follows via traditional C-style typedef or alias such as uint512_t. An instance of the defined type can be used very much like a built-in integral type.

In the following code, for example, the static uint512_t variable x is initialized with unsigned, integral value 3U. The main subroutine subsequently computes $3^{301}$ with the specialized wide-integer, namespace-specific function pow, which is found via ADL.

The approximate result is

$$3^{301}~{\approx}~4.10674{\times}~10^{143}\text{.}$$

See also the following informative links to Wolfram Alpha(R).

  • Query the approximate value of $3^{301}$ with N[3^301]
  • Verify the exact value of $3^{301}$ with 3^301

This example, compiled with successful output result, is shown in its entirety in the following short link to godbolt.

In particular,

#include <iostream>

#include <math/wide_integer/uintwide_t.h>

using uint512_t = ::math::wide_integer::uintwide_t<512U, std::uint32_t>;

static const uint512_t x = 3U;

auto main() -> int
{
  // Compute x^301, i.e., 3^301.
  const auto p3 = pow(x, 301);

  // 410674437175765127973978082146264947899391086876012309414440570235106991532497229781400618467066824164751453321793982128440538198297087323698003
  std::cout << p3 << std::endl;
}

The code sequence above defines the local data type uint512_t with an alias. The first template parameter 512U sets the binary width (or bit count) while the second optional template parameter std::uint32_t sets the internal limb type. The limb type must be unsigned and one of std::uint8_t, std::uint16_t, std::uint32_t or on some systems std::uint64_t. If the second template parameter LimbType is left blank, the default limb type is thirty-two bits in width and unsigned.

The complete template signature of the uintwide_t class is shown below.

namespace math::wide_integer {

namespace detail { using size_t = std::uint32_t; }

using detail::size_t;

// Forward declaration of the uintwide_t template class.
template<const size_t Width2,
         typename LimbType      = std::uint32_t,
         typename AllocatorType = void,
         const bool IsSigned    = false>
class uintwide_t;

} // namespace math::wide_integer

uintwide_t also has a third optional template paramter that is used to set the allocator type employed for internal storage of the big integer's data. The default allocator type is void and uintwide_t uses stack allocation with an std::array-like internal representation. Setting the allocator type to an actual allocator such as, for instance, std::allocator<limb_type> activates allocator-based internal storage for uintwide_t. Using allocator-based storage reduces stack consumption and can be especially beneficial for higher digit counts. For low digit counts, the allocator type can simply be left blank (thus defaulting to void) or explicitly be set to void and stack allocation will be used in either case.

If an allocator is supplied with any granularity other than limb_type (in other words LimbType) such as std::allocator<void>, custom_allocator_type<char>, etc., then the uintwide_t class will internally rebind the allocator to the granularity and unsigned-ness of limb_type using rebind_alloc from std::allocator_traits.

The fourth template parameter IsSigned can be set to true to activate a signed integer type. If left blank, the default value of IsSigned is false and the integer type will be unsigned.

Examples

Various interesting and algorithmically challenging examples have been implemented. It is hoped that the examples provide inspiration and guidance on how to use wide-integer.

  • example000_numeric_limits.cpp verifies parts of the specializations of std::numeric_limits for (unsigned) uint256_tand (signed) int256_t.
  • example000a_builtin_convert.cpp exercises some conversions to/from built-in types/uintwide_t.
  • example001_mul_div.cpp performs multiplication and division.
  • example001a_div_mod.cpp exercises division and modulus calculations.
  • example002_shl_shr.cpp does a few left and right shift operations.
  • example003_sqrt.cpp computes a square root.
  • example003a_cbrt computes a cube root.
  • example004_rootk_pow.cpp computes an integral seventh root and its corresponding power. A negative-valued cube root is also tested.
  • example005_powm.cpp tests the power-modulus function powm.
  • example005a_pow_factors_of_p99.cpp verifies a beautiful, known prime factorization result from a classic tabulated value.
  • example006_gcd.cpp tests several computations of greatest common divisor using the gcd function.
  • example007_random_generator.cpp computes some large pseudo-random integers.
  • example008_miller_rabin_prime.cpp implements primality testing via Miller-Rabin.
  • example008a_miller_rabin_prime.cpp verifies Boost's interpretation of Miller-Rabin primality testing using uintwide_t-based types.
  • example009_timed_mul.cpp measures multiplication timings.
  • example009a_timed_mul_4_by_4.cpp also measures multiplication timings for the special case of wide integers having four limbs.
  • example009b_timed_mul_8_by_8.cpp measures, yet again, multiplication timings for the special case of wide integers having eight limbs.
  • example010_uint48_t.cpp verifies 48-bit integer caluclations.
  • example011_uint24_t.cpp performs calculations with 24-bits, which is definitely on the small side of the range of wide-integer.
  • example012_rsa_crypto.cpp performs cryptographic calculations with 2048-bits, exploring a standardized test case.
  • example013_ecdsa_sign_verify.cpp provides an intuitive view on elliptic-curve algebra, depicting a well-known cryptographic key-gen/sign/verify method.
  • example014_pi_spigot_wide.cpp calculates $10,001$ decimal digits of the mathematical constant $\pi$ using a uintwide_t-based template spigot algorithm.

Building

Build Status

Build Status

The recent status of building and executing the tests and examples in Continuous Integration (CI) is always shown in the Build Status banner. Additional banners from other syntax checks and builds may also be visible.

It is also possible, if desired, to build and execute the tests and examples using various different OS/compiler combinations.

Build with Microsoft Visual Studio

Building and running the tests and examples can be accomplished using the Microsoft VisualStudio solution workspace provided in wide_integer.sln, wide_integer_vs2022.sln, etc. The MSVC solution file(s) are located in the project's root directory.

Build with CMake

You can also build and run tests and examples from an empty directory using CMake. Follow the CMake pattern:

cmake /path/to/wide-integer
cmake --build .
ctest --verbose

Build on the *nix command line

Alternatively building the tests and examples with native GCC (i.e., on *nix) can be executed with a simple, but rather lengthy, command line entered manually from the command shell. Consider, for instance, building in Linux with GCC in the presence of unsigned __int128. Furthermore, the Boost.Multiprecision library is used for some examples and tests. In this build example, Boost is intended to be located in the made-up directory ../boost-root, which needs to be adapted according to the actual location of Boost. The command line below illustrates how to build all of the wide_integer tests and examples directly from the *nix command line.

cd wide_integer
g++                                         \
-finline-functions                          \
-finline-limit=32                           \
-march=native                               \
-mtune=native                               \
-O3                                         \
-Wall                                       \
-Wextra                                     \
-Wpedantic                                  \
-Wconversion                                \
-Wsign-conversion                           \
-Wno-maybe-uninitialized                    \
-Wno-cast-function-type                     \
-std=c++14                                  \
-DWIDE_INTEGER_HAS_LIMB_TYPE_UINT64         \
-I.                                         \
-I../boost-root                             \
-pthread                                    \
-lpthread                                   \
test/test.cpp                               \
test/test_uintwide_t_boost_backend.cpp      \
test/test_uintwide_t_edge_cases.cpp         \
test/test_uintwide_t_examples.cpp           \
test/test_uintwide_t_float_convert.cpp      \
test/test_uintwide_t_int_convert.cpp        \
test/test_uintwide_t_n_base.cpp             \
test/test_uintwide_t_n_binary_ops_base.cpp  \
test/test_uintwide_t_spot_values.cpp        \
examples/example000_numeric_limits.cpp      \
examples/example000a_builtin_convert.cpp    \
examples/example001_mul_div.cpp             \
examples/example001a_div_mod.cpp            \
examples/example002_shl_shr.cpp             \
examples/example003_sqrt.cpp                \
examples/example003a_cbrt.cpp               \
examples/example004_rootk_pow.cpp           \
examples/example005_powm.cpp                \
examples/example005a_pow_factors_of_p99     \
examples/example006_gcd.cpp                 \
examples/example007_random_generator.cpp    \
examples/example008_miller_rabin_prime.cpp  \
examples/example008a_miller_rabin_prime.cpp \
examples/example009_timed_mul.cpp           \
examples/example009a_timed_mul_4_by_4.cpp   \
examples/example009b_timed_mul_8_by_8.cpp   \
examples/example010_uint48_t.cpp            \
examples/example011_uint24_t.cpp            \
examples/example012_rsa_crypto.cpp          \
examples/example013_ecdsa_sign_verify.cpp   \
examples/example014_pi_spigot_wide.cpp      \
-o wide_integer.exe

Testing, CI and Quality Checks

Testing

Testing is definitely a big issue. A growing, supported test suite improves confidence in the library. It provides for tested, efficient functionality on the PC and workstation. The GitHub code is, as mentioned above, delivered with an affiliated MSVC project or a variety of other build/make options that use easy-to-understand subroutines called from main(). These exercise the various examples and the full suite of test cases.

If an issue is reported, reproduced and verified, an attempt is made to correct it without breaking any other code. Upon successful correction, specific test cases exercising the reported issue are usually added as part of the issue resolution process.

CI and Quality checks

CI runs on both push-to-branch as well as pull request using GitHub Actions. Various compilers, operating systems, and C++ standards ranging from C++14, 17, 20, 23 are included in CI.

In CI, we use both elevated GCC/clang compiler warnings as well as MSVC level 4 warnings active on the correspondoing platforms. For additional in-depth syntax checking, clang-tidy is used both in CI as well as in offline checks to improve static code quality.

GCC's run-time sanitizers are used in CI in order to help assure dynamic quality.

Additional quality checks are performed on pull-request and merge to master using modern third party open-source services. These include CodeQL, Synopsis Coverity, and CodeSonar. At the moment, the Coverity check is run with manual report submission. Automation of this is, however, planned.

Code coverage uses GCC/gcov/lcov and has a quality-gate with comparison/baseline-check provided by Codecov.

Quality badges are displayed at the top of this repository's readme page.

Detailed examples

We will now present various straightforward detailed examples.

The code below performs some elementary algebraic calculations with a simple mixture of 256-bit and 512-bit unsigned integral types.

This example, compiled with successful output result, is shown in its entirety in the following short link to godbolt.

#include <iomanip>
#include <iostream>

#include <math/wide_integer/uintwide_t.h>

auto main() -> int
{
  using uint256_t = ::math::wide_integer::uint256_t;
  using uint512_t = ::math::wide_integer::uint512_t;

  // Construction from string. Additional constructors
  // are available from other built-in types.

  const uint256_t a("0xF4DF741DE58BCB2F37F18372026EF9CBCFC456CB80AF54D53BDEED78410065DE");
  const uint256_t b("0x166D63E0202B3D90ECCEAA046341AB504658F55B974A7FD63733ECF89DD0DF75");

  // Elementary arithmetic operations.
  const uint512_t c = (uint512_t(a) * uint512_t(b));
  const uint256_t d = (a / b);

  // Logical comparison.
  const auto result_is_ok = (   (c == "0x1573D6A7CEA734D99865C4F428184983CDB018B80E9CC44B83C773FBE11993E7E491A360C57EB4306C61F9A04F7F7D99BE3676AAD2D71C5592D5AE70F84AF076")
                             && (d == "0xA"));

  // Print the hexadecimal representation string output.
  std::stringstream strm;

  strm << "0x" << std::hex << std::uppercase << c << '\n';
  strm << "0x" << std::hex << std::uppercase << d << '\n';

  // Visualize if the result is OK.

  strm << "result_is_ok: " << std::boolalpha << result_is_ok;

  std::cout << strm.str() << std::endl;
}

Wide-integer also supports a small selection of number-theoretical functions such as least and most significant bit, square root, $k^{th}$ root, power, power-modulus, greatest common denominator and random number generation. These functions are found via ADL.

The example below calculates an integer square root.

This example, compiled with successful output result, is shown in its entirety in the following short link to godbolt.

#include <iomanip>
#include <iostream>

#include <math/wide_integer/uintwide_t.h>

auto main() -> int
{
  using uint256_t = ::math::wide_integer::uint256_t;

  const uint256_t a("0xF4DF741DE58BCB2F37F18372026EF9CBCFC456CB80AF54D53BDEED78410065DE");

  const uint256_t s = sqrt(a);

  const auto result_is_ok = (s == "0xFA5FE7853F1D4AD92BDF244179CA178B");

  std::cout << "result_is_ok: " << std::boolalpha << result_is_ok << std::endl;
}

The following sample performs add, subtract, multiply and divide of uint48_t. See this example also in the following short link to godbolt.

#include <iomanip>
#include <iostream>
#include <random>

#include <math/wide_integer/uintwide_t.h>

auto main() -> int
{
  using uint48_t = ::math::wide_integer::uintwide_t<static_cast<math::wide_integer::size_t>(UINT32_C(48)), std::uint8_t>;

  using distribution_type  = ::math::wide_integer::uniform_int_distribution<static_cast<math::wide_integer::size_t>(UINT32_C(48)), typename uint48_t::limb_type>;

  using random_engine_type = std::linear_congruential_engine<std::uint32_t, UINT32_C(48271), UINT32_C(0), UINT32_C(2147483647)>;

  random_engine_type generator(static_cast<std::uint32_t>(UINT32_C(0xF00DCAFE))); // NOLINT(cert-msc32-c,cert-msc51-cpp,cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)

  distribution_type distribution;

  const auto a64 = static_cast<std::uint64_t>(distribution(generator));
  const auto b64 = static_cast<std::uint64_t>(distribution(generator));

  const uint48_t a(a64);
  const uint48_t b(b64);

  const uint48_t c_add = (a + b);
  const uint48_t c_sub = (a - b);
  const uint48_t c_mul = (a * b);
  const uint48_t c_div = (a / b);

  const auto result_is_ok = (   (   (c_add == static_cast<std::uint64_t>((a64 + b64) & static_cast<std::uint64_t>(UINT64_C(0x0000FFFFFFFFFFFF))))
                                 && (c_sub == static_cast<std::uint64_t>((a64 - b64) & static_cast<std::uint64_t>(UINT64_C(0x0000FFFFFFFFFFFF))))
                                 && (c_mul == static_cast<std::uint64_t>((a64 * b64) & static_cast<std::uint64_t>(UINT64_C(0x0000FFFFFFFFFFFF))))
                                 && (c_div == static_cast<std::uint64_t>((a64 / b64) & static_cast<std::uint64_t>(UINT64_C(0x0000FFFFFFFFFFFF)))))
                             &&
                                (   (static_cast<std::uint64_t>(c_add) == static_cast<std::uint64_t>((a64 + b64) & static_cast<std::uint64_t>(UINT64_C(0x0000FFFFFFFFFFFF))))
                                 && (static_cast<std::uint64_t>(c_sub) == static_cast<std::uint64_t>((a64 - b64) & static_cast<std::uint64_t>(UINT64_C(0x0000FFFFFFFFFFFF))))
                                 && (static_cast<std::uint64_t>(c_mul) == static_cast<std::uint64_t>((a64 * b64) & static_cast<std::uint64_t>(UINT64_C(0x0000FFFFFFFFFFFF))))
                                 && (static_cast<std::uint64_t>(c_div) == static_cast<std::uint64_t>((a64 / b64) & static_cast<std::uint64_t>(UINT64_C(0x0000FFFFFFFFFFFF))))));

  std::cout << "result_is_ok: " << std::boolalpha << result_is_ok << std::endl;
}

The next example computes the real-valued cube root of $10^{3,333}$. The real-valued cube root of this very large unsigned integer is $10^{1,111}$. We will use the (somewhat uncommon) integral data type uint11264_t. Since uint11264_t has approximately $3,390$ decimal digits of precision, it is large enough to hold the value of $10^{3,333}$ prior to (and following) the cube root operation.

See this example fully worked out at the following short link to godbolt

#include <iomanip>
#include <iostream>

#include <math/wide_integer/uintwide_t.h>

auto main() -> int
{
  using uint11264_t = ::math::wide_integer::uintwide_t<11264U, std::uint32_t>;

  // Create the string '1' + 3,333 times '0', which is
  // equivalent to the decimal integral value 10^3333.

  const std::string str_a = "1" + std::string(3333U, '0');

  const uint11264_t a = str_a.data();

  const uint11264_t s = cbrt(a);

  // Create the string '1' + 1,111 times '0', which is
  // equivalent to the decimal integral value 10^1111.
  // (This is the cube root of 10^3333.)

  const std::string str_control = "1" + std::string(1111U, '0');

  const auto result_is_ok = (s == uint11264_t(str_control.data()));

  std::cout << s << std::endl;

  std::cout << "result_is_ok: " << std::boolalpha << result_is_ok << std::endl;
}

Additional details

Wide-Integer has been tested with numerous compilers, for target systems ranging from eight to sixty-four bits. The library is specifically designed for efficiency with small to medium bit counts. Supported bit counts include integers $1 {\ldots} 63 {\times} 2^N$ while being $16$, $24$, $32$ or larger such as $256$, $384$, $512$, $768$, $1024$, or other less common bit counts such as $11,264$, etc.

Small, medium and large bit counts are supported. Common applications might use the range of uint128_t, uint256_t or uint512_t. It is also possible to make software-synthesized (not very efficient) versions of uint24_t, uint32_t or uint48_t, which might useful for hardware prototyping or other simulation and verification needs. On the high-digit end, Karatsuba multiplication extends the high performance range to many thousands of bits. Fast long division, however, relies on a classical algorithm and sub-quadratic high-precision division is not yet implemented.

Portability of the code is another key point of focus. Special care has been taken to test in certain high-performance embedded real-time programming environments.

Configuration macros (compile-time)

Various configuration features can optionally be enabled or disabled at compile time with the compiler switches:

#define WIDE_INTEGER_DISABLE_IOSTREAM
#define WIDE_INTEGER_DISABLE_TO_STRING
#define WIDE_INTEGER_DISABLE_FLOAT_INTEROP
#define WIDE_INTEGER_DISABLE_IMPLEMENT_UTIL_DYNAMIC_ARRAY
#define WIDE_INTEGER_HAS_LIMB_TYPE_UINT64
#define WIDE_INTEGER_HAS_MUL_8_BY_8_UNROLL
#define WIDE_INTEGER_DISABLE_TRIVIAL_COPY_AND_STD_LAYOUT_CHECKS
#define WIDE_INTEGER_NAMESPACE
#define WIDE_INTEGER_DISABLE_PRIVATE_CLASS_DATA_MEMBERS
#define WIDE_INTEGER_HAS_CLZ_LIMB_OPTIMIZATIONS

When working with even the most tiny microcontroller systems, I/O streaming can optionally be disabled with the compiler switch:

#define WIDE_INTEGER_DISABLE_IOSTREAM

The default setting is WIDE_INTEGER_DISABLE_IOSTREAM not set and I/O streaming operations are enabled.

Conversion to std::string is supported with the specialized wide-integer, namespace-specific function to_string. This analagous to the standard library's std::to_string function, but implemented specifically for instances of uintwide_t. Wide-integer's local, namespace-specific to_string function (and the inclusion of the necessary <string> header) are both deactivated with:

#define WIDE_INTEGER_DISABLE_TO_STRING

Interoperability with built-in floating-point types such as construct-from, cast-to, binary arithmetic with built-in floating-point types can be optionally disabled by defining:

#define WIDE_INTEGER_DISABLE_FLOAT_INTEROP

The default setting is WIDE_INTEGER_DISABLE_FLOAT_INTEROP not set and all available functions implementing construction-from, cast-to, binary arithmetic with built-in floating-point types are enabled.

#define WIDE_INTEGER_DISABLE_IMPLEMENT_UTIL_DYNAMIC_ARRAY

This macro disables uintwide_t.h's own local implementation of the util::dynamic_array template class. The logic of this macro is negated. Its default setting (of being disabled itself) ensures that standalone uintwide_t.h is free from any additional header dependencies.

The template utility class util::dynamic_array is used as a storage container for certain instantiations of uintwide_t. This macro is disabled by default and uintwide_t.h does actually provide its own local implementation of the util::dynamic_array template class. Otherwise, the header file <util/utility/util_dynamic_array.h> must be found in the include path.

When working on high-performance systems having unsigned __int128 (an extended-width, yet non-standard data type), a 64-bit limb of type uint64_t can be used. Enable the 64-bit limb type on such systems with the compiler switch:

#define WIDE_INTEGER_HAS_LIMB_TYPE_UINT64

or (when using GCC, clang or similar) on the compiler command line with:

-DWIDE_INTEGER_HAS_LIMB_TYPE_UINT64

This macro is disabled by default.

The example below, for instance, uses a 64-bit limb type on GCC or clang.

#define WIDE_INTEGER_HAS_LIMB_TYPE_UINT64

#include <math/wide_integer/uintwide_t.h>

using uint_fast256_t = ::math::wide_integer::uintwide_t<256U, std::uint64_t>;

static uint_fast256_t x = 42U;

Another potential optimization macro can be activated with:

#define WIDE_INTEGER_HAS_MUL_8_BY_8_UNROLL

This macro might improve performance on some target/compiler systems by manually unrolling the multiplication loop(s) for uintwide_t instances having eight limbs. This macro is disabled by default.

#define WIDE_INTEGER_DISABLE_TRIVIAL_COPY_AND_STD_LAYOUT_CHECKS

This macro disables compile-time checks for std::is_trivially_copyable and std::is_standard_layout. These checks provide assurance (among other attributes) that uintwide_t's constructors satisfy rules needed for mixed-language C/C++ usage. Some older legacy target/compiler systems might have non-standard or incomplete STL implementations that lack these compile-time templates. For such compilers, it makes sense to deactivate these compile-time checks via activation of this macro. This macro is disabled by default and both the trivially-copyable as well as the standard-layout compile-time checks are active.

#define WIDE_INTEGER_NAMESPACE something_unique

This is an advanced macro intended to be used in strict, exacting applications for which using the unqualified, global namespace math (i.e., namespace ::math) is undesired or inacceptable. We recall that all parts of the wide-integer implementation, such as the uintwide_t class and its associated implementation details reside within namespace ::math::wide_integer

Defining the macro WIDE_INTEGER_NAMESPACE to be something like, for instance,

-DWIDE_INTEGER_NAMESPACE=something_unique

places all parts of the wide-integer implementation and its details within the prepended outer namespace something_unique - as in

namespace something_unique::math::wide_integer
{
  // ...
}

When utilizing the WIDE_INTEGER_NAMESPACE option, the actual name or nesting depth of the desired prepended outer namespace can be varied if (or as) needed for the particular project.

By default the macro WIDE_INTEGER_NAMESPACE is not defined. In this default state, namespace ::math::wide_integer is used and the uintwide_t class and its associated implementation details reside therein.

#define WIDE_INTEGER_DISABLE_PRIVATE_CLASS_DATA_MEMBERS

This optional macro can be used to switch uintwide_t's data member access from private to public. This allows the uintwide_t class to be used as a so-called structured class, such as is needed for constant-valued template parameters in a constexpr context. This preprocessor switch was invented based on the discussion in issue 335

Making private data members public is unusual for some designs. So the preprocessor switch WIDE_INTEGER_DISABLE_PRIVATE_CLASS_DATA_MEMBERS is not defined (i.e., not set) by default. This ensures that uintwide_t's data members remain private by default.

#define WIDE_INTEGER_HAS_CLZ_LIMB_OPTIMIZATIONS

This optional macro activates certain optimizations that count leading zero-limbs prior to classical quadratic multiplication. This may offer performance advantages on some systems by avoiding some potentially costly zero-valued limb-multiplication steps.

This preprocessor switch was motivated by the discussion in issue 362

By default, the preprocessor switch WIDE_INTEGER_HAS_CLZ_LIMB_OPTIMIZATIONS is not defined and CLZ-limb optimizations are default-disabled.

C++14, 17, 20, 23 and beyond constexpr support

uintwide_t supports C++14, 17, 20, 23 and beyond compile-time constexpr-ness for all constructions, casts, operations, evaluation of function results, etc.

The code below, for instance, shows compile-time instantiations of uintwide_t from character strings with subsequent constexpr evaluations of binary operations multiply, divide, intergal cast and comparison.

See this example fully worked out at the following short link to godbolt. The generated assembly includes nothing other than the call to main() and its subsequent return of the value zero (i.e., main()'s successful return-value in this example).

#include <math/wide_integer/uintwide_t.h>

using uint256_t = ::math::wide_integer::uintwide_t<256U>;
using uint512_t = ::math::wide_integer::uintwide_t<512U>;

// Compile-time construction from string.
constexpr auto a = uint256_t("0xF4DF741DE58BCB2F37F18372026EF9CBCFC456CB80AF54D53BDEED78410065DE");
constexpr auto b = uint256_t("0x166D63E0202B3D90ECCEAA046341AB504658F55B974A7FD63733ECF89DD0DF75");

// Compile time binary arithmetic operations.
constexpr auto c = uint512_t(a) * uint512_t(b);
constexpr auto d = uint256_t(a / b);

// Compile-time comparison.
constexpr auto result_is_ok = (   (c == "0x1573D6A7CEA734D99865C4F428184983CDB018B80E9CC44B83C773FBE11993E7E491A360C57EB4306C61F9A04F7F7D99BE3676AAD2D71C5592D5AE70F84AF076")
                               && (static_cast<std::uint_fast8_t>(d) == static_cast<std::uint_fast8_t>(UINT8_C(10))));

// constexpr verification.
static_assert(result_is_ok, "Error: example is not OK!");

auto main() -> int
{
  return (result_is_ok ? 0 : -1);
}

Signed integer support

Signed big integers are also supported in the wide_integer library. Use the fourth template partameter IsSigned to indicate the signed-ness (or unsigned-ness) of uintwide_t. The code below, for instance, uses an aliased version of signed int256_t.

#include <math/wide_integer/uintwide_t.h>

using int256_t = ::math::wide_integer::uintwide_t<256U, std::uint32_t, void, true>;

const int256_t n1(-3);
const int256_t n2(-3);

// 9
const int256_t n3 = n1 * n2;

Negative arguments in number theoretical functions

The following design choices have been implemented when handling negative arguments in number theoretical functions.

  • Right shift by n bits via operator>>(n) performs a so-called arithmetic right shift (ASHR). For signed integers having negative value, right-shift continually fills the sign bit with 1 while shifting right. The result is similar to signed division and closely mimics common compiler behavior for right-shift of negative-valued built-in signed int.
  • sqrt of x negative returns zero.
  • cbrt of x nexative integer returns -cbrt(-x).
  • $k^{th}$ root of x negative returns zero unless the cube root is being computed, in which case -cbrt(-x) is returned.
  • GCD and LCM of a, b signed convert both arguments to positive and negate the result for a, b having opposite signs.
  • Miller-Rabin primality testing treats negative inetegers as positive when testing for prime, thus extending the set of primes to negative integers.
  • MSB/LSB (most/least significant bit) do not differentiate between positive or negative argument such that MSB of a negative integer will be the highest bit of the corresponding unsigned type.
  • Printing both positive-valued and negative-valued signed integers in hexadecimal format is supported. When printing negative-valued, signed uintwide_t in hexadecimal format, the sign bit and all other bits are treated as if the integer were unsigned. The negative sign is not explicitly shown when using hexadecimal format, even if the underlying integer is signed and negative-valued. A potential positive sign, however, will be shown for positive-valued signed integers in hexadecimal form in the presence of std::showpos.
  • Signed integer division and modulus results obtained from the divmod() function follow established number-theoretical rounding conventions, which are the same as those used by the //-operator in Python-3 (i.e., the same as Python-3's built-in divmod() function). These conventions also match those used by Mathematica(R)'s QuotientRemainder[] function.

Further details

Notable construction/conversion rules

The following notable construction/conversion rules have been implemented in the wide-integer project.

  • Constructions-from built-in types are implicit. These are considered widening conversions.
  • Casts to built-in types are explicit and considered narrowing, regardless of the widths of left-and-right hand sides of the conversion.
  • All of both constructions-from as well as casts-to wider/less-wide and signed/unsigned wide-integer types are implicit (even if the conversion at hand is narrowing via having fewer bits). Casts such as int128_t to/from uint160_t and similar, for instance, are implicit.
  • All wide-integer-types are move constructable.
  • All wide-integer types having the same widths and having the same limb-type (but possibly different sign) are move-assignable and std::move()-capable.

Importing and exporting characters and bits

For sufficiently modern standards-conforming compilers, namespace-specific functions to_chars() and from_chars() are available. These each have the usual <charconv>-like behavior, known from C++17. For motivational words on to_chars() and from_chars(), see also issue 153 and issue 398.

Support for importing and exporting bits is granted by the subroutines import_bits() and export_bits(). Their interfaces, input/output forms and constraints are intended to be identical with those used in Boost's import/export-bits functions.

Alternatives and limitations

Alternative libraries for big integral types include, among others, most notably GMP and Boost.Multiprecision.

At the moment, the digit range of wide-integer is limited to the granularity of the full limb type. This means that less-common bit counts requiring the use of non-full limbs are not supported. It is not possible with this library, for instance, to synthesize, let's say, a 61-bit integral type.

This can have performance impact. If you would like to synthesize an 80-bit integral type, for example, this can be done, but at the cost of using five 16-bit limbs. This degrades performance due to the higher limb count. This phenomenon was discussed in issue 234

wide-integer's People

Contributors

ckormanyos avatar imahjoub avatar johnmcfarlane avatar vaivaswatha avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

wide-integer's Issues

Is a question?

uint128_t n("12312452345");
std::cout << std::boolalpha;
std::cout << std::is_integral<uint128_t>::value << '\n';

output : false

Negative shifts

The shift operators should recurse on the negative count, e.g.

-      if     (n <  0) { operator>>=(n); }
+      if     (n <  0) { operator>>=(-n); }

Faster division?

Division uses Knuth long division routine.
Is it possible to implement a faster division scheme?

Fix -Wconversion warnings?

This isn't essential. It's just a suggestion. I probably shouldn't even be seeing warnings from that file. (I must have forgotten to use -isystem instead of -I.)

Here's some example output from g++ -Wconversion:

../external/wide-integer/math/wide_integer/uintwide_t.h:768:95: error: conversion from ‘uint_fast32_t’ {aka ‘long unsigned int’} to ‘unsigned int’ may change value [-Werror=conversion]
  768 |     static_assert(   (detail::verify_power_of_two_times_granularity_one_sixty_fourth<my_width2>::conditional_value == true)
      |                                                                                               ^
../external/wide-integer/math/wide_integer/uintwide_t.h:786:52: error: conversion from ‘long unsigned int’ to ‘unsigned int’ may change value [-Werror=conversion]
  786 |     using double_width_type = uintwide_t<my_width2 * 2U, limb_type>;
      |                                          ~~~~~~~~~~^~~~
../external/wide-integer/math/wide_integer/uintwide_t.h:1865:99: error: conversion from ‘long unsigned int’ to ‘unsigned int’ may change value [-Werror=conversion]
 1865 |              typename std::enable_if<(uintwide_t<RePhraseWidth2, LimbType, AllocatorType, IsSigned>::number_of_limbs == 4U)>::type const* = nullptr>
      |                                                                                                   ^
../external/wide-integer/math/wide_integer/uintwide_t.h:2249:93: error: conversion from ‘long unsigned int’ to ‘unsigned int’ may change value [-Werror=conversion]
 2249 |              typename std::enable_if<(   (uintwide_t<RePhraseWidth2, LimbType, AllocatorType>::number_of_limbs != 4U)
      |                                                                                             ^
../external/wide-integer/math/wide_integer/uintwide_t.h: In member function ‘bool math::wide_integer::uintwide_t<Width2, LimbType, AllocatorType, IsSigned>::wr_string(char*, uint_fast8_t, bool, bool, bool, uint_fast32_t, char) const’:
../external/wide-integer/math/wide_integer/uintwide_t.h:1421:66: error: conversion from ‘uint_fast32_t’ {aka ‘long unsigned int’} to ‘unsigned int’ may change value [-Werror=conversion]
 1421 |             uintwide_t<my_width2, limb_type, AllocatorType, false> tu(t);
      |                                                                  ^

It does seem like uint_fast32_t is 64 bits on my system which upsets the compiler when it gets converted to uint32_t. I don't think there's very much reason at all to be choosing the _fast variant of <cstdint> types in template parameters. I would highly recommend coming up with an alias, perhaps

namespace math::wide_integer {
  using size_t = std::int64_t;  // if you'd rather unsigned, I understand
}

which you then apply pretty-much everywhere you have a constexpr variable or a template parameter. Not only would this avoid a lot of the conversion errors (which are daft because the compiler knows where any overflow occurs here) but you can change your mind about exactly what the type is without huge churn later on.

Thoughts?

Long division broken on AVR 8-bit

Long division is at the moment broken on the AVR 8-bit target. The error is in an unknown location in the Knuth division algorithm and the benchmark test fails on the embedded controller. Other platforms such as PC and ARM do not seem to be influenced by this issue.

Fix or explain why example007 breaks CI

Example007 breaks CI and the strict testing of the randomly generated result is not used in verification. This is a workaround.

The point of this issue is to try to understand why Example007 behaves differently on CI server. Is there an issue with byte ordering? Another possibility might be misunderstanding of an initialized creation of an instance of a standard random generator.

Handle negative arguments in functions like cbrt, GCD, prime

Handle negative arguments in functions like cbrt, GCD, prime, for instance cbrt of a negative number should work, whereas k'th root should not (except for cube root). Need to decide what to do with primality testing and GCD of signed integers, where Boost behavior could potentially be used for guidance here.

Optimize unsigned integer sqrt

Try to optimize unsigned integer square root, for instance, with the algorithm SqrtRem known from the MPFR author's literature.

Remove PCG random and use <random> instead

The use of a specialized version of PCG seems non-intuitive and cluttered within the context of modern C++ programming in this particular lasss library. It will probably be better for this design to simply use available engines and adapters from standard instead.

uint24_t missing (removed)

Support for uint24_t is missing because it has been removed. The reason is because digit checks for higher digit counts disallow 24-bit type because the half-width type plays such an important role in the implementation. And there is no valid half-width type with 12 bits.

At the moment, there is no plan to restore support for the 24-bit type.

Clean up trivial thread snizize issues in tests

Some trivial thread sanitize issues are found in the test routines. These should be cleaned up and eliminated. There do not seem to be any difficult issues, just some forgotten sync objects needed.

Breaking Change Announcement April 2021

Breaking Change Announcement April 2021

Name changes of header file and namespace are planned in April 2021.
The reason for the changes is to provide better compatibility
with the wide-decimal project.

  • Change name of header file generic_template_uintwide_t to uintwide_t.h.
  • Change name of namespace wide_integer::generic_template to math::wide_integer.
  • No backward compatibility measures are planned at the moment.

Continuous testing

I was wondering though: have you thought of gating PRs on passing tests? (The practice is mentioned here). To do this, I think you'd have to change the YML file like this and make a change to the master branch in the project settings.

  • The downside of this is that you cannot merge the code until all of the tests have passed, which slow down merges of error-free changes.
  • The upside of this is that you cannot merge the code until all of the tests have passed, which blocks merges of failing changes.

Even if you don't block merges, it would still be helpful for contributors and reviewers to be able to see their PRs pass tests publicly. If you agree, I'd be happy to submit a PR.

Consider optional allocator type template parameter

Some applications may want to use an allocator instead of local or stack storage for individual uintwide_t instances. This could be facilitated by supporting an optional allocator type template parameter which defaults to void or void* when no allocator is provided. In case of allocated storage, a simple fixed-width container can be used.

Conversion from non-limb types

The following doesn't compile:

auto input{math::wide_integer::uintwide_t<320, unsigned, void, true>{1729348762983LL}};

The reasons are to do with the c'tors in uintwide_t and detail::fixed_static_array. I couldn't get the the bottom of them but certainly, it's too easy for a c'tor in fixed_static_array to be chosen during overload resolution. I would recommend far fewer constructors, following the reasoning given here.

A uintwide_t isn't an array, or a string, or a fixed_static_array either, so it shouldn't be constructible from these things. You couldn't do that with int (unless you're making some terrible mistake caused by lax C conversion rules)!

Allowing a number to be initialised by any numeric type is a good idea to aim for. In ascending order of difficulty / descending order of priority:

  • std::is_integral
  • std::is_arithmetic
  • std::numeric_limits::is_specialized

Another bit of advice, try not to be too 'helpful' by providing convenience functions (especially c'tors) which aren't essential. A good class has the fewest member functions possible. Try and aim for the opposite of std::string, which has tonnes of member functions and is a poorly-designed class.

Limb size & speedups

I appreciate the speed this library brings - it's very close to the speed of boost's multiprecision library for basic arithmetic.

I am trying to speed up uint256_t and uint512_t arithmetic in any possible way. I see that uint256_t and uint512_t are defined using 32-bit limbs. Is there anything prohibiting the use of 64-bit limbs instead?

I get a bunch of compile errors when I manually try to set the limb type to uint64_t. They seem to be template checks for the most part along with overflow warnings, but I don't know the code well enough to understand if we can work around them or not:

include/math/wide_integer/uintwide_t.h: In instantiation of ‘struct math::wide_integer::detail::uint_type_helper<128, void>’:
include/math/wide_integer/uintwide_t.h:637:125:   required from ‘class math::wide_integer::uintwide_t<256, long unsigned int>’
test_main.cpp:13:39:   required from here
include/math/wide_integer/uintwide_t.h:527:5: error: static assertion failed: Error: uint_type_helper is not intended to be used for this BitCount
     static_assert((   ((BitCount >= 8U) && (BitCount <= 64U))
     ^~~~~~~~~~~~~
include/math/wide_integer/uintwide_t.h: In instantiation of ‘class math::wide_integer::uintwide_t<256, long unsigned int>’:
test_main.cpp:13:39:   required from here
include/math/wide_integer/uintwide_t.h:645:5: error: static assertion failed: Error: Please check the characteristics of the template parameters ST and LT
     static_assert((    (std::numeric_limits<limb_type>::is_integer        == true)
     ^~~~~~~~~~~~~
include/math/wide_integer/uintwide_t.h: In instantiation of ‘class math::wide_integer::uintwide_t<512, long unsigned int, void>’:
test_main.cpp:15:56:   recursively required by substitution of ‘template<class UnknownUnsignedWideIntegralType, class> math::wide_integer::uintwide_t<256, long unsigned int>::operator math::wide_integer::uintwide_t<256, long unsigned int>::double_width_type<UnknownUnsignedWideIntegralType, <template-parameter-1-2> >() const [with UnknownUnsignedWideIntegralType = <missing>; <template-parameter-1-2> = <missing>]’
test_main.cpp:15:56:   required from here
include/math/wide_integer/uintwide_t.h:645:5: error: static assertion failed: Error: Please check the characteristics of the template parameters ST and LT
include/math/wide_integer/uintwide_t.h: In instantiation of ‘void math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::eval_divide_knuth(const math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>&, math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>*) [with long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void]’:
include/math/wide_integer/uintwide_t.h:984:26:   required from ‘math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>& math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::operator/=(const math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>&) [with long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void]’
test_main.cpp:16:58:   required from here
include/math/wide_integer/uintwide_t.h:2029:80: warning: left shift count >= width of type [-Wshift-count-overflow]
  limb_type(double_limb_type(  double_limb_type(double_limb_type(1U) << std::numeric_limits<limb_type>::digits)
                                                ~~~~~~~~~~~~~~~~~~~~~^~~~~~
include/math/wide_integer/uintwide_t.h:2075:76: warning: left shift count >= width of type [-Wshift-count-overflow]
      const double_limb_type      u_j_j1 = (double_limb_type(uu[uj]) << std::numeric_limits<limb_type>::digits) + uu[uj - 1U];
                                           ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/math/wide_integer/uintwide_t.h:2089:61: warning: left shift count >= width of type [-Wshift-count-overflow]
                      <= double_limb_type(double_limb_type(t << std::numeric_limits<limb_type>::digits) + uu[uj - 2U])))
                                                           ~~^~~~~~
include/math/wide_integer/uintwide_t.h:2145:84: warning: left shift count >= width of type [-Wshift-count-overflow]
                     + double_limb_type(double_limb_type(previous_u) << std::numeric_limits<limb_type>::digits));
                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~
include/math/wide_integer/uintwide_t.h: In instantiation of ‘void math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::eval_divide_by_single_limb(math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::limb_type, uint_fast32_t, math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>*) [with long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void; math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::limb_type = long unsigned int; uint_fast32_t = long unsigned int]’:
include/math/wide_integer/uintwide_t.h:2021:37:   required from ‘void math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::eval_divide_knuth(const math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>&, math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>*) [with long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void]’
include/math/wide_integer/uintwide_t.h:984:26:   required from ‘math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>& math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::operator/=(const math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>&) [with long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void]’
test_main.cpp:16:58:   required from here
include/math/wide_integer/uintwide_t.h:1432:97: warning: left shift count >= width of type [-Wshift-count-overflow]
  - double_limb_type(double_limb_type(short_denominator) * hi_part)) << std::numeric_limits<limb_type>::digits);
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/math/wide_integer/uintwide_t.h:1442:141: warning: left shift count >= width of type [-Wshift-count-overflow]
  - double_limb_type(double_limb_type(short_denominator) * hi_part)) << std::numeric_limits<limb_type>::digits);
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/math/wide_integer/uintwide_t.h:1444:47: warning: right shift count >= width of type [-Wshift-count-overflow]
         *remainder = limb_type(long_numerator >> std::numeric_limits<limb_type>::digits);
                                ~~~~~~~~~~~~~~~^~~~~~
include/math/wide_integer/uintwide_t.h: In instantiation of ‘ST math::wide_integer::detail::make_hi(const LT&) [with ST = long unsigned int; LT = long unsigned int]’:
include/math/wide_integer/uintwide_t.h:2087:48:   required from ‘void math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::eval_divide_knuth(const math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>&, math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>*) [with long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void]’
include/math/wide_integer/uintwide_t.h:984:26:   required from ‘math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>& math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::operator/=(const math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>&) [with long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void]’
test_main.cpp:16:58:   required from here
include/math/wide_integer/uintwide_t.h:591:5: error: static assertion failed: Error: Please check the characteristics of the template parameters ST and LT
     static_assert((    (std::numeric_limits<local_ushort_type>::is_integer == true)
     ^~~~~~~~~~~~~
include/math/wide_integer/uintwide_t.h:598:45: warning: right shift count >= width of type [-Wshift-count-overflow]
     return static_cast<local_ushort_type>(u >> std::numeric_limits<local_ushort_type>::digits);
                                           ~~^~~~~~
include/math/wide_integer/uintwide_t.h: In instantiation of ‘ST math::wide_integer::detail::make_lo(const LT&) [with ST = long unsigned int; LT = long unsigned int]’:
include/math/wide_integer/uintwide_t.h:1617:60:   required from ‘static void math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::eval_multiply_n_by_n_to_lo_part(math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::limb_type*, const limb_type*, const limb_type*, uint_fast32_t) [with long unsigned int RePhraseDigits2 = 256; const typename std::enable_if<((std::numeric_limits<LT>::digits * 4) == RePhraseDigits2)>::type* <anonymous> = 0; long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void; math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::limb_type = long unsigned int; uint_fast32_t = long unsigned int]’
include/math/wide_integer/uintwide_t.h:1495:38:   required from ‘static void math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::eval_mul_unary(math::wide_integer::uintwide_t<OtherDigits2, LimbType, AllocatorType>&, const math::wide_integer::uintwide_t<OtherDigits2, LimbType, AllocatorType>&, typename std::enable_if<((OtherDigits2 / std::numeric_limits<LT>::digits) < math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::number_of_limbs_karatsuba_threshold)>::type*) [with long unsigned int OtherDigits2 = 256; long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void; typename std::enable_if<((OtherDigits2 / std::numeric_limits<LT>::digits) < math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::number_of_limbs_karatsuba_threshold)>::type = void]’
include/math/wide_integer/uintwide_t.h:946:23:   required from ‘math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>& math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>::operator*=(const math::wide_integer::uintwide_t<Digits2, LimbType, AllocatorType>&) [with long unsigned int Digits2 = 256; LimbType = long unsigned int; AllocatorType = void]’
test_main.cpp:15:56:   required from here
include/math/wide_integer/uintwide_t.h:569:5: error: static assertion failed: Error: Please check the characteristics of the template parameters ST and LT
     static_assert((    (std::numeric_limits<local_ushort_type>::is_integer == true)
     ^~~~~~~~~~~~~

The reason I ask is that one of the goals of the library is to run on embedded systems, many of which I presume are 32-bit systems. I am running a 64-bit system and would like to know if speed gains are possible using full 64-bit limbs. It looks like Boost uses 32-bit limbs, too, so maybe there's a constraint I don't understand.

Other speedup tips would be very much appreciated, too.

Set up CI

Set up continuous integration preliminarily use Ubuntu/GCC with libboost.

Construct-from float types constexpr

If is_iec559, then it should be possible to write a local constexpr set of floating-point dissection functions allowing for constexpr construct from built-in floating-poin types.

This is a refinement of #47. See also #77 which achieved constexprcast-to built-in floating-poin types.

Location of public APIs

I've started writing a Conan recipe in order to cleanly integrate wide-integer into CNL. The plumbing and the conclusions I've come up with are hopefully applicable beyond CNL's concerns so I though I'd share the following observations/suggestions:

  • There are two public include directories (the thing that GCC receives as a header search path with options like -I or -isystem):
    • ./math/wide-integer/, and
    • ./util/utility/.
  • There are two public global namespaces:
    • util, and
    • math/wide_integer.
  • Two of the four headers exposed in public include directories are only necessary for testing, not integration with dependent packages:
    • uintwide_t_examples.h, and
    • uintwide_t_test.h.

In the CMake or Conan scripts, it is possible to shuffle some of the files around in order to omit the test headers from the install destination. However, because of this line

#include <util/utility/util_dynamic_array.h>

two separate install directories are needed and neither -- at their root -- do very much to indicate that they contain headers specific to the wide-integer library. The namespaces suffer from a similar problem: they are one or two levels deep and given very open-ended names at their root: 'util' and 'math'.

Like namespaces, directories (under usr/include at least) are not for creating taxonomies. (That's a great video to watch generally but that nugget of advice translates very well to C++.) By using 'util' and 'math', you're more likely to risk collisions and it's less likely for users to be able to find and remember where you library is on their system. So please consider some minor rearrangement to your source files and their location -- even if you don't go with the following suggestions...

My recommended changes (which are just one possible solution and which I'd be happy to submit in a PR) are to:

  • move test-only headers out of the public search path;
  • decide on a namespace and a directory for the library (e.g. ::wide_integer and /wide-integer/);
  • move public library headers together and away from the rest of the project files, e.g.:
    • ./include/wide-integer/uintwide_t.h, and
    • ./include/wide-integer/dynamic_array.h;
  • move the public definitions into this top-level namespace, e.g. ::wide_integer::uintwide_t;
  • put non-public (but header-exposed) definitions in a detail sub-namespace, e.g. ::wide_integer::detail;
  • move util::dynamic_array and its comparison operators to the detail sub-namespace.

I suggest this with virtually no understanding of:

  • the other libraries that might share dynamic_array (there are ways to keep it hidden in detail for wide-integer but inject it into another, public namespace elsewhere), and
  • the users of wide-integer who would be disrupted by this change. A less disruptive change is entirely possible but you may find that they appreciate the added clarity -- especially if it is accompanied by build and package management facilities which are simple and idiomatic.

Toom Cook multiplication

Implement (at least) Toom-Cook3 and Toom-Cook4 and possibly additional other order(s) for speed-up of multiplication when the bit count is many thousands of bits.

Optimize Karatsuba Mul possible?

Karatsuba multiplication has been implemented.
There are separate steps for complementing negative subtraction results.
Can these be eliminated via use of alternate ordering in Kara Multiplication algorithm?

Relax most constraints on binary digit count

Relax most of the constraints on the binary digit count allowed in uintwide_t. Add support for bit counts that also contain fractional parts of a limb such as uint99_t, etc. To be decided is storage left-shifted to MSB or resting on the LSB.

Use Boost develop branch in CI

The Boost distros on the runners are quite old. This issue is about using develop branch (in particular Boost.Math and Boost.Multiprecision) on CI runners.

Examples of doing this are in the wide-decimal repository.

literal types

(This may be a dupe of #31 but types are not literals, i.e. not usable in constant expressions. It's also brought up in #10 but it's a separate concern from signedness so I thought it might be good to organise the threads into two.)

I took a quick look and things like std::fill and std::array may pose a problem in earlier language revisions, or even C++20. Would you be OK with abandoning C++11. Not necessary but very helpful because then you can keep variables and loops.

And would you consider abandoning C++14 and C++17? Even less necessary but still would reduce the amount of change necessary to achieve constexpr.

digits now means width

When a type is unsigned, you can get away with using these terms interchangeably. Now, I'd caution against continuing to use the term 'digits' to mean number of binary digits in the storage. Not only is it inconsistent with CNL, but more importantly it doesn't match numeric limits.

The giveaway that this is problematic is the definition of numeric_limits_uintwide_t_base::digits where clearly this word is being used in to different ways within the project.

I'd recommend either

  1. changing the first tparam from Digits2 to Width2 or Bits, or
  2. using the value to mean something different when type is unsigned.

If you choose (1), I'd consider this issue to be low priority; you're just changing a name. But if you choose the second option, you'll be breaking the API of your signed type going forward so you might want to do this sooner.

Reduce amount of Git cloning in CI

Reduce amount of Git cloning in CI, which should be possible with the reduced dependencies of Math and (to a lesser extent) Multiprecision in develop branch.

Try support 64-bit limb type

Some high-performance clients would like to try for speedup with 64-bit native limb_type, where this is available from compier/hardware, etc.

Add construct from/cast to built in float types

From standardization perspective, we will definitely need construction from and cast to built-in floating-point types float, double and long double.

Implement these. Along the way it might be necessary to resolve more clearly some of the existing (but not quite complete set) of casts to built in integral types.

Reduce amount of Boost develop branch used in CI

The Boost multiprecision (and if needed math) dependencies have been reduced as of Boost 1.76. The amount of Boost develop branch needed for CI builds and runs can be significantly reduced.

Add more test cases and strive for higher test coverage

A few basic functions are tested in the test suite. But not yet all functions are tested and not yet all border cases are included in tests. In this issue, we plan to add more test cases and strive for higher test coverage. Boost.Test is expected to be used, optionally in combination with GCOV and/or LCOV.

GCC with -fsanitize=address indicates problem

When using GCC with -fsanitize=address, there is indication of a problem. It seems like it might stem from the multiplication routine. it arises when the multithreaded add test begins, but not in the tests of the examples.

This is a preliminary report. Not very much is known of this issue and it seems to not influence numerical correctness. Nonetheless, address sanitation does seem to pick up a clear problem indication.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.