Skip to content

Commit 9d09df9

Browse files
authored
Merge pull request #1219 from cppalliance/1184
Enable string constructor to parse payloads of NANs
2 parents 53b2c7b + 14d4bbf commit 9d09df9

File tree

10 files changed

+258
-52
lines changed

10 files changed

+258
-52
lines changed

include/boost/decimal/charconv.hpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <boost/decimal/detail/countl.hpp>
2727
#include <boost/decimal/detail/remove_trailing_zeros.hpp>
2828
#include <boost/decimal/detail/promotion.hpp>
29+
#include <boost/decimal/detail/write_payload.hpp>
2930

3031
#ifndef BOOST_DECIMAL_BUILD_MODULE
3132
#include <cstdint>
@@ -81,13 +82,26 @@ constexpr auto from_chars_general_impl(const char* first, const char* last, Targ
8182
{
8283
if (r.ec == std::errc::not_supported)
8384
{
84-
if (significand)
85+
using resultant_sig_type = typename TargetDecimalType::significand_type;
86+
87+
resultant_sig_type payload_value {};
88+
if (significand < std::numeric_limits<resultant_sig_type>::max())
89+
{
90+
payload_value = static_cast<resultant_sig_type>(significand);
91+
}
92+
93+
if (expval > 0)
8594
{
86-
value = std::numeric_limits<TargetDecimalType>::signaling_NaN();
95+
value = write_payload<TargetDecimalType, true>(payload_value);
8796
}
8897
else
8998
{
90-
value = std::numeric_limits<TargetDecimalType>::quiet_NaN();
99+
value = write_payload<TargetDecimalType, false>(payload_value);
100+
}
101+
102+
if (sign)
103+
{
104+
value = -value;
91105
}
92106

93107
r.ec = std::errc();

include/boost/decimal/decimal_fast128_t.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ template <BOOST_DECIMAL_DECIMAL_FLOATING_TYPE TargetDecimalType>
7474
constexpr auto to_chars_cohort_preserving_scientific(char* first, char* last, const TargetDecimalType& value) noexcept -> to_chars_result;
7575

7676
template <typename TargetDecimalType, bool is_snan>
77-
constexpr auto nan_impl(const char* arg) noexcept
77+
constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value)
7878
BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType);
7979

8080
} // namespace detail
@@ -196,7 +196,7 @@ BOOST_DECIMAL_EXPORT class decimal_fast128_t final
196196
friend constexpr auto detail::to_chars_cohort_preserving_scientific(char* first, char* last, const TargetDecimalType& value) noexcept -> to_chars_result;
197197

198198
template <typename TargetDecimalType, bool is_snan>
199-
friend constexpr auto detail::nan_impl(const char* arg) noexcept
199+
friend constexpr auto detail::write_payload(typename TargetDecimalType::significand_type payload_value)
200200
BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType);
201201

202202
template <typename T>

include/boost/decimal/decimal_fast32_t.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ template <bool checked, BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T>
7272
constexpr auto d32_fma_impl(T x, T y, T z) noexcept -> T;
7373

7474
template <typename TargetDecimalType, bool is_snan>
75-
constexpr auto nan_impl(const char* arg) noexcept
75+
constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value)
7676
BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType);
7777

7878
} // namespace detail
@@ -196,7 +196,7 @@ BOOST_DECIMAL_EXPORT class decimal_fast32_t final
196196
friend constexpr auto detail::generic_div_impl(const T& lhs, const T& rhs) noexcept -> DecimalType;
197197

198198
template <typename TargetDecimalType, bool is_snan>
199-
friend constexpr auto detail::nan_impl(const char* arg) noexcept
199+
friend constexpr auto detail::write_payload(typename TargetDecimalType::significand_type payload_value)
200200
BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType);
201201

202202
template <typename T>

include/boost/decimal/decimal_fast64_t.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ template <bool checked, BOOST_DECIMAL_DECIMAL_FLOATING_TYPE T>
7474
constexpr auto d64_fma_impl(T x, T y, T z) noexcept -> T;
7575

7676
template <typename TargetDecimalType, bool is_snan>
77-
constexpr auto nan_impl(const char* arg) noexcept
77+
constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value)
7878
BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType);
7979

8080
} // namespace detail
@@ -203,7 +203,7 @@ BOOST_DECIMAL_EXPORT class decimal_fast64_t final
203203
friend constexpr auto detail::d64_fma_impl(T x, T y, T z) noexcept -> T;
204204

205205
template <typename TargetDecimalType, bool is_snan>
206-
friend constexpr auto detail::nan_impl(const char* arg) noexcept
206+
friend constexpr auto detail::write_payload(typename TargetDecimalType::significand_type payload_value)
207207
BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType);
208208

209209
template <typename T>

include/boost/decimal/detail/cmath/nan.hpp

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <boost/decimal/detail/concepts.hpp>
1212
#include <boost/decimal/detail/utilities.hpp>
1313
#include <boost/decimal/detail/promotion.hpp>
14+
#include <boost/decimal/detail/write_payload.hpp>
1415
#include <boost/decimal/cstdlib.hpp>
1516

1617
#ifndef BOOST_DECIMAL_BUILD_MODULE
@@ -33,23 +34,16 @@ constexpr auto nan_impl(const char* arg) noexcept
3334
constexpr TargetDecimalType nan_type {is_snan ? std::numeric_limits<TargetDecimalType>::signaling_NaN() :
3435
std::numeric_limits<TargetDecimalType>::quiet_NaN()};
3536

36-
constexpr std::uint32_t significand_field_bits {decimal_val_v<TargetDecimalType> < 64 ? 23U :
37-
decimal_val_v<TargetDecimalType> < 128 ? 53U : 110U};
38-
39-
constexpr sig_type max_payload_value {(static_cast<sig_type>(1) << significand_field_bits) - 1U};
40-
4137
sig_type payload_value {};
4238
const auto r {from_chars_integer_impl<sig_type, sig_type>(arg, arg + detail::strlen(arg), payload_value, 10)};
4339

44-
TargetDecimalType return_value {nan_type};
45-
if (!r || payload_value > max_payload_value)
40+
if (!r)
4641
{
47-
return return_value;
42+
return nan_type;
4843
}
4944
else
5045
{
51-
return_value.significand_ |= payload_value;
52-
return return_value;
46+
return write_payload<TargetDecimalType, is_snan>(payload_value);
5347
}
5448
}
5549

@@ -62,23 +56,16 @@ constexpr auto nan_impl(const char* arg) noexcept
6256
constexpr TargetDecimalType nan_type {is_snan ? std::numeric_limits<TargetDecimalType>::signaling_NaN() :
6357
std::numeric_limits<TargetDecimalType>::quiet_NaN()};
6458

65-
constexpr std::uint32_t significand_field_bits {sizeof(TargetDecimalType) == sizeof(std::uint32_t) ? 23U :
66-
sizeof(TargetDecimalType) == sizeof(std::uint64_t) ? 53U : 110U};
67-
68-
constexpr sig_type max_payload_value {(static_cast<sig_type>(1) << significand_field_bits) - 1U};
69-
constexpr TargetDecimalType zero {};
70-
constexpr TargetDecimalType zero_bits {zero ^ zero};
71-
7259
sig_type payload_value {};
7360
const auto r {from_chars_integer_impl<sig_type, sig_type>(arg, arg + detail::strlen(arg), payload_value, 10)};
7461

75-
if (!r || payload_value > max_payload_value)
62+
if (!r)
7663
{
7764
return nan_type;
7865
}
7966
else
8067
{
81-
return (zero_bits | payload_value) | nan_type;
68+
return write_payload<TargetDecimalType, is_snan>(payload_value);
8269
}
8370
}
8471

include/boost/decimal/detail/parser.hpp

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,15 @@ constexpr auto parser(const char* first, const char* last, bool& sign, Unsigned_
9898
sign = false;
9999
}
100100

101+
constexpr std::size_t significand_buffer_size = std::numeric_limits<Unsigned_Integer>::digits10 ;
102+
char significand_buffer[significand_buffer_size] {};
103+
101104
// Handle non-finite values
102105
// Stl allows for string like "iNf" to return inf
103106
//
104107
// This is nested ifs rather than a big one-liner to ensure that once we hit an invalid character
105108
// or an end of buffer we return the correct value of next
109+
bool signaling {};
106110
if (next != last && (*next == 'i' || *next == 'I'))
107111
{
108112
++next;
@@ -112,14 +116,21 @@ constexpr auto parser(const char* first, const char* last, bool& sign, Unsigned_
112116
if (next != last && (*next == 'f' || *next == 'F'))
113117
{
114118
++next;
115-
significand = 0;
119+
exponent = 0;
116120
return {next, std::errc::value_too_large};
117121
}
118122
}
119123

120124
return {first, std::errc::invalid_argument};
121125
}
122-
else if (next != last && (*next == 'n' || *next == 'N'))
126+
127+
if (next != last && (*next == 's' || *next == 'S'))
128+
{
129+
++next;
130+
signaling = true;
131+
}
132+
133+
if (next != last && (*next == 'n' || *next == 'N'))
123134
{
124135
++next;
125136
if (next != last && (*next == 'a' || *next == 'A'))
@@ -128,56 +139,97 @@ constexpr auto parser(const char* first, const char* last, bool& sign, Unsigned_
128139
if (next != last && (*next == 'n' || *next == 'N'))
129140
{
130141
++next;
131-
if (next != last && (*next == '('))
142+
if (next != last)
132143
{
133144
const auto current_pos {next};
134-
++next;
145+
146+
bool any_valid_char {false};
147+
bool has_opening_brace {false};
148+
if (*next == '(')
149+
{
150+
++next;
151+
has_opening_brace = true;
152+
}
135153

136154
// Handle nan(SNAN)
137155
if ((last - next) >= 4 && (*next == 's' || *next == 'S') && (*(next + 1) == 'n' || *(next + 1) == 'N')
138156
&& (*(next + 2) == 'a' || *(next + 2) == 'A') && (*(next + 3) == 'n' || *(next + 3) == 'N'))
139157
{
140-
next += 3;
141-
significand = 1;
158+
next += 4;
159+
signaling = true;
160+
any_valid_char = true;
142161
}
143162
// Handle Nan(IND)
144163
else if ((last - next) >= 3 && (*next == 'i' || *next == 'I') && (*(next + 1) == 'n' || *(next + 1) == 'N')
145164
&& (*(next + 2) == 'd' || *(next + 2) == 'D'))
146165
{
147-
next += 2;
148-
significand = 0;
166+
next += 3;
167+
sign = true;
168+
any_valid_char = true;
149169
}
150170

151-
// Arbitrary payload
152-
bool valid_payload {false};
171+
// Arbitrary numerical payload
172+
bool has_numerical_payload {false};
173+
auto significand_buffer_first {significand_buffer};
174+
std::size_t significand_characters {};
153175
while (next != last && (*next != ')'))
154176
{
155-
if (is_payload_char(*next))
177+
if (significand_characters < significand_buffer_size && is_integer_char(*next))
156178
{
157-
++next;
158-
valid_payload = true;
179+
++significand_characters;
180+
*significand_buffer_first++ = *next++;
181+
any_valid_char = true;
182+
has_numerical_payload = true;
159183
}
160184
else
161185
{
162-
valid_payload = false;
186+
// End of valid payload even if there are more characters
187+
// e.g. SNAN42JUNK stops at J
163188
break;
164189
}
165190
}
166191

167-
if (valid_payload)
192+
// Non-numerical payload still needs to be parsed
193+
// e.g. nan(PAYLOAD)
194+
if (!has_numerical_payload && has_opening_brace)
195+
{
196+
while (next != last && (*next != ')'))
197+
{
198+
if (is_payload_char(*next))
199+
{
200+
any_valid_char = true;
201+
++next;
202+
}
203+
else
204+
{
205+
break;
206+
}
207+
}
208+
}
209+
210+
if (next != last && any_valid_char)
168211
{
212+
// One past the end if we need to
169213
++next;
170214
}
171-
else
215+
216+
if (significand_characters != 0)
217+
{
218+
from_chars_dispatch(significand_buffer, significand_buffer + significand_characters, significand, 10);
219+
}
220+
221+
if (!any_valid_char)
172222
{
223+
// If we have nan(..BAD..) we should point to (
173224
next = current_pos;
174225
}
175226

227+
exponent = static_cast<Integer>(signaling);
176228
return {next, std::errc::not_supported};
177229
}
178230
else
179231
{
180-
significand = 0;
232+
exponent = static_cast<Integer>(signaling);
181233
return {next, std::errc::not_supported};
182234
}
183235
}
@@ -204,8 +256,6 @@ constexpr auto parser(const char* first, const char* last, bool& sign, Unsigned_
204256
}
205257

206258
// Next we get the significand
207-
constexpr std::size_t significand_buffer_size = std::numeric_limits<Unsigned_Integer>::digits10 ;
208-
char significand_buffer[significand_buffer_size] {};
209259
std::size_t i = 0;
210260
std::size_t dot_position = 0;
211261
Integer extra_zeros = 0;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2025 Matt Borland
2+
// Distributed under the Boost Software License, Version 1.0.
3+
// https://www.boost.org/LICENSE_1_0.txt
4+
5+
#ifndef BOOST_DECIMAL_DETAIL_WRITE_PAYLOAD_HPP
6+
#define BOOST_DECIMAL_DETAIL_WRITE_PAYLOAD_HPP
7+
8+
#include <boost/decimal/detail/config.hpp>
9+
#include <boost/decimal/detail/concepts.hpp>
10+
#include <boost/decimal/detail/promotion.hpp>
11+
12+
namespace boost {
13+
namespace decimal {
14+
namespace detail {
15+
16+
template <typename TargetDecimalType, bool is_snan>
17+
constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value)
18+
BOOST_DECIMAL_REQUIRES(detail::is_fast_type_v, TargetDecimalType)
19+
{
20+
using sig_type = typename TargetDecimalType::significand_type;
21+
22+
constexpr TargetDecimalType nan_type {is_snan ? std::numeric_limits<TargetDecimalType>::signaling_NaN() :
23+
std::numeric_limits<TargetDecimalType>::quiet_NaN()};
24+
25+
constexpr std::uint32_t significand_field_bits {decimal_val_v<TargetDecimalType> < 64 ? 23U :
26+
decimal_val_v<TargetDecimalType> < 128 ? 53U : 110U};
27+
28+
constexpr sig_type max_payload_value {(static_cast<sig_type>(1) << significand_field_bits) - 1U};
29+
30+
TargetDecimalType return_value {nan_type};
31+
if (payload_value < max_payload_value)
32+
{
33+
return_value.significand_ |= payload_value;
34+
}
35+
36+
return return_value;
37+
}
38+
39+
template <typename TargetDecimalType, bool is_snan>
40+
constexpr auto write_payload(typename TargetDecimalType::significand_type payload_value)
41+
BOOST_DECIMAL_REQUIRES(detail::is_ieee_type_v, TargetDecimalType)
42+
{
43+
using sig_type = typename TargetDecimalType::significand_type;
44+
45+
constexpr TargetDecimalType nan_type {is_snan ? std::numeric_limits<TargetDecimalType>::signaling_NaN() :
46+
std::numeric_limits<TargetDecimalType>::quiet_NaN()};
47+
48+
constexpr std::uint32_t significand_field_bits {decimal_val_v<TargetDecimalType> < 64 ? 23U :
49+
decimal_val_v<TargetDecimalType> < 128 ? 53U : 110U};
50+
51+
constexpr sig_type max_payload_value {(static_cast<sig_type>(1) << significand_field_bits) - 1U};
52+
53+
constexpr TargetDecimalType zero {};
54+
constexpr TargetDecimalType zero_bits {zero ^ zero};
55+
56+
TargetDecimalType return_value {nan_type};
57+
if (payload_value < max_payload_value)
58+
{
59+
return_value = (zero_bits | payload_value) | nan_type;
60+
}
61+
62+
return return_value;
63+
}
64+
65+
} // namespace detail
66+
} // namespace decimal
67+
} // namespace boost
68+
69+
#endif // BOOST_DECIMAL_DETAIL_WRITE_PAYLOAD_HPP

test/Jamfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ run test_format_fmtlib.cpp ;
147147
run-fail test_fprintf.cpp ;
148148
run test_frexp_ldexp.cpp ;
149149
run test_from_chars.cpp /boost/charconv//boost_charconv ;
150+
run test_from_chars_nan_payloads.cpp ;
150151
run test_git_issue_266.cpp ;
151152
run test_git_issue_271.cpp ;
152153
run test_hash.cpp ;

0 commit comments

Comments
 (0)