Skip to content

Commit c98f81a

Browse files
committed
Add and base-10 specialization
1 parent 1e0b907 commit c98f81a

File tree

2 files changed

+266
-4
lines changed

2 files changed

+266
-4
lines changed

include/boost/decimal/charconv.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ BOOST_DECIMAL_CONSTEXPR auto to_chars_scientific_impl(char* first, char* last, c
324324
}
325325

326326
// Offset the value of first by 1 so that we can copy the leading digit and insert a decimal point
327-
auto r = to_chars_integer_impl<uint_type, uint_type>(first + 1, last, significand, 10);
327+
auto r = to_chars_integer_impl<uint_type>(first + 1, last, significand);
328328

329329
// Only real reason we will hit this is a buffer overflow
330330
if (BOOST_DECIMAL_UNLIKELY(!r))
@@ -398,7 +398,7 @@ BOOST_DECIMAL_CONSTEXPR auto to_chars_scientific_impl(char* first, char* last, c
398398
*first++ = '0';
399399
}
400400

401-
r = to_chars_integer_impl<int, unsigned>(first, last, abs_exp, 10);
401+
r = to_chars_integer_impl<int>(first, last, abs_exp);
402402
if (BOOST_DECIMAL_UNLIKELY(!r))
403403
{
404404
return r; // LCOV_EXCL_LINE
@@ -544,7 +544,7 @@ BOOST_DECIMAL_CONSTEXPR auto to_chars_fixed_impl(char* first, char* last, const
544544
std::numeric_limits<std::uint64_t>::digits),
545545
int128::uint128_t, std::uint64_t>;
546546

547-
auto r = to_chars_integer_impl<uint_type, uint_type>(first, last, significand, 10);
547+
auto r = to_chars_integer_impl<uint_type>(first, last, significand);
548548

549549
if (BOOST_DECIMAL_UNLIKELY(!r))
550550
{
@@ -775,7 +775,7 @@ BOOST_DECIMAL_CONSTEXPR auto to_chars_hex_impl(char* first, char* last, const Ta
775775
*first++ = '0';
776776
}
777777

778-
return to_chars_integer_impl<std::uint32_t, std::uint32_t>(first, last, static_cast<std::uint32_t>(abs_exp), 10);
778+
return to_chars_integer_impl<std::uint32_t>(first, last, static_cast<std::uint32_t>(abs_exp));
779779
}
780780

781781
#ifdef _MSC_VER

include/boost/decimal/detail/to_chars_integer_impl.hpp

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,268 @@ BOOST_DECIMAL_CONSTEXPR auto to_chars_integer_impl(char* first, char* last, Inte
105105
return {first + num_chars, std::errc()};
106106
}
107107

108+
// Specialization for base-10
109+
110+
BOOST_DECIMAL_CONSTEXPR_VARIABLE char radix_table[] = {
111+
'0', '0', '0', '1', '0', '2', '0', '3', '0', '4',
112+
'0', '5', '0', '6', '0', '7', '0', '8', '0', '9',
113+
'1', '0', '1', '1', '1', '2', '1', '3', '1', '4',
114+
'1', '5', '1', '6', '1', '7', '1', '8', '1', '9',
115+
'2', '0', '2', '1', '2', '2', '2', '3', '2', '4',
116+
'2', '5', '2', '6', '2', '7', '2', '8', '2', '9',
117+
'3', '0', '3', '1', '3', '2', '3', '3', '3', '4',
118+
'3', '5', '3', '6', '3', '7', '3', '8', '3', '9',
119+
'4', '0', '4', '1', '4', '2', '4', '3', '4', '4',
120+
'4', '5', '4', '6', '4', '7', '4', '8', '4', '9',
121+
'5', '0', '5', '1', '5', '2', '5', '3', '5', '4',
122+
'5', '5', '5', '6', '5', '7', '5', '8', '5', '9',
123+
'6', '0', '6', '1', '6', '2', '6', '3', '6', '4',
124+
'6', '5', '6', '6', '6', '7', '6', '8', '6', '9',
125+
'7', '0', '7', '1', '7', '2', '7', '3', '7', '4',
126+
'7', '5', '7', '6', '7', '7', '7', '8', '7', '9',
127+
'8', '0', '8', '1', '8', '2', '8', '3', '8', '4',
128+
'8', '5', '8', '6', '8', '7', '8', '8', '8', '9',
129+
'9', '0', '9', '1', '9', '2', '9', '3', '9', '4',
130+
'9', '5', '9', '6', '9', '7', '9', '8', '9', '9'
131+
};
132+
133+
// See: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/
134+
// https://arxiv.org/abs/2101.11408
135+
constexpr char* decompose32(std::uint32_t value, char* buffer) noexcept
136+
{
137+
constexpr auto mask = (UINT64_C(1) << 57) - 1;
138+
auto y = value * UINT64_C(1441151881);
139+
140+
for (std::size_t i {}; i < 10; i += 2)
141+
{
142+
boost::decimal::detail::memcpy(buffer + i, radix_table + static_cast<std::size_t>(y >> 57) * 2, 2);
143+
y &= mask;
144+
y *= 100U;
145+
}
146+
147+
return buffer + 10;
148+
}
149+
150+
#ifdef _MSC_VER
151+
# pragma warning(push)
152+
# pragma warning(disable: 4127 4146)
153+
#endif
154+
155+
template <typename Integer>
156+
constexpr to_chars_result to_chars_integer_impl(char* first, char* last, Integer value) noexcept
157+
{
158+
using Unsigned_Integer = typename std::make_unsigned<Integer>::type;
159+
Unsigned_Integer unsigned_value {};
160+
161+
char buffer[10] {};
162+
int converted_value_digits {};
163+
bool is_negative = false;
164+
165+
if (first > last)
166+
{
167+
return {last, std::errc::invalid_argument};
168+
}
169+
170+
// Strip the sign from the value and apply at the end after parsing if the type is signed
171+
BOOST_DECIMAL_IF_CONSTEXPR (std::is_signed<Integer>::value)
172+
{
173+
if (value < 0)
174+
{
175+
is_negative = true;
176+
unsigned_value = apply_sign(value);
177+
}
178+
else
179+
{
180+
unsigned_value = static_cast<Unsigned_Integer>(value);
181+
}
182+
}
183+
else
184+
{
185+
unsigned_value = static_cast<Unsigned_Integer>(value);
186+
}
187+
188+
const std::ptrdiff_t user_buffer_size = last - first - static_cast<std::ptrdiff_t>(is_negative);
189+
190+
// If the type is less than 32 bits we can use this without change
191+
// If the type is greater than 32 bits we use a binary search tree to figure out how many digits
192+
// are present and then decompose the value into two (or more) std::uint32_t of known length so that we
193+
// don't have the issue of removing leading zeros from the least significant digits
194+
195+
// Yields: warning C4127: conditional expression is constant because first half of the expression is constant,
196+
// but we need to short circuit to avoid UB on the second half
197+
if (std::numeric_limits<Integer>::digits <= std::numeric_limits<std::uint32_t>::digits ||
198+
unsigned_value <= static_cast<Unsigned_Integer>((std::numeric_limits<std::uint32_t>::max)()))
199+
{
200+
const auto converted_value = static_cast<std::uint32_t>(unsigned_value);
201+
converted_value_digits = num_digits(converted_value);
202+
203+
if (converted_value_digits > user_buffer_size)
204+
{
205+
return {last, std::errc::value_too_large};
206+
}
207+
208+
decompose32(converted_value, buffer);
209+
210+
if (is_negative)
211+
{
212+
*first++ = '-';
213+
}
214+
215+
boost::decimal::detail::memcpy(first, buffer + (sizeof(buffer) - static_cast<unsigned>(converted_value_digits)),
216+
static_cast<std::size_t>(converted_value_digits));
217+
}
218+
else if (std::numeric_limits<Integer>::digits <= std::numeric_limits<std::uint64_t>::digits ||
219+
static_cast<std::uint64_t>(unsigned_value) <= (std::numeric_limits<std::uint64_t>::max)())
220+
{
221+
auto converted_value = static_cast<std::uint64_t>(unsigned_value);
222+
converted_value_digits = num_digits(converted_value);
223+
224+
if (converted_value_digits > user_buffer_size)
225+
{
226+
return {last, std::errc::value_too_large};
227+
}
228+
229+
if (is_negative)
230+
{
231+
*first++ = '-';
232+
}
233+
234+
// Only store 9 digits in each to avoid overflow
235+
if (num_digits(converted_value) <= 18)
236+
{
237+
const auto x = static_cast<std::uint32_t>(converted_value / UINT64_C(1000000000));
238+
const auto y = static_cast<std::uint32_t>(converted_value % UINT64_C(1000000000));
239+
const int first_value_chars = num_digits(x);
240+
241+
decompose32(x, buffer);
242+
boost::decimal::detail::memcpy(first, buffer + (sizeof(buffer) - static_cast<unsigned>(first_value_chars)),
243+
static_cast<std::size_t>(first_value_chars));
244+
245+
decompose32(y, buffer);
246+
boost::decimal::detail::memcpy(first + first_value_chars, buffer + 1, sizeof(buffer) - 1);
247+
}
248+
else
249+
{
250+
const auto x = static_cast<std::uint32_t>(converted_value / UINT64_C(100000000000));
251+
converted_value -= x * UINT64_C(100000000000);
252+
const auto y = static_cast<std::uint32_t>(converted_value / UINT64_C(100));
253+
const auto z = static_cast<std::uint32_t>(converted_value % UINT64_C(100));
254+
255+
if (converted_value_digits == 19)
256+
{
257+
decompose32(x, buffer);
258+
boost::decimal::detail::memcpy(first, buffer + 2, sizeof(buffer) - 2);
259+
260+
decompose32(y, buffer);
261+
boost::decimal::detail::memcpy(first + 8, buffer + 1, sizeof(buffer) - 1);
262+
263+
// Always prints 2 digits last
264+
boost::decimal::detail::memcpy(first + 17, radix_table + z * 2, 2);
265+
}
266+
else // 20
267+
{
268+
decompose32(x, buffer);
269+
boost::decimal::detail::memcpy(first, buffer + 1, sizeof(buffer) - 1);
270+
271+
decompose32(y, buffer);
272+
boost::decimal::detail::memcpy(first + 9, buffer + 1, sizeof(buffer) - 1);
273+
274+
// Always prints 2 digits last
275+
boost::decimal::detail::memcpy(first + 18, radix_table + z * 2, 2);
276+
}
277+
}
278+
}
279+
280+
return {first + converted_value_digits, std::errc()};
281+
}
282+
283+
template <typename Integer, typename Unsigned_Integer = boost::int128::uint128_t>
284+
constexpr to_chars_result to_chars_128integer_impl(char* first, char* last, Integer value) noexcept
285+
{
286+
Unsigned_Integer unsigned_value {};
287+
288+
const std::ptrdiff_t user_buffer_size = last - first;
289+
BOOST_DECIMAL_ATTRIBUTE_UNUSED bool is_negative = false;
290+
291+
if (first > last)
292+
{
293+
return {last, std::errc::invalid_argument};
294+
}
295+
296+
// Strip the sign from the value and apply at the end after parsing if the type is signed
297+
BOOST_DECIMAL_IF_CONSTEXPR (std::numeric_limits<Integer>::is_signed
298+
#ifdef BOOST_DECIMAL_HAS_INT128
299+
|| std::is_same<boost::decimal::detail::builtin_uint128_t, Integer>::value
300+
#endif
301+
)
302+
{
303+
if (value < 0)
304+
{
305+
is_negative = true;
306+
unsigned_value = -(static_cast<Unsigned_Integer>(value));
307+
}
308+
else
309+
{
310+
unsigned_value = static_cast<Unsigned_Integer>(value);
311+
}
312+
}
313+
else
314+
{
315+
unsigned_value = static_cast<Unsigned_Integer>(value);
316+
}
317+
318+
auto converted_value = static_cast<Unsigned_Integer>(unsigned_value);
319+
320+
const int converted_value_digits = num_digits(converted_value);
321+
322+
if (converted_value_digits > user_buffer_size)
323+
{
324+
return {last, std::errc::value_too_large};
325+
}
326+
327+
if (is_negative)
328+
{
329+
*first++ = '-';
330+
}
331+
332+
// If the value fits into 64 bits use the other method of processing
333+
if (converted_value < (std::numeric_limits<std::uint64_t>::max)())
334+
{
335+
return to_chars_integer_impl(first, last, static_cast<std::uint64_t>(value));
336+
}
337+
338+
constexpr std::uint32_t ten_9 = UINT32_C(1000000000);
339+
char buffer[5][10] {};
340+
int num_chars[5] {};
341+
int i = 0;
342+
343+
while (converted_value != 0)
344+
{
345+
auto digits = static_cast<std::uint32_t>(converted_value % ten_9);
346+
num_chars[i] = num_digits(digits);
347+
decompose32(digits, buffer[i]); // Always returns 10 digits (to include leading 0s) which we want
348+
converted_value = (converted_value - digits) / ten_9;
349+
++i;
350+
}
351+
352+
--i;
353+
auto offset = static_cast<std::size_t>(num_chars[i]);
354+
boost::decimal::detail::memcpy(first, buffer[i] + 10 - offset, offset);
355+
356+
while (i > 0)
357+
{
358+
--i;
359+
boost::decimal::detail::memcpy(first + offset, buffer[i] + 1, 9);
360+
offset += 9;
361+
}
362+
363+
return {first + converted_value_digits, std::errc()};
364+
}
365+
366+
#ifdef _MSC_VER
367+
# pragma warning(pop)
368+
#endif
369+
108370
#if defined(__GNUC__) && __GNUC__ == 7
109371
#pragma GCC diagnostic pop
110372
#endif

0 commit comments

Comments
 (0)