CNL  2.0.2 (development)
Compositional Numeric Library
to_chars.h
1 
2 // Copyright John McFarlane 2018.
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file ../LICENSE_1_0.txt or copy at
5 // http://www.boost.org/LICENSE_1_0.txt)
6 
7 #if !defined(CNL_IMPL_SCALED_INTEGER_TO_CHARS_H)
8 #define CNL_IMPL_SCALED_INTEGER_TO_CHARS_H
9 
10 #include "../../integer.h"
11 #include "../../rounding_integer.h"
12 #include "../charconv/constants.h"
13 #include "../charconv/descale.h"
14 #include "../cnl_assert.h"
15 #include "../cstdint/types.h"
16 #include "../num_traits/fixed_width_scale.h"
17 #include "../num_traits/to_rep.h"
18 #include "../scaled/power.h"
19 #include "../ssize.h"
20 #include "../ssizeof.h"
21 #include "definition.h"
22 #include "num_traits.h"
23 #include "numbers.h"
24 
25 #include <algorithm>
26 #include <array>
27 #include <cctype>
28 #include <charconv>
29 #include <iterator>
30 #include <span>
31 #include <system_error>
32 #include <tuple>
33 #include <type_traits>
34 #include <utility>
35 
37 namespace cnl {
38  namespace _impl {
39  constexpr auto num_digits_from_binary(int num_digits, int radix)
40  {
41  switch (radix) {
42  case 2:
43  return num_digits;
44  case 8:
45  return (num_digits + 2) / 3;
46  case 10:
47  return (num_digits * 1000 + 3322) / 3321;
48  case 16:
49  return (num_digits + 3) / 4;
50  default: {
51  auto const binary_digits_per_digit{used_digits(radix - 1)};
52  return (num_digits + binary_digits_per_digit - 1) / binary_digits_per_digit;
53  }
54  }
55  }
56 
57  constexpr auto num_digits_to_binary(int num_digits, int radix)
58  {
59  switch (radix) {
60  case 2:
61  return num_digits;
62  case 8:
63  return num_digits * 3;
64  case 10:
65  return (num_digits * 3322 + 678) / 1000;
66  case 16:
67  return num_digits * 4;
68  default:
69  return num_digits * used_digits(radix - 1);
70  }
71  }
72 
73  template<typename Rep, int Exponent, int Radix>
74  struct max_to_chars_chars<scaled_integer<Rep, power<Exponent, Radix>>> {
75  private:
77 
78  // This number is a little pessemistic in the case that Radix != 2.
79  static constexpr auto _fractional_digits =
80  std::max(cnl::_impl::fractional_digits<scalar>, 0);
81 
82  static constexpr auto _sign_chars = static_cast<int>(cnl::numbers::signedness_v<scalar>);
83  static constexpr auto _num_significant_integer_bits{cnl::digits_v<scalar> - _fractional_digits};
84  static constexpr auto _num_trailing_integer_bits{
85  num_digits_to_binary(std::max(0, Exponent), Radix)};
86  static constexpr auto _num_integer_bits{
87  _num_significant_integer_bits + _num_trailing_integer_bits};
88  static constexpr auto _integer_chars = num_digits_from_binary(_num_integer_bits, 10);
89  static constexpr auto _radix_chars = static_cast<int>(_fractional_digits > 0);
90  static constexpr auto _fractional_chars = std::max(0, _fractional_digits);
91 
92  public:
93  static constexpr auto value =
94  _sign_chars + _integer_chars + _radix_chars + _fractional_chars;
95  };
96 
97  struct descaled_info {
98  std::string_view significand_digits;
99  std::string_view exponent_chars;
100  std::span<char> output;
101  int num_significand_digits{};
102  int exponent{};
103  int max_chars{};
104  bool exponent_has_sign{};
105  };
106 
107  struct fixed_solution {
108  int num_significand_digits;
109  int num_chars;
110  int leading_zeros;
111  int trailing_zeros;
112  bool has_radix;
113  };
114 
115  [[nodiscard]] constexpr auto solve_fixed(descaled_info const& info)
116  {
117  auto const num_integer_digits{info.num_significand_digits + info.exponent};
118  if (num_integer_digits > info.max_chars) {
119  return fixed_solution{};
120  }
121 
122  auto const leading_zeros{std::max(0, -num_integer_digits)};
123  auto const has_radix{info.exponent < 0};
124  auto const trailing_zeros{std::max(0, info.exponent)};
125  auto const unbounded_num_chars{info.num_significand_digits + leading_zeros + has_radix + trailing_zeros};
126  auto const chars_truncated{std::max(0, unbounded_num_chars - info.max_chars)};
127  auto const truncated_significand_digits{info.num_significand_digits - chars_truncated};
128  auto const truncated_num_chars{unbounded_num_chars - chars_truncated};
129 
130  return fixed_solution{
131  truncated_significand_digits,
132  truncated_num_chars,
133  leading_zeros,
134  trailing_zeros,
135  has_radix};
136  }
137 
138  [[nodiscard]] constexpr auto fill(
139  descaled_info const& info,
140  fixed_solution const& solution)
141  {
142  auto out{std::begin(info.output)};
143 
144  auto significand_digits_first = std::begin(info.significand_digits);
145 
146  // copy part of significand before the period, including the sign
147  for (auto n{std::max(0, info.num_significand_digits + std::min(0, info.exponent))};
148  n;
149  --n) {
150  CNL_ASSERT(*significand_digits_first);
151  *out++ = *significand_digits_first++;
152  }
153 
154  if (solution.trailing_zeros) {
155  out = std::fill_n(out, solution.trailing_zeros, zero_char);
156  } else if (out < std::end(info.output)) { // NOLINT(hicpp-use-nullptr,modernize-use-nullptr)
157  if (solution.has_radix) {
158  *out++ = radix_char;
159  }
160 
161  out = std::fill_n(out, solution.leading_zeros, zero_char);
162 
163  // copy part of significand after the period
164  out = std::copy(
165  significand_digits_first,
166  std::next(std::begin(info.significand_digits), solution.num_significand_digits),
167  out);
168  }
169 
170  CNL_ASSERT(significand_digits_first <= std::end(info.significand_digits));
171  CNL_ASSERT(out == std::begin(info.output) + solution.num_chars);
172  CNL_ASSERT(out <= std::end(info.output)); // NOLINT(hicpp-use-nullptr,modernize-use-nullptr)
173  return std::to_chars_result{&*out, std::errc{}};
174  }
175 
176  struct scientific_solution {
177  int num_significand_digits;
178  int num_chars;
179  };
180 
181  [[nodiscard]] constexpr auto solve_scientific(descaled_info const& info)
182  {
183  auto const num_exponent_chars{_impl::ssize(info.exponent_chars)};
184 
185  auto const unbounded_num_chars{
186  info.num_significand_digits + _impl::ssizeof(radix_char) + _impl::ssizeof(e_char) + num_exponent_chars};
187  auto const chars_truncated{std::max(0, unbounded_num_chars - info.max_chars)};
188 
189  return scientific_solution{
190  info.num_significand_digits - chars_truncated,
191  unbounded_num_chars - chars_truncated};
192  }
193 
194  [[nodiscard]] constexpr auto fill(
195  descaled_info const& info,
196  scientific_solution const& solution)
197  {
198  auto out{std::begin(info.output)};
199 
200  auto significand_digits_first = std::begin(info.significand_digits);
201 
202  // copy part of significand after the period
203  while (!isdigit(*out++ = *significand_digits_first++)) {
204  CNL_ASSERT(out < std::end(info.output)); // NOLINT(hicpp-use-nullptr,modernize-use-nullptr)
205  }
206 
207  // add the radix point
208  *out++ = radix_char;
209 
210  // copy part of significand after the period
211  out = std::copy(
212  significand_digits_first,
213  std::next(std::begin(info.significand_digits), solution.num_significand_digits),
214  out);
215 
216  // add the 'e'
217  *out++ = e_char;
218 
219  // copy exponent value
220  out = std::copy(
221  std::begin(info.exponent_chars),
222  std::next(std::begin(info.exponent_chars), _impl::ssize(info.exponent_chars)),
223  out);
224 
225  CNL_ASSERT(out == std::begin(info.output) + solution.num_chars);
226  CNL_ASSERT(out <= std::end(info.output)); // NOLINT(hicpp-use-nullptr,modernize-use-nullptr)
227  return std::to_chars_result{&*out, std::errc{}};
228  }
229 
230  [[nodiscard]] inline constexpr auto to_chars_positive(
231  char* const first,
232  char* const last,
233  std::string_view const& significand_digits,
234  int exponent)
235  {
236  _impl::descaled_info info;
237  info.significand_digits = significand_digits;
238  info.output = std::span<char>{first, last};
239  info.num_significand_digits = _impl::ssize(info.significand_digits);
240  info.exponent = exponent;
241  info.max_chars = _impl::ssize(info.output);
242 
243  auto const exponent_chars_static{to_chars_static(exponent + info.num_significand_digits - 1)};
244  info.exponent_chars = std::string_view(
245  exponent_chars_static.chars.data(),
246  exponent_chars_static.length);
247  info.exponent_has_sign = info.exponent_chars[0] == _impl::minus_char;
248 
249  CNL_ASSERT(isdigit(info.exponent_chars[0]) || info.exponent_chars[0] == _impl::minus_char);
250 
251  auto const scientific_solution{_impl::solve_scientific(info)};
252  auto const fixed_solution{_impl::solve_fixed(info)};
253 
254  if (std::tuple{
255  scientific_solution.num_significand_digits,
256  -scientific_solution.num_chars}
257  > std::tuple{// NOLINT(hicpp-use-nullptr,modernize-use-nullptr)
258  fixed_solution.num_significand_digits,
259  -fixed_solution.num_chars}) {
260  CNL_ASSERT(scientific_solution.num_significand_digits > 0);
261  return _impl::fill(info, scientific_solution);
262  }
263 
264  if (fixed_solution.num_significand_digits > 0) {
265  return _impl::fill(info, fixed_solution);
266  }
267 
268  return std::to_chars_result{last, std::errc::value_too_large};
269  }
270 
271  template<integer Significand, int Radix>
272  [[nodiscard]] constexpr auto to_chars_non_zero(
273  char* const first,
274  char* const last,
275  descaled<Significand, Radix> const& descaled)
276  {
277  CNL_ASSERT(descaled.significand);
278 
279  auto const significand_chars_static{to_chars_static(descaled.significand)};
280  auto const significand_chars_cstr{significand_chars_static.chars.data()};
281  if (*significand_chars_cstr == minus_char) {
282  *first = minus_char;
283  return to_chars_positive(first + 1, last, std::string_view(significand_chars_cstr + 1, significand_chars_static.length - 1), descaled.exponent);
284  }
285 
286  return to_chars_positive(first, last, std::string_view(significand_chars_cstr, significand_chars_static.length), descaled.exponent);
287  }
288  }
289 
290  // a partial implementation of std::to_chars overloaded on cnl::scaled_integer;
291  // known to exhibit rounding errors; not yet tested with Radix!=2
292  template<integer Rep, int Exponent, int Radix>
293  [[nodiscard]] inline constexpr auto to_chars(
294  char* const first,
295  char* const last,
296  cnl::scaled_integer<Rep, power<Exponent, Radix>> const& value)
297  {
298  if (first == last) {
299  // buffer too small to contain "0"
300  return std::to_chars_result{last, std::errc::value_too_large};
301  }
302 
303  if (!value) {
304  // zero
305  *first = _impl::zero_char;
306  return std::to_chars_result{first + 1, std::errc{}};
307  }
308 
309  using significand_type = std::conditional_t<(digits_v<Rep> > digits_v<std::int64_t>), Rep, std::int64_t>;
310  auto const descaled{_impl::descale<significand_type, 10>(
311  _impl::to_rep(value), power<Exponent, Radix>{})};
312 
313  return _impl::to_chars_non_zero(first, last, descaled);
314  }
315 }
316 
317 #endif // CNL_IMPL_SCALED_INTEGER_TO_CHARS_H
std::isdigit
T isdigit(T... args)
num_traits.h
cnl::scaled_integer specializations of num_traits traits and similar
std::tuple
std::fill
T fill(T... args)
numbers.h
scaled_integer specializations of math constants from <numbers>
std::copy
T copy(T... args)
std::errc
std::int64_t
cnl
compositional numeric library
Definition: abort.h:15
std::min
T min(T... args)
std::begin
T begin(T... args)
cnl::scaled_integer
_impl::wrapper< Rep, Scale > scaled_integer
literal real number approximation that uses fixed-point arithmetic
Definition: definition.h:52
std::end
T end(T... args)
std::max
T max(T... args)
std::fill_n
T fill_n(T... args)
std::next
T next(T... args)