This is an automated email from the ASF dual-hosted git repository.

wesm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/master by this push:
     new aaec0d4  ARROW-7011: [C++] Implement casts from float/double to decimal
aaec0d4 is described below

commit aaec0d46c56b7168e2e83295dd91a84f108d8fcd
Author: Antoine Pitrou <[email protected]>
AuthorDate: Thu Jul 2 16:44:10 2020 -0500

    ARROW-7011: [C++] Implement casts from float/double to decimal
    
    Also naturally available in Python using the Array.cast() method.
    
    Closes #7612 from pitrou/ARROW-7011-cast-float-to-decimal
    
    Lead-authored-by: Antoine Pitrou <[email protected]>
    Co-authored-by: Wes McKinney <[email protected]>
    Signed-off-by: Wes McKinney <[email protected]>
---
 .../arrow/compute/kernels/scalar_cast_internal.h   |   4 +
 .../arrow/compute/kernels/scalar_cast_numeric.cc   |  44 ++++-
 cpp/src/arrow/compute/kernels/scalar_cast_test.cc  |  88 ++++++++--
 cpp/src/arrow/util/decimal.cc                      | 136 ++++++++++++---
 cpp/src/arrow/util/decimal.h                       |   5 +
 cpp/src/arrow/util/decimal_test.cc                 | 191 +++++++++++++++++++++
 6 files changed, 429 insertions(+), 39 deletions(-)

diff --git a/cpp/src/arrow/compute/kernels/scalar_cast_internal.h 
b/cpp/src/arrow/compute/kernels/scalar_cast_internal.h
index 59cff56..914b670 100644
--- a/cpp/src/arrow/compute/kernels/scalar_cast_internal.h
+++ b/cpp/src/arrow/compute/kernels/scalar_cast_internal.h
@@ -75,6 +75,10 @@ Result<ValueDescr> ResolveOutputFromOptions(KernelContext* 
ctx,
 
 ARROW_EXPORT extern OutputType kOutputTargetType;
 
+// Add generic casts to out_ty from:
+// - the null type
+// - dictionary with out_ty as given value type
+// - extension types with a compatible storage type
 void AddCommonCasts(Type::type out_type_id, OutputType out_ty, CastFunction* 
func);
 
 }  // namespace internal
diff --git a/cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc 
b/cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc
index f93bb35..3b5aac4 100644
--- a/cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_cast_numeric.cc
@@ -467,6 +467,42 @@ struct CastFunctor<Decimal128Type, Decimal128Type> {
   }
 };
 
+// ----------------------------------------------------------------------
+// Real to decimal
+
+struct RealToDecimal {
+  template <typename OUT, typename RealType>
+  Decimal128 Call(KernelContext* ctx, RealType val) const {
+    auto result = Decimal128::FromReal(val, out_precision_, out_scale_);
+    if (ARROW_PREDICT_FALSE(!result.ok())) {
+      if (!allow_truncate_) {
+        ctx->SetStatus(result.status());
+      }
+      return Decimal128();  // Zero
+    } else {
+      return *std::move(result);
+    }
+  }
+
+  int32_t out_scale_, out_precision_;
+  bool allow_truncate_;
+};
+
+template <typename I>
+struct CastFunctor<Decimal128Type, I, enable_if_t<is_floating_type<I>::value>> 
{
+  static void Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
+    const auto& options = checked_cast<const 
CastState*>(ctx->state())->options;
+    ArrayData* output = out->mutable_array();
+    const auto& out_type_inst = checked_cast<const 
Decimal128Type&>(*output->type);
+    const auto out_scale = out_type_inst.scale();
+    const auto out_precision = out_type_inst.precision();
+
+    applicator::ScalarUnaryNotNullStateful<Decimal128Type, I, RealToDecimal> 
kernel(
+        RealToDecimal{out_scale, out_precision, 
options.allow_decimal_truncate});
+    return kernel.Exec(ctx, batch, out);
+  }
+};
+
 namespace {
 
 template <typename OutType>
@@ -530,10 +566,16 @@ std::shared_ptr<CastFunction> 
GetCastToFloating(std::string name) {
 std::shared_ptr<CastFunction> GetCastToDecimal() {
   OutputType sig_out_ty(ResolveOutputFromOptions);
 
-  // Cast to decimal
   auto func = std::make_shared<CastFunction>("cast_decimal", Type::DECIMAL);
   AddCommonCasts(Type::DECIMAL, sig_out_ty, func.get());
 
+  // Cast from floating point
+  DCHECK_OK(func->AddKernel(Type::FLOAT, {float32()}, sig_out_ty,
+                            CastFunctor<Decimal128Type, FloatType>::Exec));
+  DCHECK_OK(func->AddKernel(Type::DOUBLE, {float64()}, sig_out_ty,
+                            CastFunctor<Decimal128Type, DoubleType>::Exec));
+
+  // Cast from other decimal
   auto exec = CastFunctor<Decimal128Type, Decimal128Type>::Exec;
   // We resolve the output type of this kernel from the CastOptions
   DCHECK_OK(func->AddKernel(Type::DECIMAL, {InputType::Array(Type::DECIMAL)}, 
sig_out_ty,
diff --git a/cpp/src/arrow/compute/kernels/scalar_cast_test.cc 
b/cpp/src/arrow/compute/kernels/scalar_cast_test.cc
index a479fe9..252e50e 100644
--- a/cpp/src/arrow/compute/kernels/scalar_cast_test.cc
+++ b/cpp/src/arrow/compute/kernels/scalar_cast_test.cc
@@ -101,20 +101,11 @@ class TestCast : public TestBase {
     }
   }
 
-  template <typename InType, typename I_TYPE = typename 
TestCType<InType>::type>
-  void CheckFails(const std::shared_ptr<DataType>& in_type,
-                  const std::vector<I_TYPE>& in_values, const 
std::vector<bool>& is_valid,
-                  const std::shared_ptr<DataType>& out_type, const 
CastOptions& options,
-                  bool check_scalar = true) {
-    std::shared_ptr<Array> input;
-    if (is_valid.size() > 0) {
-      ArrayFromVector<InType, I_TYPE>(in_type, is_valid, in_values, &input);
-    } else {
-      ArrayFromVector<InType, I_TYPE>(in_type, in_values, &input);
-    }
-    ASSERT_RAISES(Invalid, Cast(*input, out_type, options));
+  void CheckFails(const Array& input, const std::shared_ptr<DataType>& 
out_type,
+                  const CastOptions& options, bool check_scalar = true) {
+    ASSERT_RAISES(Invalid, Cast(input, out_type, options));
 
-    if (in_type->id() == Type::DECIMAL || out_type->id() == Type::DECIMAL) {
+    if (input.type_id() == Type::DECIMAL || out_type->id() == Type::DECIMAL) {
       // ARROW-9194
       check_scalar = false;
     }
@@ -124,8 +115,8 @@ class TestCast : public TestBase {
     // cases we will want to check more precisely
     if (check_scalar) {
       int64_t num_failing = 0;
-      for (int64_t i = 0; i < input->length(); ++i) {
-        auto maybe_out = Cast(*input->GetScalar(i), out_type, options);
+      for (int64_t i = 0; i < input.length(); ++i) {
+        auto maybe_out = Cast(*input.GetScalar(i), out_type, options);
         num_failing += static_cast<int>(maybe_out.status().IsInvalid());
       }
       ASSERT_GT(num_failing, 0);
@@ -133,6 +124,20 @@ class TestCast : public TestBase {
   }
 
   template <typename InType, typename I_TYPE = typename 
TestCType<InType>::type>
+  void CheckFails(const std::shared_ptr<DataType>& in_type,
+                  const std::vector<I_TYPE>& in_values, const 
std::vector<bool>& is_valid,
+                  const std::shared_ptr<DataType>& out_type, const 
CastOptions& options,
+                  bool check_scalar = true) {
+    std::shared_ptr<Array> input;
+    if (is_valid.size() > 0) {
+      ArrayFromVector<InType, I_TYPE>(in_type, is_valid, in_values, &input);
+    } else {
+      ArrayFromVector<InType, I_TYPE>(in_type, in_values, &input);
+    }
+    CheckFails(*input, out_type, options, check_scalar);
+  }
+
+  template <typename InType, typename I_TYPE = typename 
TestCType<InType>::type>
   void CheckFails(const std::vector<I_TYPE>& in_values, const 
std::vector<bool>& is_valid,
                   const std::shared_ptr<DataType>& out_type, const 
CastOptions& options,
                   bool check_scalar = true) {
@@ -202,6 +207,14 @@ class TestCast : public TestBase {
     }
   }
 
+  void CheckFailsJSON(const std::shared_ptr<DataType>& in_type,
+                      const std::shared_ptr<DataType>& out_type,
+                      const std::string& in_json, bool check_scalar = true,
+                      const CastOptions& options = CastOptions()) {
+    std::shared_ptr<Array> input = ArrayFromJSON(in_type, in_json);
+    CheckFails(*input, out_type, options, check_scalar);
+  }
+
   template <typename SourceType, typename DestType>
   void TestCastBinaryToString() {
     CastOptions options;
@@ -369,6 +382,23 @@ class TestCast : public TestBase {
 
     // NOTE: timestamp parsing is tested comprehensively in 
parsing-util-test.cc
   }
+
+  void TestCastFloatingToDecimal(const std::shared_ptr<DataType>& in_type) {
+    auto out_type = decimal(5, 2);
+
+    CheckCaseJSON(in_type, out_type, "[0.0, null, 123.45, 123.456, 999.994]",
+                  R"(["0.00", null, "123.45", "123.46", "999.99"])");
+
+    // Overflow
+    CastOptions options{};
+    out_type = decimal(5, 2);
+    CheckFailsJSON(in_type, out_type, "[999.996]", /*check_scalar=*/true, 
options);
+
+    options.allow_decimal_truncate = true;
+    CheckCaseJSON(in_type, out_type, "[0.0, null, 999.996, 123.45, 999.994]",
+                  R"(["0.00", null, "0.00", "123.45", "999.99"])", 
/*check_scalar=*/true,
+                  options);
+  }
 };
 
 TEST_F(TestCast, SameTypeZeroCopy) {
@@ -901,6 +931,34 @@ TEST_F(TestCast, DecimalToDecimal) {
   check_truncate(decimal(4, 2), v5, is_valid1, decimal(2, 1), e5);
 }
 
+TEST_F(TestCast, FloatToDecimal) {
+  auto in_type = float32();
+
+  TestCastFloatingToDecimal(in_type);
+
+  // 2**64 + 2**41 (exactly representable as a float)
+  auto out_type = decimal(20, 0);
+  CheckCaseJSON(in_type, out_type, "[1.8446746e+19, -1.8446746e+19]",
+                R"(["18446746272732807168", "-18446746272732807168"])");
+  out_type = decimal(20, 4);
+  CheckCaseJSON(in_type, out_type, "[1.8446746e+15, -1.8446746e+15]",
+                R"(["1844674627273280.7168", "-1844674627273280.7168"])");
+}
+
+TEST_F(TestCast, DoubleToDecimal) {
+  auto in_type = float64();
+
+  TestCastFloatingToDecimal(in_type);
+
+  // 2**64 + 2**11 (exactly representable as a double)
+  auto out_type = decimal(20, 0);
+  CheckCaseJSON(in_type, out_type, "[1.8446744073709556e+19, 
-1.8446744073709556e+19]",
+                R"(["18446744073709555712", "-18446744073709555712"])");
+  out_type = decimal(20, 4);
+  CheckCaseJSON(in_type, out_type, "[1.8446744073709556e+15, 
-1.8446744073709556e+15]",
+                R"(["1844674407370955.5712", "-1844674407370955.5712"])");
+}
+
 TEST_F(TestCast, TimestampToTimestamp) {
   CastOptions options;
 
diff --git a/cpp/src/arrow/util/decimal.cc b/cpp/src/arrow/util/decimal.cc
index 354f979..5620803 100644
--- a/cpp/src/arrow/util/decimal.cc
+++ b/cpp/src/arrow/util/decimal.cc
@@ -18,6 +18,7 @@
 #include <algorithm>
 #include <array>
 #include <climits>
+#include <cmath>
 #include <cstdint>
 #include <cstdlib>
 #include <cstring>
@@ -48,6 +49,117 @@ static const Decimal128 
kTenTo36(static_cast<int64_t>(0xC097CE7BC90715),
                                  0xB34B9F1000000000);
 static const Decimal128 kTenTo18(0xDE0B6B3A7640000);
 
+static constexpr auto kInt64DecimalDigits =
+    static_cast<size_t>(std::numeric_limits<int64_t>::digits10);
+
+static constexpr int64_t kInt64PowersOfTen[kInt64DecimalDigits + 1] = {
+    // clang-format off
+    1LL,
+    10LL,
+    100LL,
+    1000LL,
+    10000LL,
+    100000LL,
+    1000000LL,
+    10000000LL,
+    100000000LL,
+    1000000000LL,
+    10000000000LL,
+    100000000000LL,
+    1000000000000LL,
+    10000000000000LL,
+    100000000000000LL,
+    1000000000000000LL,
+    10000000000000000LL,
+    100000000000000000LL,
+    1000000000000000000LL
+    // clang-format on
+};
+
+static constexpr float kFloatPowersOfTen[2 * 38 + 1] = {
+    1e-38f, 1e-37f, 1e-36f, 1e-35f, 1e-34f, 1e-33f, 1e-32f, 1e-31f, 1e-30f, 
1e-29f,
+    1e-28f, 1e-27f, 1e-26f, 1e-25f, 1e-24f, 1e-23f, 1e-22f, 1e-21f, 1e-20f, 
1e-19f,
+    1e-18f, 1e-17f, 1e-16f, 1e-15f, 1e-14f, 1e-13f, 1e-12f, 1e-11f, 1e-10f, 
1e-9f,
+    1e-8f,  1e-7f,  1e-6f,  1e-5f,  1e-4f,  1e-3f,  1e-2f,  1e-1f,  1e0f,   
1e1f,
+    1e2f,   1e3f,   1e4f,   1e5f,   1e6f,   1e7f,   1e8f,   1e9f,   1e10f,  
1e11f,
+    1e12f,  1e13f,  1e14f,  1e15f,  1e16f,  1e17f,  1e18f,  1e19f,  1e20f,  
1e21f,
+    1e22f,  1e23f,  1e24f,  1e25f,  1e26f,  1e27f,  1e28f,  1e29f,  1e30f,  
1e31f,
+    1e32f,  1e33f,  1e34f,  1e35f,  1e36f,  1e37f,  1e38f};
+
+static constexpr double kDoublePowersOfTen[2 * 38 + 1] = {
+    1e-38, 1e-37, 1e-36, 1e-35, 1e-34, 1e-33, 1e-32, 1e-31, 1e-30, 1e-29, 
1e-28,
+    1e-27, 1e-26, 1e-25, 1e-24, 1e-23, 1e-22, 1e-21, 1e-20, 1e-19, 1e-18, 
1e-17,
+    1e-16, 1e-15, 1e-14, 1e-13, 1e-12, 1e-11, 1e-10, 1e-9,  1e-8,  1e-7,  1e-6,
+    1e-5,  1e-4,  1e-3,  1e-2,  1e-1,  1e0,   1e1,   1e2,   1e3,   1e4,   1e5,
+    1e6,   1e7,   1e8,   1e9,   1e10,  1e11,  1e12,  1e13,  1e14,  1e15,  1e16,
+    1e17,  1e18,  1e19,  1e20,  1e21,  1e22,  1e23,  1e24,  1e25,  1e26,  1e27,
+    1e28,  1e29,  1e30,  1e31,  1e32,  1e33,  1e34,  1e35,  1e36,  1e37,  
1e38};
+
+namespace {
+
+template <typename Real, typename Derived>
+struct Decimal128FromReal {
+  static Result<Decimal128> FromPositiveReal(Real real, int32_t precision,
+                                             int32_t scale) {
+    auto x = real;
+    if (scale >= -38 && scale <= 38) {
+      x *= Derived::powers_of_ten()[scale + 38];
+    } else {
+      x *= std::pow(static_cast<Real>(10), static_cast<Real>(scale));
+    }
+    x = std::nearbyint(x);
+    const auto max_abs = Derived::powers_of_ten()[precision + 38];
+    if (x <= -max_abs || x >= max_abs) {
+      return Status::Invalid("Cannot convert ", real,
+                             " to Decimal128(precision = ", precision,
+                             ", scale = ", scale, "): overflow");
+    }
+    // Extract high and low bits
+    const auto high = std::floor(std::ldexp(x, -64));
+    const auto low = x - std::ldexp(high, 64);
+
+    DCHECK_GE(high, -9.223372036854776e+18);  // -2**63
+    DCHECK_LT(high, 9.223372036854776e+18);   // 2**63
+    DCHECK_GE(low, 0);
+    DCHECK_LT(low, 1.8446744073709552e+19);  // 2**64
+    return Decimal128(static_cast<int64_t>(high), static_cast<uint64_t>(low));
+  }
+
+  static Result<Decimal128> FromReal(Real x, int32_t precision, int32_t scale) 
{
+    DCHECK_GT(precision, 0);
+    DCHECK_LE(precision, 38);
+
+    if (!std::isfinite(x)) {
+      return Status::Invalid("Cannot convert ", x, " to Decimal128");
+    }
+    if (x < 0) {
+      ARROW_ASSIGN_OR_RAISE(auto dec, FromPositiveReal(-x, precision, scale));
+      return dec.Negate();
+    } else {
+      // Includes negative zero
+      return FromPositiveReal(x, precision, scale);
+    }
+  }
+};
+
+struct Decimal128FromFloat : public Decimal128FromReal<float, 
Decimal128FromFloat> {
+  static constexpr const float* powers_of_ten() { return kFloatPowersOfTen; }
+};
+
+struct Decimal128FromDouble : public Decimal128FromReal<double, 
Decimal128FromDouble> {
+  static constexpr const double* powers_of_ten() { return kDoublePowersOfTen; }
+};
+
+}  // namespace
+
+Result<Decimal128> Decimal128::FromReal(float x, int32_t precision, int32_t 
scale) {
+  return Decimal128FromFloat::FromReal(x, precision, scale);
+}
+
+Result<Decimal128> Decimal128::FromReal(double x, int32_t precision, int32_t 
scale) {
+  return Decimal128FromDouble::FromReal(x, precision, scale);
+}
+
 std::string Decimal128::ToIntegerString() const {
   Decimal128 remainder;
   std::stringstream buf;
@@ -154,35 +266,13 @@ std::string Decimal128::ToString(int32_t scale) const {
   return "0." + std::string(static_cast<size_t>(scale - len), '0') + str;
 }
 
-static constexpr auto kInt64DecimalDigits =
-    static_cast<size_t>(std::numeric_limits<int64_t>::digits10);
-static constexpr int64_t kPowersOfTen[kInt64DecimalDigits + 1] = {1LL,
-                                                                  10LL,
-                                                                  100LL,
-                                                                  1000LL,
-                                                                  10000LL,
-                                                                  100000LL,
-                                                                  1000000LL,
-                                                                  10000000LL,
-                                                                  100000000LL,
-                                                                  1000000000LL,
-                                                                  
10000000000LL,
-                                                                  
100000000000LL,
-                                                                  
1000000000000LL,
-                                                                  
10000000000000LL,
-                                                                  
100000000000000LL,
-                                                                  
1000000000000000LL,
-                                                                  
10000000000000000LL,
-                                                                  
100000000000000000LL,
-                                                                  
1000000000000000000LL};
-
 // Iterates over data and for each group of kInt64DecimalDigits multiple out by
 // the appropriate power of 10 necessary to add source parsed as uint64 and
 // then adds the parsed value of source.
 static inline void ShiftAndAdd(const char* data, size_t length, Decimal128* 
out) {
   for (size_t posn = 0; posn < length;) {
     const size_t group_size = std::min(kInt64DecimalDigits, length - posn);
-    const int64_t multiple = kPowersOfTen[group_size];
+    const int64_t multiple = kInt64PowersOfTen[group_size];
     int64_t chunk = 0;
     ARROW_CHECK(internal::ParseValue<Int64Type>(data + posn, group_size, 
&chunk));
 
diff --git a/cpp/src/arrow/util/decimal.h b/cpp/src/arrow/util/decimal.h
index 8ce337f..d20a246 100644
--- a/cpp/src/arrow/util/decimal.h
+++ b/cpp/src/arrow/util/decimal.h
@@ -32,6 +32,8 @@ namespace arrow {
 
 /// Represents a signed 128-bit integer in two's complement.
 /// Calculations wrap around and overflow is ignored.
+/// The max decimal precision that can be safely represented is
+/// 38 significant digits.
 ///
 /// For a discussion of the algorithms, look at Knuth's volume 2,
 /// Semi-numerical Algorithms section 4.3.1.
@@ -101,6 +103,9 @@ class ARROW_EXPORT Decimal128 : public BasicDecimal128 {
   static Result<Decimal128> FromString(const std::string& s);
   static Result<Decimal128> FromString(const char* s);
 
+  static Result<Decimal128> FromReal(double real, int32_t precision, int32_t 
scale);
+  static Result<Decimal128> FromReal(float real, int32_t precision, int32_t 
scale);
+
   /// \brief Convert from a big-endian byte representation. The length must be
   ///        between 1 and 16.
   /// \return error status if the length is an invalid value
diff --git a/cpp/src/arrow/util/decimal_test.cc 
b/cpp/src/arrow/util/decimal_test.cc
index a152c76..e80eaab 100644
--- a/cpp/src/arrow/util/decimal_test.cc
+++ b/cpp/src/arrow/util/decimal_test.cc
@@ -349,6 +349,197 @@ TEST(Decimal128ParseTest, WithExponentAndNullptrScale) {
   ASSERT_OK_AND_EQ(expected_value, Decimal128::FromString("1.23E-8"));
 }
 
+template <typename Real>
+void CheckDecimalFromReal(Real real, int32_t precision, int32_t scale,
+                          const std::string& expected) {
+  ASSERT_OK_AND_ASSIGN(auto dec, Decimal128::FromReal(real, precision, scale));
+  ASSERT_EQ(dec.ToString(scale), expected);
+}
+
+template <typename Real>
+void CheckDecimalFromRealIntegerString(Real real, int32_t precision, int32_t 
scale,
+                                       const std::string& expected) {
+  ASSERT_OK_AND_ASSIGN(auto dec, Decimal128::FromReal(real, precision, scale));
+  ASSERT_EQ(dec.ToIntegerString(), expected);
+}
+
+template <typename Real>
+struct FromRealTestParam {
+  Real real;
+  int32_t precision;
+  int32_t scale;
+  std::string expected;
+};
+
+using FromFloatTestParam = FromRealTestParam<float>;
+using FromDoubleTestParam = FromRealTestParam<double>;
+
+// Common tests for Decimal128::FromReal(T, ...)
+template <typename T>
+class TestDecimalFromReal : public ::testing::Test {
+ public:
+  using ParamType = FromRealTestParam<T>;
+
+  void TestSuccess() {
+    const std::vector<ParamType> params{
+        // clang-format off
+        {0.0f, 1, 0, "0"},
+        {-0.0f, 1, 0, "0"},
+        {0.0f, 19, 4, "0.0000"},
+        {-0.0f, 19, 4, "0.0000"},
+        {123.0f, 7, 4, "123.0000"},
+        {-123.0f, 7, 4, "-123.0000"},
+        {456.78f, 7, 4, "456.7800"},
+        {-456.78f, 7, 4, "-456.7800"},
+        {456.784f, 5, 2, "456.78"},
+        {-456.784f, 5, 2, "-456.78"},
+        {456.786f, 5, 2, "456.79"},
+        {-456.786f, 5, 2, "-456.79"},
+        {999.99f, 5, 2, "999.99"},
+        {-999.99f, 5, 2, "-999.99"},
+        {123.0f, 19, 0, "123"},
+        {-123.0f, 19, 0, "-123"},
+        {123.4f, 19, 0, "123"},
+        {-123.4f, 19, 0, "-123"},
+        {123.6f, 19, 0, "124"},
+        {-123.6f, 19, 0, "-124"},
+        // 2**62
+        {4.611686e+18f, 19, 0, "4611686018427387904"},
+        {-4.611686e+18f, 19, 0, "-4611686018427387904"},
+        // 2**63
+        {9.223372e+18f, 19, 0, "9223372036854775808"},
+        {-9.223372e+18f, 19, 0, "-9223372036854775808"},
+        // 2**64
+        {1.8446744e+19f, 20, 0, "18446744073709551616"},
+        {-1.8446744e+19f, 20, 0, "-18446744073709551616"}
+        // clang-format on
+    };
+    for (const ParamType& param : params) {
+      CheckDecimalFromReal(param.real, param.precision, param.scale, 
param.expected);
+    }
+  }
+
+  void TestErrors() {
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(INFINITY, 19, 4));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(-INFINITY, 19, 4));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(NAN, 19, 4));
+    // Overflows
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(1000.0, 3, 0));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(-1000.0, 3, 0));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(1000.0, 5, 2));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(-1000.0, 5, 2));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(999.996, 5, 2));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(-999.996, 5, 2));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(1e+38, 38, 0));
+    ASSERT_RAISES(Invalid, Decimal128::FromReal(-1e+38, 38, 0));
+  }
+};
+
+using RealTypes = ::testing::Types<float, double>;
+TYPED_TEST_SUITE(TestDecimalFromReal, RealTypes);
+
+TYPED_TEST(TestDecimalFromReal, TestSuccess) { this->TestSuccess(); }
+
+TYPED_TEST(TestDecimalFromReal, TestErrors) { this->TestErrors(); }
+
+// Custom test for Decimal128::FromReal(float, ...)
+class TestDecimalFromRealFloat : public 
::testing::TestWithParam<FromFloatTestParam> {};
+
+TEST_P(TestDecimalFromRealFloat, SuccessConversion) {
+  const auto param = GetParam();
+  CheckDecimalFromReal(param.real, param.precision, param.scale, 
param.expected);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    TestDecimalFromRealFloat, TestDecimalFromRealFloat,
+    ::testing::Values(
+        // 2**63 + 2**40 (exactly representable in a float's 24 bits of 
precision)
+        FromFloatTestParam{9.223373e+18f, 19, 0, "9223373136366403584"},
+        FromFloatTestParam{-9.223373e+18f, 19, 0, "-9223373136366403584"},
+        FromFloatTestParam{9.223373e+14f, 19, 4, "922337313636640.3584"},
+        FromFloatTestParam{-9.223373e+14f, 19, 4, "-922337313636640.3584"},
+        // 2**64 - 2**40 (exactly representable in a float)
+        FromFloatTestParam{1.8446743e+19f, 20, 0, "18446742974197923840"},
+        FromFloatTestParam{-1.8446743e+19f, 20, 0, "-18446742974197923840"},
+        // 2**64 + 2**41 (exactly representable in a double)
+        FromFloatTestParam{1.8446746e+19f, 20, 0, "18446746272732807168"},
+        FromFloatTestParam{-1.8446746e+19f, 20, 0, "-18446746272732807168"},
+        FromFloatTestParam{1.8446746e+15f, 20, 4, "1844674627273280.7168"},
+        FromFloatTestParam{-1.8446746e+15f, 20, 4, "-1844674627273280.7168"},
+        // Almost 10**38 (minus 2**103)
+        FromFloatTestParam{9.999999e+37f, 38, 0,
+                           "99999986661652122824821048795547566080"},
+        FromFloatTestParam{-9.999999e+37f, 38, 0,
+                           "-99999986661652122824821048795547566080"}
+));
+// clang-format on
+
+TEST(TestDecimalFromRealFloat, LargeValues) {
+  // Test the entire float range
+  for (int32_t scale = -38; scale <= 38; ++scale) {
+    float real = std::pow(10.0f, static_cast<float>(scale));
+    CheckDecimalFromRealIntegerString(real, 1, -scale, "1");
+  }
+  for (int32_t scale = -37; scale <= 36; ++scale) {
+    float real = 123.f * std::pow(10.f, static_cast<float>(scale));
+    CheckDecimalFromRealIntegerString(real, 2, -scale - 1, "12");
+    CheckDecimalFromRealIntegerString(real, 3, -scale, "123");
+    CheckDecimalFromRealIntegerString(real, 4, -scale + 1, "1230");
+  }
+}
+
+// Custom test for Decimal128::FromReal(double, ...)
+class TestDecimalFromRealDouble : public 
::testing::TestWithParam<FromDoubleTestParam> {};
+
+TEST_P(TestDecimalFromRealDouble, SuccessConversion) {
+  const auto param = GetParam();
+  CheckDecimalFromReal(param.real, param.precision, param.scale, 
param.expected);
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+    TestDecimalFromRealDouble, TestDecimalFromRealDouble,
+    ::testing::Values(
+        // 2**63 + 2**11 (exactly representable in a double's 53 bits of 
precision)
+        FromDoubleTestParam{9.223372036854778e+18, 19, 0, 
"9223372036854777856"},
+        FromDoubleTestParam{-9.223372036854778e+18, 19, 0, 
"-9223372036854777856"},
+        FromDoubleTestParam{9.223372036854778e+10, 19, 8, 
"92233720368.54777856"},
+        FromDoubleTestParam{-9.223372036854778e+10, 19, 8, 
"-92233720368.54777856"},
+        // 2**64 - 2**11 (exactly representable in a double)
+        FromDoubleTestParam{1.844674407370955e+19, 20, 0, 
"18446744073709549568"},
+        FromDoubleTestParam{-1.844674407370955e+19, 20, 0, 
"-18446744073709549568"},
+        // 2**64 + 2**11 (exactly representable in a double)
+        FromDoubleTestParam{1.8446744073709556e+19, 20, 0, 
"18446744073709555712"},
+        FromDoubleTestParam{-1.8446744073709556e+19, 20, 0, 
"-18446744073709555712"},
+        FromDoubleTestParam{1.8446744073709556e+15, 20, 4, 
"1844674407370955.5712"},
+        FromDoubleTestParam{-1.8446744073709556e+15, 20, 4, 
"-1844674407370955.5712"},
+        // Almost 10**38 (minus 2**73)
+        FromDoubleTestParam{9.999999999999998e+37, 38, 0,
+                            "99999999999999978859343891977453174784"},
+        FromDoubleTestParam{-9.999999999999998e+37, 38, 0,
+                            "-99999999999999978859343891977453174784"},
+        FromDoubleTestParam{9.999999999999998e+27, 38, 10,
+                            "9999999999999997885934389197.7453174784"},
+        FromDoubleTestParam{-9.999999999999998e+27, 38, 10,
+                            "-9999999999999997885934389197.7453174784"}
+));
+// clang-format on
+
+TEST(TestDecimalFromRealDouble, LargeValues) {
+  // Test the entire double range
+  for (int32_t scale = -308; scale <= 308; ++scale) {
+    double real = std::pow(10.0, static_cast<double>(scale));
+    CheckDecimalFromRealIntegerString(real, 1, -scale, "1");
+  }
+  for (int32_t scale = -307; scale <= 306; ++scale) {
+    double real = 123. * std::pow(10.0, static_cast<double>(scale));
+    CheckDecimalFromRealIntegerString(real, 2, -scale - 1, "12");
+    CheckDecimalFromRealIntegerString(real, 3, -scale, "123");
+    CheckDecimalFromRealIntegerString(real, 4, -scale + 1, "1230");
+  }
+}
+
 TEST(Decimal128Test, TestSmallNumberFormat) {
   Decimal128 value("0.2");
   std::string expected("0.2");

Reply via email to