Files
ortools-clone/ortools/util/fp_roundtrip_conv.cc
Corentin Le Molgat c34026b101 Bump copyright to 2025
note: done using
```sh
git grep -l "2010-2024 Google" | xargs sed -i 's/2010-2024 Google/2010-2025 Google/'
```
2025-01-10 11:33:35 +01:00

154 lines
5.8 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed 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 "ortools/util/fp_roundtrip_conv.h"
#include <array>
#include <charconv>
#include <limits>
#include <ostream>
#include <string>
#include <system_error> // NOLINT(build/c++11)
#include <type_traits>
#include <utility>
#include "absl/base/attributes.h"
#include "absl/log/check.h"
#include "absl/status/statusor.h"
#include "absl/strings/charconv.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "ortools/base/logging.h"
#include "ortools/base/status_builder.h"
namespace operations_research {
namespace {
// Template type to test if std::to_chars() has an overload for the given
// type. This source will use it only with `T = double` but for SFINAE to work
// we must have a generic version.
template <typename T, typename = void>
struct std_to_chars_has_overload : std::false_type {};
template <typename T>
struct std_to_chars_has_overload<
T, std::void_t<decltype(std::to_chars(
std::declval<char*>(), std::declval<char*>(), std::declval<T>()))>>
: std::true_type {};
// Alias to get std_to_chars_has_overload<T>::value directly.
template <typename T>
inline constexpr bool std_to_chars_has_overload_v =
std_to_chars_has_overload<T>::value;
// When using std::to_chars(), the maximum number of digits for the mantissa is
// std::numeric_limits<double>::max_digits10, which is 17. On top of that the
// max_exponent10 is 308, which takes at most 3 digits. We also have to take
// into account the "e+"/"e-", the "-" sign and the ".". Thus the buffer must be
// at least 17 + 3 + 2 + 1 + 1 = 24 bytes long.
//
// When using absl::SNPrintF() with "%.*g" with `max_digits10` for the
// precision, it prints at most `precision + 4` digits. The +4 occurs for
// numbers d.ddddd...eX where -1 <= X <= -4 since in that case the number is
// printed with some leading zeros (i.e. 0.000dddd...). Thus we must have a
// string at least 28 bytes long.
//
// To be safe we had some margin and use a buffer of 4 * 8 bytes.
using RoundTripDoubleBuffer = std::array<char, 32>;
// Writes the double to the provided buffer and returns a view on the written
// range.
//
// We have two overloads selected with std::enable_if; one used when
// std::to_chars() supports double, the other when not.
template <
typename Double = double,
typename std::enable_if_t<std_to_chars_has_overload_v<Double>, bool> = true>
absl::string_view RoundTripDoubleToBuffer(const Double value,
RoundTripDoubleBuffer& buffer) {
const auto result =
std::to_chars(buffer.data(), buffer.data() + buffer.size(), value);
CHECK(result.ec == std::errc()) << std::make_error_code(result.ec).message();
return absl::string_view(buffer.data(), result.ptr - buffer.data());
}
// Overload for the case where std::to_chars() does not support double.
//
// Here we use a version that use enough digits to have roundtrip. We lose the
// specification that we use the shortest string though.
template <typename Double = double,
typename std::enable_if_t<!std_to_chars_has_overload_v<Double>,
bool> = true>
absl::string_view RoundTripDoubleToBuffer(const Double value,
RoundTripDoubleBuffer& buffer) {
// We use absl::SNPrintF() since it does not depend on the locale (contrary to
// std::snprintf()).
const int written =
absl::SNPrintF(buffer.data(), buffer.size(), "%.*g",
std::numeric_limits<double>::max_digits10, value);
CHECK_GT(written, 0);
CHECK_LT(written, buffer.size());
return absl::string_view(buffer.data(), written);
}
} // namespace
ABSL_CONST_INIT const bool kStdToCharsDoubleIsSupported =
std_to_chars_has_overload_v<double>;
std::ostream& operator<<(std::ostream& out,
const RoundTripDoubleFormat& format) {
RoundTripDoubleBuffer buffer;
out << RoundTripDoubleToBuffer(format.value_, buffer);
return out;
}
std::string RoundTripDoubleFormat::ToString(const double value) {
RoundTripDoubleBuffer buffer;
return std::string(RoundTripDoubleToBuffer(value, buffer));
}
absl::StatusOr<double> RoundTripDoubleFormat::Parse(
const absl::string_view str_value) {
const char* const begin = str_value.data();
const char* const end = begin + str_value.size();
double ret = 0.0;
const auto result = absl::from_chars(begin, end, ret);
if (result.ec == std::errc()) {
if (result.ptr != end) {
return util::InvalidArgumentErrorBuilder()
<< '"' << absl::CEscape(str_value)
<< "\" has unexpected suffix starting at index "
<< (result.ptr - begin);
}
return ret;
}
switch (result.ec) {
case std::errc::invalid_argument:
return util::InvalidArgumentErrorBuilder()
<< '"' << absl::CEscape(str_value) << "\" is not a valid double";
case std::errc::result_out_of_range:
return util::InvalidArgumentErrorBuilder()
<< '"' << absl::CEscape(str_value)
<< "\" does not fit in a double precision float";
default:
return util::InternalErrorBuilder()
<< "parsing of \"" << absl::CEscape(str_value)
<< "\" failed with an unexpected error: "
<< std::make_error_code(result.ec).message();
}
}
} // namespace operations_research