Code Monkey home page Code Monkey logo

wide-decimal's Introduction

Wide-decimal

Build Status Language grade: C/C++ Total alerts Coverity Scan Quality Gate Status Boost Software License 1.0 GitHub code size in bytes

Wide-decimal implements a generic C++ template for extended precision decimal float types.

This C++ template header-only library implements drop-in big decimal float types, such as dec51_t, dec101_t, dec1001_t, dec10001_t, dec1000001_t, etc., that can be used essentially like regular built-in floating-point types. Wide-decimal supports decimal float types having digit counts ranging roughly from about .

Wide-decimal implements both common algebraic operations as well as a few common <cmath>-like functions such as fabs, sqrt and log, and also includes full support for std::numeric_limits.

Wide-decimal is written in header-only C++11, and compatible through C++11, 14, 17, 20.

Implementation goals

  • Wide precision range up to one million decimal digits
  • Moderately good efficiency over the entire wide precision range
  • Clean header-only C++11 design
  • Seamless portability to any modern C++11, 14, 17, 20 compiler
  • Scalability with small memory footprint and efficiency suitable for bare-metal embedded systems

Quick start

Easy application follows via a traditional C-style typedef or C++11 alias. The defined type can be used very much like a built-in floating-point type.

The following sample, for instance, defines and uses a decimal type called dec101_t having decimal digits of precision. The subroutine do_something() initializes the variable d of type dec101_t with 1/3 and subsequently prints the 101 digit value of d to the console.

In particular,

#include <iomanip>
#include <iostream>

#include <math/wide_decimal/decwide_t.h>

void do_something()
{
  using dec101_t = math::wide_decimal::decwide_t<INT32_C(101), std::uint32_t, void>;

  dec101_t d = dec101_t(1) / 3;

  // 0.33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
  std::cout << std::setprecision(std::numeric_limits<dec101_t>::digits) << d << std::endl;
}

The local data type dec101_t is defined with a C++11 alias. The first template parameter INT32_C(101) sets the decimal digit count while the second optional template parameter std::uint32_t sets the internal limb type. If the second template parameter is left blank, the default limb type is 32 bits in width and unsigned.

The template signature of the decwide_t class is shown below.

template<const std::int32_t ParamDigitsBaseTen,
         typename LimbType = std::uint32_t,
         typename AllocatorType = std::allocator<void>,
         typename InternalFloatType = double,
         typename ExponentType = std::int64_t,
         typename FftFloatType = double>
  class decwide_t;

decwide_t also has a third (and a few more) optional template paramter(s). The third template parameter can be used to set the allocator type for internal storage of the wide decimal type. The default allocator type is std::allocator<limb_type> which helps to reduce stack consumption, especially when using higher digit counts. If low digit counts are used, the allocator type can be explicitly set to void and stack allocation is used with an array-like internal representation.

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-decimal.

  • example000_multiply_nines.cpp performa a check of shoolbook multiplication using a small digit range and small 8-bit limb.
  • example000a_multiply_pi_squared.cpp performs a hard-coded multiplication check resulting in .
  • example001_roots_sqrt.cpp computes a square root.
  • example001a_roots_seventh.cpp computes a seventh root.
  • example001b_roots_almost_integer.cpp uses roots and algebraic operations to compute decimal digits of a fascinating Pisot number that is almost integer.
  • example001c_roots_sqrt_limb08.cpp computes a square root with a wide decimal representation having 8-bit limbs.
  • example001d_pow2_from_list.cpp verifies a list of values with .
  • example002_pi.cpp calculates decimal digits of using a Gauss AGM iteration.
  • example002a_pi_small_limb.cpp calculates decimal digits of using a 16-bit internal limb type.
  • example002b_pi_100k.cpp calculates decimal digits of .
  • example002c_pi_quintic.cpp calculates decimal digits of using a Borwein quintic iteration.
  • example002d_pi_limb08.cpp calculates yet again decimal digits of using an 8-bit internal limb type and float internal floating-point type.
  • example003_zeta.cpp computes a Riemann zeta function value.
  • example004_bessel_recur.cpp implements cylindrical Bessel functions of integral order via downward recursion with a Neumann sum.
  • example005_polylog_series.cpp performs a small-argument polylogarithm series calculation.
  • example006_logarithm.cpp calculates the value of a logarithm (internally using a Gauss AGM method).
  • example007_catalan_series.cpp computes decimal digits of Catalan's constant using an accelerated series.
  • example008_bernoulli_tgamma.cpp implements tgamma(x) using Stirling's asymptotic expansion of the logarithm of the Gamma function with Bernoulli numbers and subsequently calculates decimal digits of for small integer .
  • example009_boost_math_standalone.cpp checks basic compatibility of standalone decwide_t with Boost.Math by testing a cube root value obtained from boost::math::cbrt.
  • example009a_boost_math_standalone.cpp also checks standalone decwide_t with significantly more of Boost.Math by testing a digit generalized Legendre function value (using boost::math::tgamma and more to do so).
  • example009b_boost_math_standalone.cpp checks yet again standalone decwide_t with Boost.Math's available boost::math::tgamma function for small-ish decimal floats having decimal digits.
  • example010_hypergeometric_2f1.cpp calculates a decimal digit hypergeometric function value using an iterative rational approximation scheme.
  • example010a_hypergeometric_1f1.cpp calculates another decimal digit hypergeometric function in a similar fashion.
  • example011_trig_trapezoid_integral.cpp uses trapezoid integration with an integral representation involving locally-written trigonometric sine and cosine functions to compute several cylindrical Bessel function values.
  • example012_rational_floor_ceil.cpp verifies the proper representation of a wide selection of small-valued, pure integral rational quotients.
  • example013_embeddable_sqrt.cpp and example013a_embeddable_agm.cpp exercise calculations that also run on tiny bare-metal embedded systems, featuring a digit square root calculation and a digit AGM iteration for .

Testing and CI

Testing is a big issue and a growing test suite is in continued progress providing for tested, efficient functionality on the PC and workstation. The GitHub code is delivered with an affiliated MSVC project that uses easy-to-understand subroutines called from main() that exercise various test cases.

Continuous integration runs on push using GitHub Actions. Various compilers, operating systems, and C++ standards ranging from C++11, 14, 17, 20 are included in CI.

Build Status

Build Status

Details

Wide-Decimal has been tested with numerous compilers for target systems ranging from 8 to 64 bits. The library is specifically designed for modest efficiency (not the world's fastest) over the entire range of small to large digit counts. How efficient is the code? Based on a very general comparison, a million digit AGM calculation of is about five times slower than an equivalent calculation performed with a big float data type based on GMP.

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.

When working with even the most tiny microcontroller systems, various heavy-wieght features such as I/O streaming, dynamic memory allocation, construction from character string and caching constant values such as and can optionally be disabled with the compiler switches:

#define WIDE_DECIMAL_DISABLE_IOSTREAM
#define WIDE_DECIMAL_DISABLE_DYNAMIC_MEMORY_ALLOCATION
#define WIDE_DECIMAL_DISABLE_CONSTRUCT_FROM_STRING
#define WIDE_DECIMAL_DISABLE_CACHED_CONSTANTS
#define WIDE_DECIMAL_DISABLE_USE_STD_FUNCTION
#define WIDE_DECIMAL_NAMESPACE

Each one of these compiler switches has an intuitive name intended to represent its meaning.

Note: Activating the option WIDE_DECIMAL_DISABLE_DYNAMIC_MEMORY_ALLOCATION simultaneously disallows using decwide_t in a multithreaded application. So if PC-based or other kinds of multithreading are used, then dynamic memory allocation is needed and can not be disabled. In other words,

// Activate/Deactivate the disable of dynamic memory.
// For most multithreaded PC work, comment out or remove
// this line entirely (i.e., thereby enable dynamic memory).

#define WIDE_DECIMAL_DISABLE_DYNAMIC_MEMORY_ALLOCATION

Let's consider also the macro WIDE_DECIMAL_NAMESPACE in greater detail.

#define WIDE_DECIMAL_NAMESPACE

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-decimal implementation, such as the decwide_t class and its associated implementation details reside within namespace ::math::wide_decimal

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

-DWIDE_DECIMAL_NAMESPACE=something_unique

places all parts of the wide-decimal's uintwide_t template class implementation and its associated details within the prepended outer namespace something_unique --- as in

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

When utilizing the WIDE_DECIMAL_NAMESPACE option, vary the actual name or nesting depth of the desired prepended outer namespace if/as needed for your particular project.

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

Detailed examples

The example below calculates the square root of the decimal representation of , the result of which is approximately .

#include <cstdint>
#include <iomanip>
#include <iostream>

#include <math/wide_decimal/decwide_t.h>

auto main() -> int
{
  using local_limb_type = std::uint16_t;

  using dec101_t = math::wide_decimal::decwide_t<INT32_C(101), local_limb_type, void>;

  const dec101_t s = sqrt(dec101_t(123456U) / 100);

  // N[Sqrt[123456/100], 101]
  const dec101_t control
  {
    "35.136306009596398663933384640418055759751518287169314528165976164717710895452890928635031219132220978"
  };

  const dec101_t closeness = fabs(1 - (s / control));

  const auto result_is_ok = (closeness < (std::numeric_limits<dec101_t>::epsilon() * 10));

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

In the following code, we compute (one million and one) decimal digits of the fundamental constant . The truncated (non-rounded) expected result is . In this particular example, all heavy-weight components are deactivated and this particular calculation is, in fact, suitable for a bare-metal mega-digit pi calculation.

In this example, note how a specialized custom allocator called util::n_slot_array_allocator is utilized for exact, efficient memory management of a certain number of temporary storages of mega-digit numbers (tuned to 18 in this particular example).

#include <iomanip>
#include <iostream>

#define WIDE_DECIMAL_DISABLE_IOSTREAM
#define WIDE_DECIMAL_DISABLE_DYNAMIC_MEMORY_ALLOCATION
#define WIDE_DECIMAL_DISABLE_CONSTRUCT_FROM_STRING
#define WIDE_DECIMAL_DISABLE_CACHED_CONSTANTS

#include <math/wide_decimal/decwide_t.h>
#include <util/memory/util_n_slot_array_allocator.h>

auto main() -> int
{
  using local_limb_type = std::uint32_t;

  constexpr std::int32_t wide_decimal_digits10 = INT32_C(1000001);

  constexpr std::int32_t local_elem_number =
    math::wide_decimal::detail::decwide_t_helper<wide_decimal_digits10, local_limb_type>::elem_number;

  constexpr std::int32_t local_elem_digits10 =
    math::wide_decimal::detail::decwide_t_helper<wide_decimal_digits10, local_limb_type>::elem_digits10;

  using local_allocator_type = util::n_slot_array_allocator<void, local_elem_number, 18U>;

  using local_wide_decimal_type =
    math::wide_decimal::decwide_t<wide_decimal_digits10, local_limb_type, local_allocator_type, double>;

  const auto start = std::clock();

  const local_wide_decimal_type my_pi =
    math::wide_decimal::pi<wide_decimal_digits10, local_limb_type, local_allocator_type, double>(nullptr);

  const auto stop = std::clock();

  std::cout << "Time example002_pi(): "
            << (float) (stop - start) / (float) CLOCKS_PER_SEC
            << std::endl;

  const auto head_is_ok = std::equal(my_pi.crepresentation().cbegin(),
                                     my_pi.crepresentation().cbegin() + math::constants::const_pi_control_head_32.size(),
                                     math::constants::const_pi_control_head_32.begin());

  using const_iterator_type = typename local_wide_decimal_type::array_type::const_iterator;

  const_iterator_type
    fi
    (
        my_pi.crepresentation().cbegin()
      + static_cast<std::uint32_t>
        (
            static_cast<std::uint32_t>(1UL + ((wide_decimal_digits10 - 1UL) / local_elem_digits10))
          - static_cast<std::uint32_t>(math::constants::const_pi_control_tail_32_1000001.size())
        )
    );

  const auto tail_is_ok = std::equal(fi,
                                     fi + math::constants::const_pi_control_tail_32_1000001.size(),
                                          math::constants::const_pi_control_tail_32_1000001.begin());

  const auto result_is_ok = (head_is_ok && tail_is_ok);

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

The million digit run is comparatively slow and requires approximately 10 seconds on a modern PC. Considering, however, the design goals of header-only and capable of running on bare-metal, this is a very nice calculational result.

1,000,001 digits of pi on a bare metal microcontroller

The wide-decimal float back end is used to compute decimal digits of the mathematical constant on selected bare-metal OS-less microcontroller systems in pi-crunch-metal

wide-decimal's People

Contributors

ckormanyos avatar

Watchers

 avatar

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.