Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/lang/numeric/decimal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@

#include <mpdecimal.h> // mpd_*

namespace {
template <typename FloatingPointType>
requires std::is_floating_point_v<FloatingPointType>
auto floating_point_to_string(const FloatingPointType value) -> std::string {
std::ostringstream oss;
oss << std::setprecision(std::numeric_limits<FloatingPointType>::max_digits10)
<< value;
return oss.str();
}
} // namespace

namespace sourcemeta::core {

struct Decimal::Data {
Expand Down Expand Up @@ -248,6 +259,12 @@ Decimal::Decimal(const std::uint64_t integral_value) {
}
}

Decimal::Decimal(const float floating_point_value)
: Decimal{floating_point_to_string(floating_point_value)} {}

Decimal::Decimal(const double floating_point_value)
: Decimal{floating_point_to_string(floating_point_value)} {}

Decimal::Decimal(const char *const string_value) {
new (this->storage) Data{};
this->data()->value.flags = MPD_STATIC | MPD_STATIC_DATA;
Expand Down
6 changes: 6 additions & 0 deletions src/lang/numeric/include/sourcemeta/core/numeric_decimal.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ class SOURCEMETA_CORE_NUMERIC_EXPORT Decimal {
/// Construct a decimal number from a 64-bit unsigned integer
Decimal(std::uint64_t value);

/// Construct a decimal number from a 32-bit float
explicit Decimal(float value);

/// Construct a decimal number from a 64-bit double
explicit Decimal(double value);

/// Construct a decimal number from a C-string
explicit Decimal(const char *const value);

Expand Down
163 changes: 163 additions & 0 deletions test/numeric/numeric_decimal_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,66 @@ TEST(Numeric_decimal, to_double_negative_infinity) {
EXPECT_LT(result, 0.0);
}

TEST(Numeric_decimal, to_float_not_exactly_representable_gets_rounded) {
const sourcemeta::core::Decimal value{"3.2"};
EXPECT_FALSE(value.is_float());
const float result{value.to_float()};
EXPECT_FLOAT_EQ(result, 3.2f);
}

TEST(Numeric_decimal, to_double_not_exactly_representable_gets_rounded) {
const sourcemeta::core::Decimal value{"3.2"};
EXPECT_FALSE(value.is_double());
const double result{value.to_double()};
EXPECT_DOUBLE_EQ(result, 3.2);
}

TEST(Numeric_decimal, to_float_exceeds_max_float_throws) {
const sourcemeta::core::Decimal value{"1e100"};
EXPECT_FALSE(value.is_float());
EXPECT_THROW(
{ [[maybe_unused]] const float result = value.to_float(); },
std::out_of_range);
}

TEST(Numeric_decimal, to_double_exceeds_max_double_throws) {
const sourcemeta::core::Decimal value{"1e500"};
EXPECT_FALSE(value.is_double());
EXPECT_THROW(
{ [[maybe_unused]] const double result = value.to_double(); },
std::out_of_range);
}

TEST(Numeric_decimal, to_float_below_min_float_throws) {
const sourcemeta::core::Decimal value{"1e-100"};
EXPECT_FALSE(value.is_float());
EXPECT_THROW(
{ [[maybe_unused]] const float result = value.to_float(); },
std::out_of_range);
}

TEST(Numeric_decimal, to_double_below_min_double_throws) {
const sourcemeta::core::Decimal value{"1e-500"};
EXPECT_FALSE(value.is_double());
EXPECT_THROW(
{ [[maybe_unused]] const double result = value.to_double(); },
std::out_of_range);
}

TEST(Numeric_decimal, to_float_high_precision_gets_rounded) {
const sourcemeta::core::Decimal value{"1.23456789"};
EXPECT_FALSE(value.is_float());
const float result{value.to_float()};
EXPECT_FLOAT_EQ(result, 1.23456789f);
}

TEST(Numeric_decimal, to_double_high_precision_gets_rounded) {
const sourcemeta::core::Decimal value{"1.234567890123456789"};
EXPECT_FALSE(value.is_double());
const double result{value.to_double()};
EXPECT_DOUBLE_EQ(result, 1.234567890123456789);
}

TEST(Numeric_decimal, divisible_by_integer_true) {
const sourcemeta::core::Decimal dividend{10};
const sourcemeta::core::Decimal divisor{5};
Expand Down Expand Up @@ -1222,6 +1282,7 @@ TEST(Numeric_decimal, is_uint64_false_too_large) {
const sourcemeta::core::Decimal value{"18446744073709551616"};
EXPECT_FALSE(value.is_uint64());
}

TEST(Numeric_decimal, exception_conversion_syntax_invalid_string) {
EXPECT_THROW(
{ const sourcemeta::core::Decimal value{"not_a_number"}; },
Expand Down Expand Up @@ -1455,3 +1516,105 @@ TEST(Numeric_decimal, negative_zero_preserves_sign) {
EXPECT_TRUE(copy.is_signed());
EXPECT_TRUE(copy.is_zero());
}

TEST(Numeric_decimal, construct_from_float_simple) {
const float value{3.5f};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_float());
EXPECT_TRUE(decimal.is_double());
EXPECT_EQ(decimal.to_float(), 3.5f);
EXPECT_EQ(decimal.to_double(), 3.5);
EXPECT_EQ(decimal.to_string(), "3.5");
}

TEST(Numeric_decimal, construct_from_float_negative) {
const float value{-7.25f};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_float());
EXPECT_TRUE(decimal.is_double());
EXPECT_EQ(decimal.to_float(), -7.25f);
EXPECT_EQ(decimal.to_double(), -7.25);
EXPECT_EQ(decimal.to_string(), "-7.25");
}

TEST(Numeric_decimal, construct_from_float_zero) {
const float value{0.0f};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_float());
EXPECT_TRUE(decimal.is_double());
EXPECT_TRUE(decimal.is_zero());
EXPECT_EQ(decimal.to_float(), 0.0f);
EXPECT_EQ(decimal.to_double(), 0.0);
}

TEST(Numeric_decimal, construct_from_float_very_small) {
const float value{0.125f};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_float());
EXPECT_TRUE(decimal.is_double());
EXPECT_EQ(decimal.to_float(), 0.125f);
EXPECT_EQ(decimal.to_double(), 0.125);
EXPECT_EQ(decimal.to_string(), "0.125");
}

TEST(Numeric_decimal, construct_from_double_simple) {
const double value{3.5};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_double());
EXPECT_EQ(decimal.to_double(), 3.5);
EXPECT_EQ(decimal.to_string(), "3.5");
}

TEST(Numeric_decimal, construct_from_double_negative) {
const double value{-7.25};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_double());
EXPECT_EQ(decimal.to_double(), -7.25);
EXPECT_EQ(decimal.to_string(), "-7.25");
}

TEST(Numeric_decimal, construct_from_double_zero) {
const double value{0.0};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_double());
EXPECT_TRUE(decimal.is_zero());
EXPECT_EQ(decimal.to_double(), 0.0);
}

TEST(Numeric_decimal, construct_from_double_very_small) {
const double value{0.125};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_double());
EXPECT_EQ(decimal.to_double(), 0.125);
EXPECT_EQ(decimal.to_string(), "0.125");
}

TEST(Numeric_decimal, construct_from_double_roundtrip) {
const double value{3.2};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_double());
const double roundtrip{decimal.to_double()};
EXPECT_EQ(roundtrip, value);
}

TEST(Numeric_decimal, construct_from_float_roundtrip) {
const float value{3.2f};
const sourcemeta::core::Decimal decimal{value};
EXPECT_TRUE(decimal.is_float());
const float roundtrip{decimal.to_float()};
EXPECT_EQ(roundtrip, value);
}

TEST(Numeric_decimal, construct_from_double_high_precision) {
const double value{1.234567890123456};
const sourcemeta::core::Decimal decimal{value};
const double roundtrip{decimal.to_double()};
EXPECT_EQ(roundtrip, value);
}

TEST(Numeric_decimal, construct_from_float_high_precision) {
const float value{1.234567f};
const sourcemeta::core::Decimal decimal{value};
const float roundtrip{decimal.to_float()};
EXPECT_EQ(roundtrip, value);
}