Skip to content

Commit 16712c0

Browse files
authored
Merge pull request #742 from cppalliance/dpd64
Implement 64 bit DPD conversions
2 parents 1b49d85 + 339a887 commit 16712c0

File tree

4 files changed

+246
-13
lines changed

4 files changed

+246
-13
lines changed

include/boost/decimal/decimal64.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ BOOST_DECIMAL_EXPORT class decimal64 final
229229

230230
friend constexpr auto from_bid_d64(std::uint64_t bits) noexcept -> decimal64;
231231

232+
template <typename DecimalType>
233+
friend constexpr auto to_dpd_d64(DecimalType val) noexcept
234+
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, DecimalType, std::uint64_t);
235+
232236
public:
233237
// 3.2.3.1 construct/copy/destroy
234238
constexpr decimal64() noexcept = default;

include/boost/decimal/decimal64_fast.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ class decimal64_fast final
119119

120120
friend constexpr auto not_finite(decimal64_fast val) noexcept -> bool;
121121

122+
template <typename DecimalType>
123+
friend constexpr auto to_dpd_d64(DecimalType val) noexcept
124+
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, DecimalType, std::uint64_t);
125+
122126
public:
123127
constexpr decimal64_fast() noexcept = default;
124128

include/boost/decimal/dpd_conversion.hpp

Lines changed: 226 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -421,16 +421,6 @@ constexpr auto to_dpd_d32(DecimalType val) noexcept
421421
return dpd;
422422
}
423423

424-
constexpr auto to_dpd(decimal32 val) noexcept -> std::uint32_t
425-
{
426-
return to_dpd_d32(val);
427-
}
428-
429-
constexpr auto to_dpd(decimal32_fast val) noexcept -> std::uint32_t
430-
{
431-
return to_dpd_d32(val);
432-
}
433-
434424
template <typename DecimalType = decimal32_fast>
435425
constexpr auto from_dpd_d32(std::uint32_t dpd) noexcept
436426
BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, DecimalType)
@@ -513,13 +503,239 @@ constexpr auto from_dpd_d32(std::uint32_t dpd) noexcept
513503
return DecimalType{significand, exp, sign};
514504
}
515505

506+
template <typename DecimalType>
507+
constexpr auto to_dpd_d64(DecimalType val) noexcept
508+
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, DecimalType, std::uint64_t)
509+
{
510+
static_assert(std::is_same<DecimalType, decimal64>::value ||
511+
std::is_same<DecimalType, decimal64_fast>::value, "The input must be a 64-bit decimal type");
512+
513+
// In the non-finite cases the encodings are the same
514+
// 3.5.2.a and 3.5.2.b
515+
if (!isfinite(val))
516+
{
517+
return to_bid(val);
518+
}
519+
520+
const auto sign {val.isneg()};
521+
const auto exp {val.unbiased_exponent()};
522+
const auto significand {val.full_significand()};
523+
524+
std::uint64_t dpd {};
525+
// Set the sign bit as applicable
526+
if (sign)
527+
{
528+
dpd |= detail::d64_sign_mask;
529+
}
530+
531+
std::uint8_t d[std::numeric_limits<DecimalType>::digits10] {};
532+
auto temp_sig {significand};
533+
for (int i = 15; i >= 0; --i)
534+
{
535+
d[i] = static_cast<std::uint8_t>(temp_sig % 10U);
536+
temp_sig /= 10U;
537+
}
538+
BOOST_DECIMAL_ASSERT(d[0] >= 0 && d[0] <= 9);
539+
BOOST_DECIMAL_ASSERT(temp_sig == 0);
540+
541+
constexpr std::uint64_t leading_two_exp_bits_mask {0b1100000000};
542+
const auto leading_two_bits {(exp & leading_two_exp_bits_mask) >> 8U};
543+
BOOST_DECIMAL_ASSERT(leading_two_bits >= 0 && leading_two_bits <= 2);
544+
constexpr std::uint64_t trailing_exp_bits_mask {0b0011111111};
545+
const auto trailing_exp_bits {(exp & trailing_exp_bits_mask)};
546+
547+
std::uint64_t combination_field_bits {};
548+
549+
// Now based on what the value of d[0] and the leading bits of exp are we can set the value of the combination field
550+
// See 3.5.2.c.1
551+
// If d0 is 8 or 9 then we follow section i
552+
if (d[0] >= 8)
553+
{
554+
const auto d0_is_nine {d[0] == 9};
555+
switch (leading_two_bits)
556+
{
557+
case 0U:
558+
combination_field_bits = d0_is_nine ? 0b11001 : 0b11000;
559+
break;
560+
case 1U:
561+
combination_field_bits = d0_is_nine ? 0b11011 : 0b11010;
562+
break;
563+
case 2U:
564+
combination_field_bits = d0_is_nine ? 0b11101 : 0b11100;
565+
break;
566+
// LCOV_EXCL_START
567+
default:
568+
BOOST_DECIMAL_UNREACHABLE;
569+
// LCOV_EXCL_STOP
570+
}
571+
}
572+
// If d0 is 0 to 7 then we follow section II
573+
else
574+
{
575+
// In here the value of d[0] = 4*G2 + 2*G3 + G4
576+
const auto d0_mask {static_cast<std::uint64_t>(d[0])};
577+
switch (leading_two_bits)
578+
{
579+
case 0U:
580+
// 00XXX
581+
combination_field_bits |= d0_mask;
582+
break;
583+
case 1U:
584+
// 01XXX
585+
combination_field_bits = 0b01000;
586+
combination_field_bits |= d0_mask;
587+
break;
588+
case 2U:
589+
// 10XXX
590+
combination_field_bits = 0b10000;
591+
combination_field_bits |= d0_mask;
592+
break;
593+
// LCOV_EXCL_START
594+
default:
595+
BOOST_DECIMAL_UNREACHABLE;
596+
// LCOV_EXCL_STOP
597+
}
598+
}
599+
600+
// Write the now known combination field and trailing exp bits to the result
601+
dpd |= (combination_field_bits << 58U);
602+
dpd |= (trailing_exp_bits << 50U);
603+
604+
// Now we need to encode all the declets
605+
// Once we have the declet right it into the result
606+
int offset {4};
607+
for (std::size_t i {1}; i < 15; i += 3U)
608+
{
609+
const auto declet {static_cast<std::uint64_t>(detail::encode_dpd(d[i], d[i + 1], d[i + 2]))};
610+
dpd |= (declet << (10 * offset));
611+
--offset;
612+
}
613+
614+
return dpd;
615+
}
616+
617+
template <typename DecimalType = decimal64_fast>
618+
constexpr auto from_dpd_d64(std::uint64_t dpd) noexcept
619+
BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, DecimalType)
620+
{
621+
// First we check for non-finite values
622+
// Since they are in the same initial format as BID it's easy to check with our existing masks
623+
if ((dpd & detail::d64_inf_mask) == detail::d64_inf_mask)
624+
{
625+
if ((dpd & detail::d64_snan_mask) == detail::d64_snan_mask)
626+
{
627+
return std::numeric_limits<DecimalType>::signaling_NaN();
628+
}
629+
else if ((dpd & detail::d64_nan_mask) == detail::d64_nan_mask)
630+
{
631+
return std::numeric_limits<DecimalType>::quiet_NaN();
632+
}
633+
else
634+
{
635+
return std::numeric_limits<DecimalType>::infinity();
636+
}
637+
}
638+
639+
// The bit lengths are the same as used in the standard bid format
640+
const auto sign {(dpd & detail::d64_sign_mask) != 0};
641+
const auto combination_field_bits {(dpd & detail::d64_combination_field_mask) >> 58U};
642+
const auto exponent_field_bits {(dpd & detail::d64_exponent_mask) >> 50U};
643+
auto significand_bits {(dpd & detail::d64_significand_mask)};
644+
645+
// Case 1: 3.5.2.c.1.i
646+
// Combination field bits are 110XX or 11110X
647+
std::uint64_t d0 {};
648+
std::uint64_t leading_biased_exp_bits {};
649+
if (combination_field_bits >= 0b11000)
650+
{
651+
// d0 = 8 + G4
652+
// Must be equal to 8 or 9
653+
d0 = 8U + (combination_field_bits & 0b00001);
654+
BOOST_DECIMAL_ASSERT(d0 == 8 || d0 == 9);
655+
656+
// leading exp bits are 2*G2 + G3
657+
// Must be equal to 0, 1 or 2
658+
leading_biased_exp_bits = 2U * ((combination_field_bits & 0b00100) >> 2U) + ((combination_field_bits & 0b00010) >> 1U);
659+
BOOST_DECIMAL_ASSERT(leading_biased_exp_bits <= 2U);
660+
}
661+
// Case 2: 3.5.2.c.1.ii
662+
// Combination field bits are 0XXXX or 10XXX
663+
else
664+
{
665+
// d0 = 4 * G2 + 2 * G3 + G4
666+
// Must be in the range 0-7
667+
d0 = combination_field_bits & 0b00111;
668+
BOOST_DECIMAL_ASSERT(d0 <= 7);
669+
670+
// Leading exp bits are 2 * G0 + G1
671+
// Must be equal to 0, 1 or 2
672+
leading_biased_exp_bits = (combination_field_bits & 0b11000) >> 3U;
673+
BOOST_DECIMAL_ASSERT(leading_biased_exp_bits <= 2U);
674+
}
675+
676+
// Now that we have the bits we can calculate the exponents value
677+
const auto complete_exp {(leading_biased_exp_bits << 8U) + exponent_field_bits};
678+
const auto exp {static_cast<std::int32_t>(complete_exp) - detail::bias_v<DecimalType>};
679+
680+
// We can now decode the remainder of the significand to recover the value
681+
std::uint8_t digits[16] {};
682+
digits[0] = static_cast<std::uint8_t>(d0);
683+
for (int i = 15; i > 0; i -= 3)
684+
{
685+
const auto declet_bits {static_cast<std::uint32_t>(significand_bits & 0b1111111111)};
686+
significand_bits >>= 10U;
687+
detail::decode_dpd(declet_bits, digits[i], digits[i - 1], digits[i - 2]);
688+
}
689+
690+
std::uint64_t significand {};
691+
for (std::uint64_t i {}; i < 16U; ++i)
692+
{
693+
significand += digits[i] * detail::pow10(15 - i);
694+
}
695+
696+
return DecimalType{significand, exp, sign};
697+
}
698+
699+
constexpr auto to_dpd(decimal32 val) noexcept -> std::uint32_t
700+
{
701+
return to_dpd_d32(val);
702+
}
703+
704+
constexpr auto to_dpd(decimal32_fast val) noexcept -> std::uint32_t
705+
{
706+
return to_dpd_d32(val);
707+
}
708+
709+
constexpr auto to_dpd(decimal64 val) -> std::uint64_t
710+
{
711+
return to_dpd_d64(val);
712+
}
713+
714+
constexpr auto to_dpd(decimal64_fast val) -> std::uint64_t
715+
{
716+
return to_dpd_d64(val);
717+
}
718+
719+
template <typename DecimalType>
720+
constexpr auto to_dpd(DecimalType val) noexcept
721+
{
722+
static_assert(detail::is_decimal_floating_point_v<DecimalType>, "Must be a decimal floating point type.");
723+
return to_dpd(val);
724+
}
725+
516726
template <typename DecimalType = decimal32_fast>
517727
constexpr auto from_dpd(std::uint32_t bits) noexcept
518728
BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, DecimalType)
519729
{
520730
return from_dpd_d32<DecimalType>(bits);
521731
}
522732

733+
template <typename DecimalType = decimal64_fast>
734+
constexpr auto from_dpd(std::uint64_t bits) noexcept
735+
BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, DecimalType)
736+
{
737+
return from_dpd_d64<DecimalType>(bits);
738+
}
523739

524740
} // namespace decimal
525741
} // namespace boost

test/test_dpd_conversions.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ using namespace boost::decimal;
1111
template <typename T>
1212
T roundtrip(T val)
1313
{
14-
const auto bits {to_dpd(val)};
14+
const auto bits {to_dpd<T>(val)};
1515
return from_dpd<T>(bits);
1616
}
1717

@@ -41,9 +41,12 @@ void test()
4141
template <typename T>
4242
void test_float_range()
4343
{
44+
using float_type = std::conditional_t<std::is_same<T, decimal32>::value ||
45+
std::is_same<T, decimal32_fast>::value, float, double>;
46+
4447
std::mt19937_64 rng(42);
45-
std::uniform_real_distribution<float> dist(std::numeric_limits<float>::min(),
46-
std::numeric_limits<float>::max());
48+
std::uniform_real_distribution<float_type> dist(std::numeric_limits<float_type>::min(),
49+
std::numeric_limits<float_type>::max());
4750

4851
for (std::size_t i {}; i < 1024; ++i)
4952
{
@@ -61,5 +64,11 @@ int main()
6164
test_float_range<decimal32>();
6265
test_float_range<decimal32_fast>();
6366

67+
test<decimal64>();
68+
test<decimal64_fast>();
69+
70+
test_float_range<decimal64>();
71+
test_float_range<decimal64_fast>();
72+
6473
return boost::report_errors();
6574
}

0 commit comments

Comments
 (0)