@@ -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