1 // Licensed to the Apache Software Foundation (ASF) under one
2 // or more contributor license agreements. See the NOTICE file
3 // distributed with this work for additional information
4 // regarding copyright ownership. The ASF licenses this file
5 // to you under the Apache License, Version 2.0 (the
6 // "License"); you may not use this file except in compliance
7 // with the License. You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing,
12 // software distributed under the License is distributed on an
13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 // KIND, either express or implied. See the License for the
15 // specific language governing permissions and limitations
21 #include "arrow/python/common.h"
22 #include "arrow/python/decimal.h"
23 #include "arrow/python/helpers.h"
24 #include "arrow/type_fwd.h"
25 #include "arrow/util/decimal.h"
26 #include "arrow/util/logging.h"
32 Status
ImportDecimalType(OwnedRef
* decimal_type
) {
33 OwnedRef decimal_module
;
34 RETURN_NOT_OK(ImportModule("decimal", &decimal_module
));
35 RETURN_NOT_OK(ImportFromModule(decimal_module
.obj(), "Decimal", decimal_type
));
39 Status
PythonDecimalToString(PyObject
* python_decimal
, std::string
* out
) {
40 // Call Python's str(decimal_object)
41 return PyObject_StdStringStr(python_decimal
, out
);
44 // \brief Infer the precision and scale of a Python decimal.Decimal instance
45 // \param python_decimal[in] An instance of decimal.Decimal
46 // \param precision[out] The value of the inferred precision
47 // \param scale[out] The value of the inferred scale
48 // \return The status of the operation
49 static Status
InferDecimalPrecisionAndScale(PyObject
* python_decimal
, int32_t* precision
,
51 DCHECK_NE(python_decimal
, NULLPTR
);
52 DCHECK_NE(precision
, NULLPTR
);
53 DCHECK_NE(scale
, NULLPTR
);
55 // TODO(phillipc): Make sure we perform PyDecimal_Check(python_decimal) as a DCHECK
56 OwnedRef
as_tuple(PyObject_CallMethod(python_decimal
, const_cast<char*>("as_tuple"),
57 const_cast<char*>("")));
59 DCHECK(PyTuple_Check(as_tuple
.obj()));
61 OwnedRef
digits(PyObject_GetAttrString(as_tuple
.obj(), "digits"));
63 DCHECK(PyTuple_Check(digits
.obj()));
65 const auto num_digits
= static_cast<int32_t>(PyTuple_Size(digits
.obj()));
68 OwnedRef
py_exponent(PyObject_GetAttrString(as_tuple
.obj(), "exponent"));
70 DCHECK(IsPyInteger(py_exponent
.obj()));
72 const auto exponent
= static_cast<int32_t>(PyLong_AsLong(py_exponent
.obj()));
76 // If exponent > num_digits, we have a number with leading zeros
77 // such as 0.01234. Ensure we have enough precision for leading zeros
78 // (which are not included in num_digits).
79 *precision
= std::max(num_digits
, -exponent
);
82 // Trailing zeros are not included in num_digits, need to add to precision.
83 // Note we don't generate negative scales as they are poorly supported
84 // in non-Arrow systems.
85 *precision
= num_digits
+ exponent
;
91 PyObject
* DecimalFromString(PyObject
* decimal_constructor
,
92 const std::string
& decimal_string
) {
93 DCHECK_NE(decimal_constructor
, nullptr);
95 auto string_size
= decimal_string
.size();
96 DCHECK_GT(string_size
, 0);
98 auto string_bytes
= decimal_string
.c_str();
99 DCHECK_NE(string_bytes
, nullptr);
101 return PyObject_CallFunction(decimal_constructor
, const_cast<char*>("s#"), string_bytes
,
102 static_cast<Py_ssize_t
>(string_size
));
107 template <typename ArrowDecimal
>
108 Status
DecimalFromStdString(const std::string
& decimal_string
,
109 const DecimalType
& arrow_type
, ArrowDecimal
* out
) {
110 int32_t inferred_precision
;
111 int32_t inferred_scale
;
113 RETURN_NOT_OK(ArrowDecimal::FromString(decimal_string
, out
, &inferred_precision
,
116 const int32_t precision
= arrow_type
.precision();
117 const int32_t scale
= arrow_type
.scale();
119 if (scale
!= inferred_scale
) {
120 DCHECK_NE(out
, NULLPTR
);
121 ARROW_ASSIGN_OR_RAISE(*out
, out
->Rescale(inferred_scale
, scale
));
124 auto inferred_scale_delta
= inferred_scale
- scale
;
125 if (ARROW_PREDICT_FALSE((inferred_precision
- inferred_scale_delta
) > precision
)) {
126 return Status::Invalid(
127 "Decimal type with precision ", inferred_precision
,
128 " does not fit into precision inferred from first array element: ", precision
);
134 template <typename ArrowDecimal
>
135 Status
InternalDecimalFromPythonDecimal(PyObject
* python_decimal
,
136 const DecimalType
& arrow_type
,
138 DCHECK_NE(python_decimal
, NULLPTR
);
139 DCHECK_NE(out
, NULLPTR
);
142 RETURN_NOT_OK(PythonDecimalToString(python_decimal
, &string
));
143 return DecimalFromStdString(string
, arrow_type
, out
);
146 template <typename ArrowDecimal
>
147 Status
InternalDecimalFromPyObject(PyObject
* obj
, const DecimalType
& arrow_type
,
149 DCHECK_NE(obj
, NULLPTR
);
150 DCHECK_NE(out
, NULLPTR
);
152 if (IsPyInteger(obj
)) {
153 // TODO: add a fast path for small-ish ints
155 RETURN_NOT_OK(PyObject_StdStringStr(obj
, &string
));
156 return DecimalFromStdString(string
, arrow_type
, out
);
157 } else if (PyDecimal_Check(obj
)) {
158 return InternalDecimalFromPythonDecimal
<ArrowDecimal
>(obj
, arrow_type
, out
);
160 return Status::TypeError("int or Decimal object expected, got ",
161 Py_TYPE(obj
)->tp_name
);
167 Status
DecimalFromPythonDecimal(PyObject
* python_decimal
, const DecimalType
& arrow_type
,
169 return InternalDecimalFromPythonDecimal(python_decimal
, arrow_type
, out
);
172 Status
DecimalFromPyObject(PyObject
* obj
, const DecimalType
& arrow_type
,
174 return InternalDecimalFromPyObject(obj
, arrow_type
, out
);
177 Status
DecimalFromPythonDecimal(PyObject
* python_decimal
, const DecimalType
& arrow_type
,
179 return InternalDecimalFromPythonDecimal(python_decimal
, arrow_type
, out
);
182 Status
DecimalFromPyObject(PyObject
* obj
, const DecimalType
& arrow_type
,
184 return InternalDecimalFromPyObject(obj
, arrow_type
, out
);
187 bool PyDecimal_Check(PyObject
* obj
) {
188 static OwnedRef decimal_type
;
189 if (!decimal_type
.obj()) {
190 ARROW_CHECK_OK(ImportDecimalType(&decimal_type
));
191 DCHECK(PyType_Check(decimal_type
.obj()));
193 // PyObject_IsInstance() is slower as it has to check for virtual subclasses
195 PyType_IsSubtype(Py_TYPE(obj
), reinterpret_cast<PyTypeObject
*>(decimal_type
.obj()));
196 ARROW_CHECK_NE(result
, -1) << " error during PyType_IsSubtype check";
200 bool PyDecimal_ISNAN(PyObject
* obj
) {
201 DCHECK(PyDecimal_Check(obj
)) << "obj is not an instance of decimal.Decimal";
203 PyObject_CallMethod(obj
, const_cast<char*>("is_nan"), const_cast<char*>("")));
204 return PyObject_IsTrue(is_nan
.obj()) == 1;
207 DecimalMetadata::DecimalMetadata()
208 : DecimalMetadata(std::numeric_limits
<int32_t>::min(),
209 std::numeric_limits
<int32_t>::min()) {}
211 DecimalMetadata::DecimalMetadata(int32_t precision
, int32_t scale
)
212 : precision_(precision
), scale_(scale
) {}
214 Status
DecimalMetadata::Update(int32_t suggested_precision
, int32_t suggested_scale
) {
215 const int32_t current_scale
= scale_
;
216 scale_
= std::max(current_scale
, suggested_scale
);
218 const int32_t current_precision
= precision_
;
220 if (current_precision
== std::numeric_limits
<int32_t>::min()) {
221 precision_
= suggested_precision
;
223 auto num_digits
= std::max(current_precision
- current_scale
,
224 suggested_precision
- suggested_scale
);
225 precision_
= std::max(num_digits
+ scale_
, current_precision
);
231 Status
DecimalMetadata::Update(PyObject
* object
) {
232 bool is_decimal
= PyDecimal_Check(object
);
234 if (ARROW_PREDICT_FALSE(!is_decimal
|| PyDecimal_ISNAN(object
))) {
238 int32_t precision
= 0;
240 RETURN_NOT_OK(InferDecimalPrecisionAndScale(object
, &precision
, &scale
));
241 return Update(precision
, scale
);
244 } // namespace internal