]> git.proxmox.com Git - ceph.git/blob - ceph/src/arrow/cpp/src/arrow/python/decimal.cc
bump version to 18.2.2-pve1
[ceph.git] / ceph / src / arrow / cpp / src / arrow / python / decimal.cc
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
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
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
16 // under the License.
17
18 #include <algorithm>
19 #include <limits>
20
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"
27
28 namespace arrow {
29 namespace py {
30 namespace internal {
31
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));
36 return Status::OK();
37 }
38
39 Status PythonDecimalToString(PyObject* python_decimal, std::string* out) {
40 // Call Python's str(decimal_object)
41 return PyObject_StdStringStr(python_decimal, out);
42 }
43
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,
50 int32_t* scale) {
51 DCHECK_NE(python_decimal, NULLPTR);
52 DCHECK_NE(precision, NULLPTR);
53 DCHECK_NE(scale, NULLPTR);
54
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*>("")));
58 RETURN_IF_PYERROR();
59 DCHECK(PyTuple_Check(as_tuple.obj()));
60
61 OwnedRef digits(PyObject_GetAttrString(as_tuple.obj(), "digits"));
62 RETURN_IF_PYERROR();
63 DCHECK(PyTuple_Check(digits.obj()));
64
65 const auto num_digits = static_cast<int32_t>(PyTuple_Size(digits.obj()));
66 RETURN_IF_PYERROR();
67
68 OwnedRef py_exponent(PyObject_GetAttrString(as_tuple.obj(), "exponent"));
69 RETURN_IF_PYERROR();
70 DCHECK(IsPyInteger(py_exponent.obj()));
71
72 const auto exponent = static_cast<int32_t>(PyLong_AsLong(py_exponent.obj()));
73 RETURN_IF_PYERROR();
74
75 if (exponent < 0) {
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);
80 *scale = -exponent;
81 } else {
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;
86 *scale = 0;
87 }
88 return Status::OK();
89 }
90
91 PyObject* DecimalFromString(PyObject* decimal_constructor,
92 const std::string& decimal_string) {
93 DCHECK_NE(decimal_constructor, nullptr);
94
95 auto string_size = decimal_string.size();
96 DCHECK_GT(string_size, 0);
97
98 auto string_bytes = decimal_string.c_str();
99 DCHECK_NE(string_bytes, nullptr);
100
101 return PyObject_CallFunction(decimal_constructor, const_cast<char*>("s#"), string_bytes,
102 static_cast<Py_ssize_t>(string_size));
103 }
104
105 namespace {
106
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;
112
113 RETURN_NOT_OK(ArrowDecimal::FromString(decimal_string, out, &inferred_precision,
114 &inferred_scale));
115
116 const int32_t precision = arrow_type.precision();
117 const int32_t scale = arrow_type.scale();
118
119 if (scale != inferred_scale) {
120 DCHECK_NE(out, NULLPTR);
121 ARROW_ASSIGN_OR_RAISE(*out, out->Rescale(inferred_scale, scale));
122 }
123
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);
129 }
130
131 return Status::OK();
132 }
133
134 template <typename ArrowDecimal>
135 Status InternalDecimalFromPythonDecimal(PyObject* python_decimal,
136 const DecimalType& arrow_type,
137 ArrowDecimal* out) {
138 DCHECK_NE(python_decimal, NULLPTR);
139 DCHECK_NE(out, NULLPTR);
140
141 std::string string;
142 RETURN_NOT_OK(PythonDecimalToString(python_decimal, &string));
143 return DecimalFromStdString(string, arrow_type, out);
144 }
145
146 template <typename ArrowDecimal>
147 Status InternalDecimalFromPyObject(PyObject* obj, const DecimalType& arrow_type,
148 ArrowDecimal* out) {
149 DCHECK_NE(obj, NULLPTR);
150 DCHECK_NE(out, NULLPTR);
151
152 if (IsPyInteger(obj)) {
153 // TODO: add a fast path for small-ish ints
154 std::string string;
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);
159 } else {
160 return Status::TypeError("int or Decimal object expected, got ",
161 Py_TYPE(obj)->tp_name);
162 }
163 }
164
165 } // namespace
166
167 Status DecimalFromPythonDecimal(PyObject* python_decimal, const DecimalType& arrow_type,
168 Decimal128* out) {
169 return InternalDecimalFromPythonDecimal(python_decimal, arrow_type, out);
170 }
171
172 Status DecimalFromPyObject(PyObject* obj, const DecimalType& arrow_type,
173 Decimal128* out) {
174 return InternalDecimalFromPyObject(obj, arrow_type, out);
175 }
176
177 Status DecimalFromPythonDecimal(PyObject* python_decimal, const DecimalType& arrow_type,
178 Decimal256* out) {
179 return InternalDecimalFromPythonDecimal(python_decimal, arrow_type, out);
180 }
181
182 Status DecimalFromPyObject(PyObject* obj, const DecimalType& arrow_type,
183 Decimal256* out) {
184 return InternalDecimalFromPyObject(obj, arrow_type, out);
185 }
186
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()));
192 }
193 // PyObject_IsInstance() is slower as it has to check for virtual subclasses
194 const int result =
195 PyType_IsSubtype(Py_TYPE(obj), reinterpret_cast<PyTypeObject*>(decimal_type.obj()));
196 ARROW_CHECK_NE(result, -1) << " error during PyType_IsSubtype check";
197 return result == 1;
198 }
199
200 bool PyDecimal_ISNAN(PyObject* obj) {
201 DCHECK(PyDecimal_Check(obj)) << "obj is not an instance of decimal.Decimal";
202 OwnedRef is_nan(
203 PyObject_CallMethod(obj, const_cast<char*>("is_nan"), const_cast<char*>("")));
204 return PyObject_IsTrue(is_nan.obj()) == 1;
205 }
206
207 DecimalMetadata::DecimalMetadata()
208 : DecimalMetadata(std::numeric_limits<int32_t>::min(),
209 std::numeric_limits<int32_t>::min()) {}
210
211 DecimalMetadata::DecimalMetadata(int32_t precision, int32_t scale)
212 : precision_(precision), scale_(scale) {}
213
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);
217
218 const int32_t current_precision = precision_;
219
220 if (current_precision == std::numeric_limits<int32_t>::min()) {
221 precision_ = suggested_precision;
222 } else {
223 auto num_digits = std::max(current_precision - current_scale,
224 suggested_precision - suggested_scale);
225 precision_ = std::max(num_digits + scale_, current_precision);
226 }
227
228 return Status::OK();
229 }
230
231 Status DecimalMetadata::Update(PyObject* object) {
232 bool is_decimal = PyDecimal_Check(object);
233
234 if (ARROW_PREDICT_FALSE(!is_decimal || PyDecimal_ISNAN(object))) {
235 return Status::OK();
236 }
237
238 int32_t precision = 0;
239 int32_t scale = 0;
240 RETURN_NOT_OK(InferDecimalPrecisionAndScale(object, &precision, &scale));
241 return Update(precision, scale);
242 }
243
244 } // namespace internal
245 } // namespace py
246 } // namespace arrow