Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions doc/modules/ROOT/pages/strings.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,39 @@ They construct the value as though calling `from_chars` without a specified form
If the input string is invalid these constructors will `throw std::runtime_error`.
If you are using a no exceptions environment instead of throwing the constructor will return a Quiet NAN.

== Conversions from `std::string`

[source,c++]
----
#include <boost/decimal/string.hpp>

namespace boost {
namespace decimal {

inline auto stod32(const std::string& str, std::size_t* idx = nullptr) -> decimal32_t;

inline auto stod32f(const std::string& str, std::size_t* idx = nullptr) -> decimal_fast32_t;

inline auto stod64(const std::string& str, std::size_t* idx = nullptr) -> decimal64_t;

inline auto stod64f(const std::string& str, std::size_t* idx = nullptr) -> decimal_fast64_t;

inline auto stod128(const std::string& str, std::size_t* idx = nullptr) -> decimal128_t;

inline auto stod128f(const std::string& str, std::size_t* idx = nullptr) -> decimal_fast128_t

} // namespace decimal
} // namespace boost
----

Attempts conversion of `str` to the decimal type specified as if with `from_chars(str, idx)` subject to:

. Overflow throws `std::out_of_range` or in a no exceptions environment returns `std::numeric_limits<DecimalType>::signaling_NaN()` with unset value of `idx`.

. If the string can not be converted into a decimal value throws `std::out_of_range` or in a no exceptions environment returns `std::numeric_limits<DecimalType>::signaling_NaN()` with unset value of `idx`.

The returned value `idx` is the number of characters.

== `to_string`

[source, c++]
Expand Down
62 changes: 62 additions & 0 deletions include/boost/decimal/string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,68 @@
namespace boost {
namespace decimal {

namespace detail {

template <typename DecimalType>
auto from_string_impl(const std::string& str, std::size_t* idx)
BOOST_DECIMAL_REQUIRES(detail::is_decimal_floating_point_v, DecimalType)
{
DecimalType val;
const auto r {from_chars(str, val)};

if (r.ec == std::errc::result_out_of_range || val == std::numeric_limits<DecimalType>::infinity())
{
val = std::numeric_limits<DecimalType>::signaling_NaN();
BOOST_DECIMAL_THROW_EXCEPTION(std::out_of_range("Conversion is outside the range of the type"));
}
else if (r.ec != std::errc{})
{
val = std::numeric_limits<DecimalType>::signaling_NaN();
BOOST_DECIMAL_THROW_EXCEPTION(std::invalid_argument("Conversion could not be performed"));
}
else
{
if (idx != nullptr)
{
*idx = static_cast<std::size_t>(r.ptr - str.data());
}
}

return val;
}

} // namespace detail

BOOST_DECIMAL_EXPORT inline auto stod32(const std::string& str, std::size_t* idx = nullptr) -> decimal32_t
{
return detail::from_string_impl<decimal32_t>(str, idx);
}

BOOST_DECIMAL_EXPORT inline auto stod32f(const std::string& str, std::size_t* idx = nullptr) -> decimal_fast32_t
{
return detail::from_string_impl<decimal_fast32_t>(str, idx);
}

BOOST_DECIMAL_EXPORT inline auto stod64(const std::string& str, std::size_t* idx = nullptr) -> decimal64_t
{
return detail::from_string_impl<decimal64_t>(str, idx);
}

BOOST_DECIMAL_EXPORT inline auto stod64f(const std::string& str, std::size_t* idx = nullptr) -> decimal_fast64_t
{
return detail::from_string_impl<decimal_fast64_t>(str, idx);
}

BOOST_DECIMAL_EXPORT inline auto stod128(const std::string& str, std::size_t* idx = nullptr) -> decimal128_t
{
return detail::from_string_impl<decimal128_t>(str, idx);
}

BOOST_DECIMAL_EXPORT inline auto stod128f(const std::string& str, std::size_t* idx = nullptr) -> decimal_fast128_t
{
return detail::from_string_impl<decimal_fast128_t>(str, idx);
}

BOOST_DECIMAL_EXPORT template <typename DecimalType>
auto to_string(const DecimalType value)
BOOST_DECIMAL_REQUIRES_RETURN(detail::is_decimal_floating_point_v, DecimalType, std::string)
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ run-fail test_fprintf.cpp ;
run test_frexp_ldexp.cpp ;
run test_from_chars.cpp /boost/charconv//boost_charconv ;
run test_from_chars_nan_payloads.cpp ;
run test_from_string.cpp ;
run test_git_issue_266.cpp ;
run test_git_issue_271.cpp ;
run test_hash.cpp ;
Expand Down
123 changes: 123 additions & 0 deletions test/test_from_string.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2025 Matt Borland
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt

#include <boost/decimal.hpp>
#include <boost/core/lightweight_test.hpp>
#include <random>
#include <string>

using namespace boost::decimal;

static std::mt19937_64 rng {42};
static constexpr std::size_t N {1024};

template <typename T>
T recover_value(const std::string& str, std::size_t* ptr);

template <>
decimal32_t recover_value<decimal32_t>(const std::string& str, std::size_t* ptr)
{
return stod32(str, ptr);
}

template <>
decimal_fast32_t recover_value<decimal_fast32_t>(const std::string& str, std::size_t* ptr)
{
return stod32f(str, ptr);
}

template <>
decimal64_t recover_value<decimal64_t>(const std::string& str, std::size_t* ptr)
{
return stod64(str, ptr);
}

template <>
decimal_fast64_t recover_value<decimal_fast64_t>(const std::string& str, std::size_t* ptr)
{
return stod64f(str, ptr);
}

template <>
decimal128_t recover_value<decimal128_t>(const std::string& str, std::size_t* ptr)
{
return stod128(str, ptr);
}

template <>
decimal_fast128_t recover_value<decimal_fast128_t>(const std::string& str, std::size_t* ptr)
{
return stod128f(str, ptr);
}

template <typename T>
void test()
{
std::uniform_int_distribution<std::int32_t> sig_dist {-9'999'999, 9'999'999};
std::uniform_int_distribution<std::int32_t> exp_dist {-50, 50};

for (std::size_t i {}; i < N; ++i)
{
const T val {sig_dist(rng), exp_dist(rng)};

char buffer[64];
const auto r {to_chars(buffer, buffer + sizeof(buffer), val)};
BOOST_TEST(r);
const auto dist {static_cast<std::size_t>(r.ptr - buffer)};

*r.ptr = '\0';
const std::string str {buffer};
std::size_t idx {};
const T return_value {recover_value<T>(str, &idx)};

BOOST_TEST_EQ(return_value, val);
BOOST_TEST_EQ(idx, dist);
}
}

inline void test_overflow_path()
{
const std::string str {"INF"};

#ifndef BOOST_DECIMAL_DISABLE_EXCEPTIONS

BOOST_TEST_THROWS(recover_value<decimal32_t>(str, nullptr), std::out_of_range);

#else

BOOST_TEST(isnan(recover_value<decimal32_t>(str, nullptr)));

#endif
}

inline void test_invalid_path()
{
const std::string str {"JUNK"};

#ifndef BOOST_DECIMAL_DISABLE_EXCEPTIONS

BOOST_TEST_THROWS(recover_value<decimal32_t>(str, nullptr), std::invalid_argument);

#else

BOOST_TEST(isnan(recover_value<decimal32_t>(str, nullptr)));

#endif
}

int main()
{
test<decimal32_t>();
test<decimal_fast32_t>();
test<decimal64_t>();
test<decimal_fast64_t>();
test<decimal128_t>();
test<decimal_fast128_t>();

test_overflow_path();
test_invalid_path();

return boost::report_errors();
}