Skip to content
Merged
71 changes: 58 additions & 13 deletions .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,34 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
"g++-14-multilib",
),

linux_pipeline(
"Linux 26.04 GCC 15 32",
"cppalliance/droneubuntu2604:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-15', CXXSTD: '03,11,14,17,20,23', ADDRMD: '32', CXXFLAGS: "-fexcess-precision=fast" },
"g++-15-multilib",
),

linux_pipeline(
"Linux 26.04 GCC 15 64",
"cppalliance/droneubuntu2604:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-15', CXXSTD: '03,11,14,17,20,23', ADDRMD: '64', CXXFLAGS: "-fexcess-precision=fast" },
"g++-15-multilib",
),

linux_pipeline(
"Linux 26.04 GCC 16 32",
"cppalliance/droneubuntu2604:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-16', CXXSTD: '03,11,14,17,20,23', ADDRMD: '32', CXXFLAGS: "-fexcess-precision=fast" },
"g++-16-multilib",
),

linux_pipeline(
"Linux 26.04 GCC 16 64",
"cppalliance/droneubuntu2604:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-16', CXXSTD: '03,11,14,17,20,23', ADDRMD: '64', CXXFLAGS: "-fexcess-precision=fast" },
"g++-16-multilib",
),

linux_pipeline(
"Linux 18.04 Clang 6.0",
"cppalliance/droneubuntu1804:1",
Expand Down Expand Up @@ -316,16 +344,9 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
),

linux_pipeline(
"Linux 22.04 Clang 14 UBSAN",
"Linux 22.04 Clang 14",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20,2b' } + ubsan,
"clang-14",
),

linux_pipeline(
"Linux 22.04 Clang 14 ASAN",
"cppalliance/droneubuntu2204:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20,2b' } + asan,
{ TOOLSET: 'clang', COMPILER: 'clang++-14', CXXSTD: '03,11,14,17,20,2b' },
"clang-14",
),

Expand All @@ -350,31 +371,55 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-17', CXXSTD: '03,11,14,17,20,2b' },
"clang-17",
["deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main"],
["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-17 main"],
),

linux_pipeline(
"Linux 24.04 Clang 18",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-18', CXXSTD: '03,11,14,17,20,2b' },
"clang-18",
["deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main"],
["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-18 main"],
),

linux_pipeline(
"Linux 24.04 Clang 19",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-19', CXXSTD: '03,11,14,17,20,2b' },
"clang-19",
["deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main"],
["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main"],
),

linux_pipeline(
"Linux 24.04 Clang 20",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-20', CXXSTD: '03,11,14,17,20,2b' },
"clang-20",
["deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-20 main"],
["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main"],
),

linux_pipeline(
"Linux 24.04 Clang 21",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-21', CXXSTD: '14,17,20,2b' },
"clang-21",
["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main"],
),

linux_pipeline(
"Linux 24.04 Clang 21 UBSAN",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-21', CXXSTD: '14,17,20,2b' } + ubsan,
"clang-21",
["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main"],
),

linux_pipeline(
"Linux 24.04 Clang 21 ASAN",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-21', CXXSTD: '14,17,20,2b' } + asan,
"clang-21",
["deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main"],
),

windows_pipeline(
Expand Down
2 changes: 1 addition & 1 deletion include/boost/decimal/charconv.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,7 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto to_chars_cohort_preserving_scientific(char* fi
using unsigned_integer = typename TargetDecimalType::significand_type;

const auto fp = fpclassify(value);
if (!(fp == FP_NORMAL || fp == FP_SUBNORMAL))
if (!(fp == FP_NORMAL || fp == FP_SUBNORMAL || fp == FP_ZERO))
{
// Cohorts are irrelevant for non-finite values
return to_chars_nonfinite(first, last, value, fp, chars_format::scientific, -1);
Expand Down
12 changes: 12 additions & 0 deletions include/boost/decimal/decimal128_t.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,18 @@ BOOST_DECIMAL_CUDA_CONSTEXPR decimal128_t::decimal128_t(T1 coeff, T2 exp, const

if (reduced_coeff == zero)
{
// IEEE 754-2008 3.5.1: zero has a cohort with one representation per exponent.
// Clamp the requested exponent to the representable range and encode it; sign was already set.
auto zero_biased_exp {biased_exp};
if (zero_biased_exp < 0)
{
zero_biased_exp = 0;
}
else if (zero_biased_exp > static_cast<int>(detail::d128_max_biased_exponent))
{
zero_biased_exp = static_cast<int>(detail::d128_max_biased_exponent);
}
bits_.high |= (static_cast<std::uint64_t>(zero_biased_exp) << detail::d128_not_11_exp_high_word_shift) & detail::d128_not_11_exp_mask;
return;
}

Expand Down
13 changes: 12 additions & 1 deletion include/boost/decimal/decimal32_t.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,18 @@ BOOST_DECIMAL_CUDA_CONSTEXPR decimal32_t::decimal32_t(T1 coeff, T2 exp, const de

if (reduced_coeff == 0U)
{
// Normalize our handling of zeros
// IEEE 754-2008 3.5.1: zero has a cohort with one representation per exponent.
// Clamp the requested exponent to the representable range and encode it; sign was already set.
auto zero_biased_exp {biased_exp};
if (zero_biased_exp < 0)
{
zero_biased_exp = 0;
}
else if (zero_biased_exp > static_cast<int>(detail::d32_max_biased_exponent))
{
zero_biased_exp = static_cast<int>(detail::d32_max_biased_exponent);
}
bits_ |= (static_cast<std::uint32_t>(zero_biased_exp) << detail::d32_not_11_exp_shift) & detail::d32_not_11_exp_mask;
return;
}

Expand Down
13 changes: 12 additions & 1 deletion include/boost/decimal/decimal64_t.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,18 @@ BOOST_DECIMAL_CUDA_CONSTEXPR decimal64_t::decimal64_t(T1 coeff, T2 exp, const de

if (reduced_coeff == 0U)
{
// Normalize our handling of zeros
// IEEE 754-2008 3.5.1: zero has a cohort with one representation per exponent.
// Clamp the requested exponent to the representable range and encode it; sign was already set.
auto zero_biased_exp {biased_exp};
if (zero_biased_exp < 0)
{
zero_biased_exp = 0;
}
else if (zero_biased_exp > static_cast<int>(detail::d64_max_biased_exponent))
{
zero_biased_exp = static_cast<int>(detail::d64_max_biased_exponent);
}
bits_ |= (static_cast<std::uint64_t>(zero_biased_exp) << detail::d64_not_11_exp_shift) & detail::d64_not_11_exp_mask;
return;
}

Expand Down
26 changes: 22 additions & 4 deletions include/boost/decimal/decimal_fast128_t.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,13 +522,32 @@ constexpr decimal_fast128_t::decimal_fast128_t(T1 coeff, T2 exp, const detail::c
const auto is_negative {static_cast<bool>(resultant_sign)};
sign_ = is_negative;

// IEEE 754-2008 3.5.1: zero has a cohort with one representation per exponent.
// Skip normalization for zero (which would otherwise expand the significand and shift the exponent)
// and clamp the requested exponent to the representable range.
if (min_coeff == minimum_coefficient_size{0})
{
significand_ = static_cast<significand_type>(0);
auto biased_exp {static_cast<int>(exp) + detail::bias_v<decimal_fast128_t>};
if (biased_exp < 0)
{
biased_exp = 0;
}
else if (biased_exp > detail::max_biased_exp_v<decimal_fast128_t>)
{
biased_exp = detail::max_biased_exp_v<decimal_fast128_t>;
}
exponent_ = static_cast<exponent_type>(biased_exp);
return;
}

// Normalize the significand in the constructor, so we don't have
// to calculate the number of digits for operations
detail::normalize<decimal_fast128_t>(min_coeff, exp, is_negative);

significand_ = static_cast<significand_type>(min_coeff);

const auto biased_exp {significand_ == 0U ? 0 : exp + detail::bias_v<decimal_fast128_t>};
const auto biased_exp {static_cast<int>(exp) + detail::bias_v<decimal_fast128_t>};

if (biased_exp > detail::max_biased_exp_v<decimal_fast128_t>)
{
Expand All @@ -540,10 +559,9 @@ constexpr decimal_fast128_t::decimal_fast128_t(T1 coeff, T2 exp, const detail::c
}
else
{
// Flush denorms to zero
// Flush denorms to zero, preserving sign per IEEE 754-2008 3.5.1
significand_ = static_cast<significand_type>(0);
exponent_ = static_cast<exponent_type>(detail::bias_v<decimal_fast128_t>);
sign_ = false;
exponent_ = static_cast<exponent_type>(0);
}
}

Expand Down
26 changes: 22 additions & 4 deletions include/boost/decimal/decimal_fast32_t.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,12 +516,31 @@ constexpr decimal_fast32_t::decimal_fast32_t(T1 coeff, T2 exp, const detail::con
const auto is_negative {static_cast<bool>(resultant_sign)};
sign_ = is_negative;

// IEEE 754-2008 3.5.1: zero has a cohort with one representation per exponent.
// Skip normalization for zero (which would otherwise expand the significand and shift the exponent)
// and clamp the requested exponent to the representable range.
if (min_coeff == minimum_coefficient_size{0})
{
significand_ = static_cast<significand_type>(0);
auto biased_exp {static_cast<int>(exp) + detail::bias};
if (biased_exp < 0)
{
biased_exp = 0;
}
else if (biased_exp > detail::max_biased_exp_v<decimal_fast32_t>)
{
biased_exp = detail::max_biased_exp_v<decimal_fast32_t>;
}
exponent_ = static_cast<exponent_type>(biased_exp);
return;
}

// Normalize in the constructor, so we never have to worry about it again
detail::normalize<decimal_fast32_t>(min_coeff, exp, is_negative);

significand_ = static_cast<significand_type>(min_coeff);

const auto biased_exp {significand_ == 0U ? 0 : exp + detail::bias};
const auto biased_exp {static_cast<int>(exp) + detail::bias};

// decimal32_t exponent holds 8 bits
if (biased_exp > detail::max_biased_exp_v<decimal_fast32_t>)
Expand All @@ -534,10 +553,9 @@ constexpr decimal_fast32_t::decimal_fast32_t(T1 coeff, T2 exp, const detail::con
}
else
{
// Flush denorms to zero
// Flush denorms to zero, preserving sign per IEEE 754-2008 3.5.1
significand_ = static_cast<significand_type>(0);
exponent_ = static_cast<exponent_type>(detail::bias);
sign_ = false;
exponent_ = static_cast<exponent_type>(0);
}
}

Expand Down
26 changes: 22 additions & 4 deletions include/boost/decimal/decimal_fast64_t.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,12 +525,31 @@ constexpr decimal_fast64_t::decimal_fast64_t(T1 coeff, T2 exp, const detail::con
const auto is_negative {static_cast<bool>(resultant_sign)};
sign_ = is_negative;

// IEEE 754-2008 3.5.1: zero has a cohort with one representation per exponent.
// Skip normalization for zero (which would otherwise expand the significand and shift the exponent)
// and clamp the requested exponent to the representable range.
if (min_coeff == minimum_coefficient_size{0})
{
significand_ = static_cast<significand_type>(0);
auto biased_exp {static_cast<int>(exp) + detail::bias_v<decimal64_t>};
if (biased_exp < 0)
{
biased_exp = 0;
}
else if (biased_exp > detail::max_biased_exp_v<decimal64_t>)
{
biased_exp = detail::max_biased_exp_v<decimal64_t>;
}
exponent_ = static_cast<exponent_type>(biased_exp);
return;
}

// Normalize the value, so we don't have to worry about it with operations
detail::normalize<decimal_fast64_t>(min_coeff, exp, is_negative);

significand_ = static_cast<significand_type>(min_coeff);

const auto biased_exp {significand_ == 0U ? 0 : exp + detail::bias_v<decimal64_t>};
const auto biased_exp {static_cast<int>(exp) + detail::bias_v<decimal64_t>};

if (biased_exp > detail::max_biased_exp_v<decimal64_t>)
{
Expand All @@ -542,10 +561,9 @@ constexpr decimal_fast64_t::decimal_fast64_t(T1 coeff, T2 exp, const detail::con
}
else
{
// Flush denorms to zero
// Flush denorms to zero, preserving sign per IEEE 754-2008 3.5.1
significand_ = static_cast<significand_type>(0);
exponent_ = static_cast<exponent_type>(detail::bias_v<decimal64_t>);
sign_ = false;
exponent_ = static_cast<exponent_type>(0);
}
}

Expand Down
43 changes: 43 additions & 0 deletions include/boost/decimal/detail/add_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,28 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto add_impl(const T& lhs, const T& rhs) noexcept
auto lhs_exp {lhs.biased_exponent()};
auto rhs_exp {rhs.biased_exponent()};

// IEEE 754-2008 3.5.1: zero has a cohort with one representation per exponent.
// The exponent-comparison logic below assumes both operands have full precision
// (which expand_significand normalizes), but a cohorted zero has no precision to
// expand and its exponent can be arbitrarily large or small. Short-circuit so the
// result is the non-zero operand (or zero with a sensible cohort if both are zero).
if (big_lhs == 0U && big_rhs == 0U)
{
// IEEE 754-2008 6.1: preferred quantum for the sum of two zeros is min(exp_x, exp_y).
// IEEE 754-2008 6.3: sum of opposite-sign zeros is +0 in default rounding.
const auto result_exp {lhs_exp < rhs_exp ? lhs_exp : rhs_exp};
const bool result_sign {lhs.isneg() && rhs.isneg()};
return ReturnType{lhs.full_significand(), result_exp, result_sign};
}
if (big_lhs == 0U)
{
return ReturnType{rhs.full_significand(), rhs.biased_exponent(), rhs.isneg()};
}
if (big_rhs == 0U)
{
return ReturnType{lhs.full_significand(), lhs.biased_exponent(), lhs.isneg()};
}

// Align to larger exponent
if (lhs_exp != rhs_exp)
{
Expand Down Expand Up @@ -212,6 +234,27 @@ BOOST_DECIMAL_CUDA_CONSTEXPR auto d128_add_impl_new(const T& lhs, const T& rhs)
promoted_sig_type promoted_lhs {big_lhs};
promoted_sig_type promoted_rhs {big_rhs};

// IEEE 754-2008 3.5.1: zero has a cohort with one representation per exponent.
// The exponent-comparison alignment below mishandles a cohorted zero whose exp
// is far from the non-zero operand's exp. Short-circuit so the result is the
// non-zero operand (or zero with a sensible cohort if both are zero).
if (big_lhs == typename T::significand_type{0} && big_rhs == typename T::significand_type{0})
{
// IEEE 754-2008 6.1: preferred quantum for the sum of two zeros is min(exp_x, exp_y).
// IEEE 754-2008 6.3: sum of opposite-sign zeros is +0 in default rounding.
const auto result_exp {lhs_exp < rhs_exp ? lhs_exp : rhs_exp};
const bool result_sign {lhs.isneg() && rhs.isneg()};
return ReturnType{lhs.full_significand(), result_exp, result_sign};
}
if (big_lhs == typename T::significand_type{0})
{
return ReturnType{rhs.full_significand(), rhs.biased_exponent(), rhs.isneg()};
}
if (big_rhs == typename T::significand_type{0})
{
return ReturnType{lhs.full_significand(), lhs.biased_exponent(), lhs.isneg()};
}

// Align to larger exponent
if (lhs_exp != rhs_exp)
{
Expand Down
8 changes: 8 additions & 0 deletions include/boost/decimal/detail/comparison.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ BOOST_DECIMAL_FORCE_INLINE BOOST_DECIMAL_CUDA_CONSTEXPR auto equality_impl(Decim
return (lhs_sig == 0U && rhs_sig == 0U);
}

// Step 4b: Same-sign zeros from any cohort compare equal regardless of their exponents
// (IEEE 754-2008 3.5.1). Without this, two zeros with delta_exp greater than the type's
// precision would fall through to the early-return below and wrongly compare unequal.
if (lhs_sig == 0U && rhs_sig == 0U)
{
return true;
}

// Step 5: Check the exponents
// If the difference is greater than we can represent in the significand than we can assume they are different
const auto lhs_exp {lhs_components.exp};
Expand Down
Loading
Loading