Skip to content

Commit 7ce57be

Browse files
authored
Add Decimal float/double constructors (#2036)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent a60dff3 commit 7ce57be

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

src/lang/numeric/decimal.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616

1717
#include <mpdecimal.h> // mpd_*
1818

19+
namespace {
20+
template <typename FloatingPointType>
21+
requires std::is_floating_point_v<FloatingPointType>
22+
auto floating_point_to_string(const FloatingPointType value) -> std::string {
23+
std::ostringstream oss;
24+
oss << std::setprecision(std::numeric_limits<FloatingPointType>::max_digits10)
25+
<< value;
26+
return oss.str();
27+
}
28+
} // namespace
29+
1930
namespace sourcemeta::core {
2031

2132
struct Decimal::Data {
@@ -248,6 +259,12 @@ Decimal::Decimal(const std::uint64_t integral_value) {
248259
}
249260
}
250261

262+
Decimal::Decimal(const float floating_point_value)
263+
: Decimal{floating_point_to_string(floating_point_value)} {}
264+
265+
Decimal::Decimal(const double floating_point_value)
266+
: Decimal{floating_point_to_string(floating_point_value)} {}
267+
251268
Decimal::Decimal(const char *const string_value) {
252269
new (this->storage) Data{};
253270
this->data()->value.flags = MPD_STATIC | MPD_STATIC_DATA;

src/lang/numeric/include/sourcemeta/core/numeric_decimal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ class SOURCEMETA_CORE_NUMERIC_EXPORT Decimal {
4848
/// Construct a decimal number from a 64-bit unsigned integer
4949
Decimal(std::uint64_t value);
5050

51+
/// Construct a decimal number from a 32-bit float
52+
explicit Decimal(float value);
53+
54+
/// Construct a decimal number from a 64-bit double
55+
explicit Decimal(double value);
56+
5157
/// Construct a decimal number from a C-string
5258
explicit Decimal(const char *const value);
5359

test/numeric/numeric_decimal_test.cc

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,66 @@ TEST(Numeric_decimal, to_double_negative_infinity) {
759759
EXPECT_LT(result, 0.0);
760760
}
761761

762+
TEST(Numeric_decimal, to_float_not_exactly_representable_gets_rounded) {
763+
const sourcemeta::core::Decimal value{"3.2"};
764+
EXPECT_FALSE(value.is_float());
765+
const float result{value.to_float()};
766+
EXPECT_FLOAT_EQ(result, 3.2f);
767+
}
768+
769+
TEST(Numeric_decimal, to_double_not_exactly_representable_gets_rounded) {
770+
const sourcemeta::core::Decimal value{"3.2"};
771+
EXPECT_FALSE(value.is_double());
772+
const double result{value.to_double()};
773+
EXPECT_DOUBLE_EQ(result, 3.2);
774+
}
775+
776+
TEST(Numeric_decimal, to_float_exceeds_max_float_throws) {
777+
const sourcemeta::core::Decimal value{"1e100"};
778+
EXPECT_FALSE(value.is_float());
779+
EXPECT_THROW(
780+
{ [[maybe_unused]] const float result = value.to_float(); },
781+
std::out_of_range);
782+
}
783+
784+
TEST(Numeric_decimal, to_double_exceeds_max_double_throws) {
785+
const sourcemeta::core::Decimal value{"1e500"};
786+
EXPECT_FALSE(value.is_double());
787+
EXPECT_THROW(
788+
{ [[maybe_unused]] const double result = value.to_double(); },
789+
std::out_of_range);
790+
}
791+
792+
TEST(Numeric_decimal, to_float_below_min_float_throws) {
793+
const sourcemeta::core::Decimal value{"1e-100"};
794+
EXPECT_FALSE(value.is_float());
795+
EXPECT_THROW(
796+
{ [[maybe_unused]] const float result = value.to_float(); },
797+
std::out_of_range);
798+
}
799+
800+
TEST(Numeric_decimal, to_double_below_min_double_throws) {
801+
const sourcemeta::core::Decimal value{"1e-500"};
802+
EXPECT_FALSE(value.is_double());
803+
EXPECT_THROW(
804+
{ [[maybe_unused]] const double result = value.to_double(); },
805+
std::out_of_range);
806+
}
807+
808+
TEST(Numeric_decimal, to_float_high_precision_gets_rounded) {
809+
const sourcemeta::core::Decimal value{"1.23456789"};
810+
EXPECT_FALSE(value.is_float());
811+
const float result{value.to_float()};
812+
EXPECT_FLOAT_EQ(result, 1.23456789f);
813+
}
814+
815+
TEST(Numeric_decimal, to_double_high_precision_gets_rounded) {
816+
const sourcemeta::core::Decimal value{"1.234567890123456789"};
817+
EXPECT_FALSE(value.is_double());
818+
const double result{value.to_double()};
819+
EXPECT_DOUBLE_EQ(result, 1.234567890123456789);
820+
}
821+
762822
TEST(Numeric_decimal, divisible_by_integer_true) {
763823
const sourcemeta::core::Decimal dividend{10};
764824
const sourcemeta::core::Decimal divisor{5};
@@ -1222,6 +1282,7 @@ TEST(Numeric_decimal, is_uint64_false_too_large) {
12221282
const sourcemeta::core::Decimal value{"18446744073709551616"};
12231283
EXPECT_FALSE(value.is_uint64());
12241284
}
1285+
12251286
TEST(Numeric_decimal, exception_conversion_syntax_invalid_string) {
12261287
EXPECT_THROW(
12271288
{ const sourcemeta::core::Decimal value{"not_a_number"}; },
@@ -1455,3 +1516,105 @@ TEST(Numeric_decimal, negative_zero_preserves_sign) {
14551516
EXPECT_TRUE(copy.is_signed());
14561517
EXPECT_TRUE(copy.is_zero());
14571518
}
1519+
1520+
TEST(Numeric_decimal, construct_from_float_simple) {
1521+
const float value{3.5f};
1522+
const sourcemeta::core::Decimal decimal{value};
1523+
EXPECT_TRUE(decimal.is_float());
1524+
EXPECT_TRUE(decimal.is_double());
1525+
EXPECT_EQ(decimal.to_float(), 3.5f);
1526+
EXPECT_EQ(decimal.to_double(), 3.5);
1527+
EXPECT_EQ(decimal.to_string(), "3.5");
1528+
}
1529+
1530+
TEST(Numeric_decimal, construct_from_float_negative) {
1531+
const float value{-7.25f};
1532+
const sourcemeta::core::Decimal decimal{value};
1533+
EXPECT_TRUE(decimal.is_float());
1534+
EXPECT_TRUE(decimal.is_double());
1535+
EXPECT_EQ(decimal.to_float(), -7.25f);
1536+
EXPECT_EQ(decimal.to_double(), -7.25);
1537+
EXPECT_EQ(decimal.to_string(), "-7.25");
1538+
}
1539+
1540+
TEST(Numeric_decimal, construct_from_float_zero) {
1541+
const float value{0.0f};
1542+
const sourcemeta::core::Decimal decimal{value};
1543+
EXPECT_TRUE(decimal.is_float());
1544+
EXPECT_TRUE(decimal.is_double());
1545+
EXPECT_TRUE(decimal.is_zero());
1546+
EXPECT_EQ(decimal.to_float(), 0.0f);
1547+
EXPECT_EQ(decimal.to_double(), 0.0);
1548+
}
1549+
1550+
TEST(Numeric_decimal, construct_from_float_very_small) {
1551+
const float value{0.125f};
1552+
const sourcemeta::core::Decimal decimal{value};
1553+
EXPECT_TRUE(decimal.is_float());
1554+
EXPECT_TRUE(decimal.is_double());
1555+
EXPECT_EQ(decimal.to_float(), 0.125f);
1556+
EXPECT_EQ(decimal.to_double(), 0.125);
1557+
EXPECT_EQ(decimal.to_string(), "0.125");
1558+
}
1559+
1560+
TEST(Numeric_decimal, construct_from_double_simple) {
1561+
const double value{3.5};
1562+
const sourcemeta::core::Decimal decimal{value};
1563+
EXPECT_TRUE(decimal.is_double());
1564+
EXPECT_EQ(decimal.to_double(), 3.5);
1565+
EXPECT_EQ(decimal.to_string(), "3.5");
1566+
}
1567+
1568+
TEST(Numeric_decimal, construct_from_double_negative) {
1569+
const double value{-7.25};
1570+
const sourcemeta::core::Decimal decimal{value};
1571+
EXPECT_TRUE(decimal.is_double());
1572+
EXPECT_EQ(decimal.to_double(), -7.25);
1573+
EXPECT_EQ(decimal.to_string(), "-7.25");
1574+
}
1575+
1576+
TEST(Numeric_decimal, construct_from_double_zero) {
1577+
const double value{0.0};
1578+
const sourcemeta::core::Decimal decimal{value};
1579+
EXPECT_TRUE(decimal.is_double());
1580+
EXPECT_TRUE(decimal.is_zero());
1581+
EXPECT_EQ(decimal.to_double(), 0.0);
1582+
}
1583+
1584+
TEST(Numeric_decimal, construct_from_double_very_small) {
1585+
const double value{0.125};
1586+
const sourcemeta::core::Decimal decimal{value};
1587+
EXPECT_TRUE(decimal.is_double());
1588+
EXPECT_EQ(decimal.to_double(), 0.125);
1589+
EXPECT_EQ(decimal.to_string(), "0.125");
1590+
}
1591+
1592+
TEST(Numeric_decimal, construct_from_double_roundtrip) {
1593+
const double value{3.2};
1594+
const sourcemeta::core::Decimal decimal{value};
1595+
EXPECT_TRUE(decimal.is_double());
1596+
const double roundtrip{decimal.to_double()};
1597+
EXPECT_EQ(roundtrip, value);
1598+
}
1599+
1600+
TEST(Numeric_decimal, construct_from_float_roundtrip) {
1601+
const float value{3.2f};
1602+
const sourcemeta::core::Decimal decimal{value};
1603+
EXPECT_TRUE(decimal.is_float());
1604+
const float roundtrip{decimal.to_float()};
1605+
EXPECT_EQ(roundtrip, value);
1606+
}
1607+
1608+
TEST(Numeric_decimal, construct_from_double_high_precision) {
1609+
const double value{1.234567890123456};
1610+
const sourcemeta::core::Decimal decimal{value};
1611+
const double roundtrip{decimal.to_double()};
1612+
EXPECT_EQ(roundtrip, value);
1613+
}
1614+
1615+
TEST(Numeric_decimal, construct_from_float_high_precision) {
1616+
const float value{1.234567f};
1617+
const sourcemeta::core::Decimal decimal{value};
1618+
const float roundtrip{decimal.to_float()};
1619+
EXPECT_EQ(roundtrip, value);
1620+
}

0 commit comments

Comments
 (0)