| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| #include "paimon/common/utils/decimal_utils.h" | |
| #include <algorithm> | |
| #include <cctype> | |
| #include <cstddef> | |
| #include <utility> | |
| #include "arrow/api.h" | |
| #include "arrow/util/basic_decimal.h" | |
| #include "arrow/util/decimal.h" | |
| #include "fmt/format.h" | |
| #include "paimon/common/utils/checked_cast.h" | |
| namespace paimon { | |
| Status DecimalUtils::CheckDecimalType(const arrow::DataType& type) { | |
| if (type.id() != arrow::Type::DECIMAL128) { | |
| return Status::Invalid(fmt::format("Invalid decimal type: {}", type.ToString())); | |
| } | |
| auto* decimal_type = checked_cast<const arrow::Decimal128Type*>(&type); | |
| if (decimal_type->precision() > Decimal::MAX_PRECISION || | |
| decimal_type->precision() < Decimal::MIN_PRECISION) { | |
| return Status::Invalid(fmt::format("Invalid decimal type, precision must in range [{}, {}]", | |
| Decimal::MIN_PRECISION, Decimal::MAX_PRECISION)); | |
| } | |
| if (decimal_type->precision() < decimal_type->scale()) { | |
| return Status::Invalid(fmt::format("Invalid decimal type {}, precision must >= scale", | |
| decimal_type->ToString())); | |
| } | |
| return Status::OK(); | |
| } | |
| std::optional<arrow::Decimal128> DecimalUtils::RescaleDecimalWithOverflowCheck( | |
| const arrow::Decimal128& src_decimal, int32_t src_scale, int32_t target_precision, | |
| int32_t target_scale) { | |
| arrow::Decimal128 target_decimal = src_decimal; | |
| if (src_scale < target_scale) { | |
| int32_t delta_scale = target_scale - src_scale; | |
| arrow::BasicDecimal128 min_bound = arrow::BasicDecimal128::GetMinSentinel(); | |
| arrow::BasicDecimal128 max_bound = arrow::BasicDecimal128::GetMaxSentinel(); | |
| min_bound /= arrow::BasicDecimal128::GetScaleMultiplier(delta_scale); | |
| max_bound /= arrow::BasicDecimal128::GetScaleMultiplier(delta_scale); | |
| // scaled_decimal may be overflow 128 bits | |
| // noted that, arrow::Decimal.Rescale() is not safe | |
| if (src_decimal > max_bound || src_decimal < min_bound) { | |
| return std::nullopt; | |
| } | |
| target_decimal = src_decimal.IncreaseScaleBy(delta_scale); | |
| } else if (src_scale > target_scale) { | |
| target_decimal = src_decimal.ReduceScaleBy(src_scale - target_scale, /*round=*/true); | |
| } | |
| if (!target_decimal.FitsInPrecision(target_precision)) { | |
| return std::nullopt; | |
| } | |
| return target_decimal; | |
| } | |
| Result<Decimal::int128_t> DecimalUtils::StrToInt128(const std::string& str) { | |
| try { | |
| size_t length = str.length(); | |
| if (length == 0) { | |
| return Status::Invalid("invalid string: [], cannot convert to int128"); | |
| } | |
| bool is_negative = str[0] == '-'; | |
| size_t posn = is_negative ? 1 : 0; | |
| if (posn == length) { | |
| return Status::Invalid( | |
| fmt::format("invalid string: [{}], cannot convert to int128", str)); | |
| } | |
| Decimal::uint128_t magnitude = 0; | |
| Decimal::uint128_t max_magnitude = (static_cast<Decimal::uint128_t>(1) << 127) - 1; | |
| if (is_negative) { | |
| max_magnitude += 1; | |
| } | |
| while (posn < length) { | |
| size_t group = std::min(18ul, length - posn); | |
| for (size_t i = 0; i < group; ++i) { | |
| if (!std::isdigit(static_cast<unsigned char>(str[posn + i]))) { | |
| return Status::Invalid( | |
| fmt::format("invalid string: [{}], cannot convert to int128", str)); | |
| } | |
| } | |
| uint64_t chunk = std::stoull(str.substr(posn, group)); | |
| uint64_t multiple = 1; | |
| for (size_t i = 0; i < group; ++i) { | |
| multiple *= 10; | |
| } | |
| if (magnitude > (max_magnitude - chunk) / multiple) { | |
| return Status::Invalid( | |
| fmt::format("invalid string: [{}], cannot convert to int128", str)); | |
| } | |
| magnitude = magnitude * multiple + chunk; | |
| posn += group; | |
| } | |
| if (is_negative) { | |
| if (magnitude == max_magnitude) { | |
| auto max_value = | |
| static_cast<Decimal::int128_t>((static_cast<Decimal::uint128_t>(1) << 127) - 1); | |
| return -max_value - 1; | |
| } | |
| return -static_cast<Decimal::int128_t>(magnitude); | |
| } | |
| return static_cast<Decimal::int128_t>(magnitude); | |
| } catch (...) { | |
| return Status::Invalid(fmt::format("invalid string: [{}], cannot convert to int128", str)); | |
| } | |
| } | |
| } // namespace paimon |