diff --git a/doc/decimal.adoc b/doc/decimal.adoc index b5e0dd5cb..19e79a83e 100644 --- a/doc/decimal.adoc +++ b/doc/decimal.adoc @@ -18,6 +18,7 @@ https://www.boost.org/LICENSE_1_0.txt Matt Borland and Chris Kormanyos include::decimal/overview.adoc[] +include::decimal/basics.adoc[] include::decimal/api_reference.adoc[] include::decimal/generic_decimal.adoc[] include::decimal/decimal32.adoc[] diff --git a/doc/decimal/basics.adoc b/doc/decimal/basics.adoc new file mode 100644 index 000000000..f501157b2 --- /dev/null +++ b/doc/decimal/basics.adoc @@ -0,0 +1,85 @@ +//// +Copyright 2025 Matt Borland +Distributed under the Boost Software License, Version 1.0. +https://www.boost.org/LICENSE_1_0.txt +//// + +[#basics] += Basic Usage +:idprefix: basics_ + +== Construction of Decimal Types + +Every decimal type can be constructed in a few ways: + +1) The parameters to the constructor are like so: + +[source, c++] +---- +template +constexpr decimal32(T coeff, T2 exp, bool sign = false) noexcept; +---- + +Where types `T` and `T2` are integral types (signed and unsigned are both allowed). +Lastly the sign follows the convention of `signbit` where `false` is positive and `true` is negative. +If both a negative coefficient and a sign are passed then the resulting decimal number will be negative. +The final number constructed is in the form (sign || coeff < 0 ? -1 : 1) x abs(coeff) x 10^exp. + +[souce, c++] +---- +boost::decimal::decimal32 a {1, 0}; // constructs 1 +boost::decimal::decimal32 b {-2, 0}; // constructs -2 +boost::decimal::decimal32 c {2, 0, true}; // Also constructs -2 +boost::decimal::decimal32 d {-2, 0, true}; // Also constructs -2 +boost::decimal::decimal32 e {5, 5}; // constructs 5x10^5 +boost::decimal::decimal32 f {1234, -3} // constructs 1.234 or 1234x10^-3 +---- + +2) A decimal number can be explicitly or implicitly constructed from an integer. +For example: + +[source, c++] +---- +boost::decimal::decimal64 g = 1; +boost::decimal::decimal32 h {-4}; +---- + +3) A decimal number can only be explicitly constructed from a floating point type. +For example: + +[source, c++] +---- +boost::decimal::decimal128 pi {3.14}; +---- + +NOTE: Due to the differences in decimal and binary floating point numbers there may be a difference in the resulting representation in decimal format, and thus it is not recommended to construct from binary floating point numbers + +== Using the Library + +The entire library should be accessed using the convenience header ``. +A short example of the basic usage: + +[source, c++] +---- +#include +#include +#include + +int main() +{ + using namespace boost::decimal; + + // Outputs 0.30000000000000004 + std::cout << std::setprecision(17) << 0.1 + 0.2 << std::endl; + + // Construct the two decimal values + constexpr decimal64 a {1, -1}; // 1e-1 or 0.1 + constexpr decimal64 b {2, -1}; // 2e-1 or 0.2 + + // Outputs 0.30000000000000000 + std::cout << a + b << std::endl; + + return 0; +} + +---- diff --git a/doc/decimal/charconv.adoc b/doc/decimal/charconv.adoc index 2ae869620..9d1d83e4f 100644 --- a/doc/decimal/charconv.adoc +++ b/doc/decimal/charconv.adoc @@ -143,6 +143,8 @@ BOOST_DECIMAL_CONSTEXPR std::to_chars_result to_chars(char* first, char* last, D } //namespace boost ---- +All `to_chars` functions ignore the effects of cohorts, and instead output normalized values. + NOTE: `BOOST_DECIMAL_CONSTEXPR` is defined if: - `_MSC_FULL_VER` >= 192528326 diff --git a/doc/decimal/cmath.adoc b/doc/decimal/cmath.adoc index b874fb8c1..bc8ac8b20 100644 --- a/doc/decimal/cmath.adoc +++ b/doc/decimal/cmath.adoc @@ -413,7 +413,7 @@ constexpr int quantexp128(decimal128 x) noexcept; Effects: if x is finite, returns its quantum exponent. -Otherwise, a domain error occurs and `INT_MIN` is returned. +Otherwise, `INT_MIN` is returned. === quantized diff --git a/doc/decimal/decimal128.adoc b/doc/decimal/decimal128.adoc index 9555352c2..6b1950a5b 100644 --- a/doc/decimal/decimal128.adoc +++ b/doc/decimal/decimal128.adoc @@ -28,6 +28,8 @@ The encoding of Decimal128 is in the <>. namespace boost { namespace decimal { +class decimal128 { + // Paragraph numbers are from ISO/IEC DTR 24733 // 3.2.4.1 construct/copy/destroy @@ -85,6 +87,8 @@ explicit constexpr operator std::bfloat16_t() const noexcept; explicit constexpr operator decimal32() const noexcept; explicit constexpr operator decimal64() const noexcept; +}; // class decimal128 + } //namespace decimal } //namespace boost diff --git a/doc/decimal/decimal128_fast.adoc b/doc/decimal/decimal128_fast.adoc index 8a24add07..8b0496831 100644 --- a/doc/decimal/decimal128_fast.adoc +++ b/doc/decimal/decimal128_fast.adoc @@ -28,6 +28,8 @@ As is often the case this trades space for time by having greater storage width namespace boost { namespace decimal { +class decimal128_fast { + // Paragraph numbers are from ISO/IEC DTR 24733 // 3.2.4.1 construct/copy/destroy @@ -85,6 +87,8 @@ explicit constexpr operator std::bfloat16_t() const noexcept; explicit constexpr operator decimal32() const noexcept; explicit constexpr operator decimal64() const noexcept; +}; // class decimal128_fast + } //namespace decimal } //namespace boost diff --git a/doc/decimal/decimal32.adoc b/doc/decimal/decimal32.adoc index 9cb9ede38..7c006b8f5 100644 --- a/doc/decimal/decimal32.adoc +++ b/doc/decimal/decimal32.adoc @@ -28,6 +28,8 @@ The encoding of Decimal32 is in the <>. namespace boost { namespace decimal { +class decimal32 { + // Paragraph numbers are from ISO/IEC DTR 24733 // 3.2.2.1 construct/copy/destroy @@ -85,6 +87,8 @@ explicit constexpr operator std::bfloat16_t() const noexcept; explicit constexpr operator decimal64() const noexcept; explicit constexpr operator decimal128() const noexcept; +}; // class decimal32 + } //namespace decimal } //namespace boost diff --git a/doc/decimal/decimal32_fast.adoc b/doc/decimal/decimal32_fast.adoc index c1e8af44e..f98ed93b1 100644 --- a/doc/decimal/decimal32_fast.adoc +++ b/doc/decimal/decimal32_fast.adoc @@ -28,6 +28,8 @@ As is often the case this trades space for time by having greater storage width namespace boost { namespace decimal { +class decimal32_fast { + // Paragraph numbers are from ISO/IEC DTR 24733 // 3.2.2.1 construct/copy/destroy @@ -85,6 +87,8 @@ explicit constexpr operator std::bfloat16_t() const noexcept; explicit constexpr operator decimal64() const noexcept; explicit constexpr operator decimal128() const noexcept; +}; // class decimal32_fast + } //namespace decimal } //namespace boost diff --git a/doc/decimal/decimal64.adoc b/doc/decimal/decimal64.adoc index 390ca2f8c..59a48619c 100644 --- a/doc/decimal/decimal64.adoc +++ b/doc/decimal/decimal64.adoc @@ -28,6 +28,8 @@ The encoding of Decimal64 is in the <>. namespace boost { namespace decimal { +class decimal64 { + // Paragraph numbers are from ISO/IEC DTR 24733 // 3.2.3.1 construct/copy/destroy @@ -85,6 +87,8 @@ explicit constexpr operator std::bfloat16_t() const noexcept; explicit constexpr operator decimal32() const noexcept; explicit constexpr operator decimal128() const noexcept; +}; // class decimal64 + } //namespace decimal } //namespace boost diff --git a/doc/decimal/decimal64_fast.adoc b/doc/decimal/decimal64_fast.adoc index 99919ded3..d7bf2efb4 100644 --- a/doc/decimal/decimal64_fast.adoc +++ b/doc/decimal/decimal64_fast.adoc @@ -28,6 +28,8 @@ As is often the case this trades space for time by having greater storage width namespace boost { namespace decimal { +class decimal64_fast { + // Paragraph numbers are from ISO/IEC DTR 24733 // 3.2.3.1 construct/copy/destroy @@ -85,6 +87,8 @@ explicit constexpr operator std::bfloat16_t() const noexcept; explicit constexpr operator decimal32() const noexcept; explicit constexpr operator decimal128() const noexcept; +}; // class decimal64_fast + } //namespace decimal } //namespace boost diff --git a/doc/decimal/overview.adoc b/doc/decimal/overview.adoc index f27c017ad..95f44d3e7 100644 --- a/doc/decimal/overview.adoc +++ b/doc/decimal/overview.adoc @@ -40,34 +40,3 @@ as well as emulated PPC64LE and STM32 using QEMU with the following compilers: Tested on https://github.com/cppalliance/decimal/actions[Github Actions] and https://drone.cpp.al/cppalliance/decimal[Drone]. Coverage can be found on https://app.codecov.io/gh/cppalliance/decimal[Codecov]. - -== Basic Usage - -The entire library should be accessed using the convince header ``. -A short example of the basic usage: - -[source, c++] ----- -#include -#include -#include - -int main() -{ - using namespace boost::decimal; - - // Outputs 0.30000000000000004 - std::cout << std::setprecision(17) << 0.1 + 0.2; - - // Construct the two decimal values - constexpr decimal64 a {1, -1}; // 1e-1 or 0.1 - constexpr decimal64 b {2, -1}; // 2e-1 or 0.2 - - // Outputs 0.30000000000000000 - std::cout << a + b << std::endl; - - return 0; -} - ----- - diff --git a/include/boost/decimal/charconv.hpp b/include/boost/decimal/charconv.hpp index cc17272ce..3ec1c90d2 100644 --- a/include/boost/decimal/charconv.hpp +++ b/include/boost/decimal/charconv.hpp @@ -542,16 +542,16 @@ BOOST_DECIMAL_CONSTEXPR auto to_chars_fixed_impl(char* first, char* last, const { append_trailing_zeros = true; } + } - // In general formatting we remove trailing 0s - if (fmt == chars_format::general) - { - - const auto zeros_removal {remove_trailing_zeros(significand)}; - significand = zeros_removal.trimmed_number; - exponent += static_cast(zeros_removal.number_of_removed_zeros); - num_dig -= static_cast(zeros_removal.number_of_removed_zeros); - } + // In general formatting we remove trailing 0s + // Same with unspecified precision fixed formatting + if ((precision == -1 && fmt == chars_format::fixed) || fmt == chars_format::general) + { + const auto zeros_removal {remove_trailing_zeros(significand)}; + significand = zeros_removal.trimmed_number; + exponent += static_cast(zeros_removal.number_of_removed_zeros); + num_dig -= static_cast(zeros_removal.number_of_removed_zeros); } // Make sure the result will fit in the buffer @@ -569,25 +569,32 @@ BOOST_DECIMAL_CONSTEXPR auto to_chars_fixed_impl(char* first, char* last, const *first++ = '0'; return {first, std::errc()}; } - else if (num_leading_zeros > precision) + else if (precision != -1 && num_leading_zeros > precision) { *first++ = '0'; *first++ = '.'; - std::memset(first, '0', static_cast(precision)); + boost::decimal::detail::memset(first, '0', static_cast(precision)); return {first + precision, std::errc()}; } else { *first++ = '0'; *first++ = '.'; - std::memset(first, '0', static_cast(num_leading_zeros)); + boost::decimal::detail::memset(first, '0', static_cast(num_leading_zeros)); first += num_leading_zeros; // We can skip the rest if there's nothing more to do for the required precision if (significand == 0) { - std::memset(first, '0', static_cast(precision - num_leading_zeros)); - return {first + precision, std::errc()}; + if (precision - num_leading_zeros > 0) + { + boost::decimal::detail::memset(first, '0', static_cast(precision - num_leading_zeros)); + return {first + precision, std::errc()}; + } + else + { + return {first, std::errc()}; + } } } } diff --git a/include/boost/decimal/detail/memcpy.hpp b/include/boost/decimal/detail/memcpy.hpp index 49f19e198..bfd8edd27 100644 --- a/include/boost/decimal/detail/memcpy.hpp +++ b/include/boost/decimal/detail/memcpy.hpp @@ -23,6 +23,7 @@ #if defined(__GNUC__) && __GNUC__ >= 10 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wstringop-overflow" +# pragma GCC diagnostic ignored "-Warray-bounds" # define BOOST_DECIMAL_STRINGOP_OVERFLOW_DISABLED #endif diff --git a/test/Jamfile b/test/Jamfile index d67baeb4f..763173f66 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -45,6 +45,7 @@ project : requirements run-fail benchmarks.cpp ; run compare_dec128_and_fast.cpp ; compile-fail concepts_test.cpp ; +run crash_report_1.cpp ; run github_issue_426.cpp ; run github_issue_448.cpp ; run-fail github_issue_519.cpp ; diff --git a/test/crash_report_1.cpp b/test/crash_report_1.cpp new file mode 100644 index 000000000..bc8f4b162 --- /dev/null +++ b/test/crash_report_1.cpp @@ -0,0 +1,27 @@ +// Copyright 2025 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +using namespace boost::decimal; + +int main() +{ + char buffer[64]{}; + const auto print_val {0.000001_df}; + + const auto r = boost::decimal::to_chars( + buffer, + buffer + sizeof(buffer), + print_val, + boost::decimal::chars_format::fixed + ); + *r.ptr = '\0'; + + BOOST_TEST_CSTR_EQ(buffer, "0.000001"); + + return boost::report_errors(); +} diff --git a/test/test_to_chars.cpp b/test/test_to_chars.cpp index 88eb681ab..ac60bf8b2 100644 --- a/test/test_to_chars.cpp +++ b/test/test_to_chars.cpp @@ -23,7 +23,7 @@ static constexpr auto N = static_cast(1024U >> 4U); // Number of tr #if !defined(BOOST_DECIMAL_DISABLE_CLIB) && !(defined(__GNUC__) && __GNUC__ >= 13 && !defined(__aarch64__)) template -void test_value(T val, const char* result, chars_format fmt, int precision = -1) +void test_value(T val, const char* result, chars_format fmt, int precision) { char buffer[256] {}; auto r = to_chars(buffer, buffer + sizeof(buffer), val, fmt, precision); @@ -32,6 +32,16 @@ void test_value(T val, const char* result, chars_format fmt, int precision = -1) BOOST_TEST_CSTR_EQ(result, buffer); } +template +void test_value(T val, const char* result, chars_format fmt) +{ + char buffer[256] {}; + auto r = to_chars(buffer, buffer + sizeof(buffer), val, fmt); + *r.ptr = '\0'; + BOOST_TEST(r); + BOOST_TEST_CSTR_EQ(result, buffer); +} + template void test_value(T val, const char* result) { @@ -764,6 +774,25 @@ void test_777() test_value(value3, "-2111000000", chars_format::fixed, 0); } +template +void test_more_powers_10() +{ + test_value(T{1, -6}, "0.000001", chars_format::fixed); + test_value(T{1, -5}, "0.00001", chars_format::fixed); + test_value(T{1, -4}, "0.0001", chars_format::fixed); + test_value(T{1, -3}, "0.001", chars_format::fixed); + test_value(T{1, -2}, "0.01", chars_format::fixed); + test_value(T{1, -1}, "0.1", chars_format::fixed); + test_value(T{1, 0}, "1", chars_format::fixed); + test_value(T{1, 1}, "10", chars_format::fixed); + test_value(T{1, 2}, "100", chars_format::fixed); + test_value(T{1, 3}, "1000", chars_format::fixed); + test_value(T{1, 4}, "10000", chars_format::fixed); + test_value(T{1, 5}, "100000", chars_format::fixed); + test_value(T{1, 6}, "1000000", chars_format::fixed); + test_value(T{1, 7}, "10000000", chars_format::fixed); +} + int main() { test_non_finite_values(); @@ -874,6 +903,10 @@ int main() test_777(); test_777(); + test_more_powers_10(); + test_more_powers_10(); + test_more_powers_10(); + return boost::report_errors(); }