1// Copyright 2022 Peter Dimov
2// Distributed under the Boost Software License, Version 1.0.
3// https://www.boost.org/LICENSE_1_0.txt
4
5#include <boost/config.hpp>
6
7#ifdef BOOST_HAS_INT128
8
9// We need to define these operator<< overloads before
10// including boost/core/lightweight_test.hpp, or they
11// won't be visible to BOOST_TEST_EQ
12// LCOV_EXCL_START
13
14#include <ostream>
15
16static char* mini_to_chars( char (&buffer)[ 64 ], boost::uint128_type v )
17{
18 char* p = buffer + 64;
19 *--p = '\0';
20
21 do
22 {
23 *--p = "0123456789"[ v % 10 ];
24 v /= 10;
25 }
26 while ( v != 0 );
27
28 return p;
29}
30
31std::ostream& operator<<( std::ostream& os, boost::uint128_type v )
32{
33 char buffer[ 64 ];
34
35 os << mini_to_chars( buffer, v );
36 return os;
37}
38
39std::ostream& operator<<( std::ostream& os, boost::int128_type v )
40{
41 char buffer[ 64 ];
42 char* p;
43
44 if( v >= 0 )
45 {
46 p = mini_to_chars( buffer, v: static_cast<boost::uint128_type>(v) );
47 }
48 else
49 {
50 p = mini_to_chars( buffer, v: -static_cast<boost::uint128_type>(v) );
51 *--p = '-';
52 }
53
54 os << p;
55 return os;
56}
57
58// LCOV_EXCL_STOP
59
60#endif // #ifdef BOOST_HAS_INT128
61
62#include <boost/charconv.hpp>
63#include <boost/core/lightweight_test.hpp>
64#include <boost/core/detail/splitmix64.hpp>
65#include <system_error>
66#include <iostream>
67#include <iomanip>
68#include <limits>
69#include <numeric>
70#include <cstdint>
71#include <cfloat>
72#include <cmath>
73
74int const N = 1024;
75
76static boost::detail::splitmix64 rng;
77
78// integral types, random values
79
80#if defined(__GNUC__) && (__GNUC__ >= 12)
81# pragma GCC diagnostic push
82# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
83#endif
84
85template<class T> void test_roundtrip( T value, int base )
86{
87 char buffer[ 256 ];
88
89 auto r = boost::charconv::to_chars( buffer, buffer + sizeof( buffer ), value, base );
90
91 BOOST_TEST( r.ec == std::errc() );
92
93 T v2 = 0;
94 auto r2 = boost::charconv::from_chars( buffer, r.ptr, v2, base );
95
96 if( BOOST_TEST( r2.ec == std::errc() ) && BOOST_TEST( v2 == value ) )
97 {
98 }
99 else
100 {
101 std::cerr << "... test failure for value=" << value << "; buffer='" << std::string( buffer, r.ptr ) << "'" << std::endl; // LCOV_EXCL_LINE
102 }
103}
104
105#if defined(__GNUC__) && (__GNUC__ == 12)
106# pragma GCC diagnostic pop
107#endif
108
109template<class T> void test_roundtrip_int8( int base )
110{
111 for( int i = -256; i <= 255; ++i )
112 {
113 test_roundtrip( static_cast<T>( i ), base );
114 }
115}
116
117template<class T> void test_roundtrip_uint8( int base )
118{
119 for( int i = 0; i <= 256; ++i )
120 {
121 test_roundtrip( static_cast<T>( i ), base );
122 }
123}
124
125template<class T> void test_roundtrip_int16( int base )
126{
127 test_roundtrip_int8<T>( base );
128
129 for( int i = 0; i < N; ++i )
130 {
131 std::int16_t w = static_cast<std::int16_t>( rng() );
132 test_roundtrip( static_cast<T>( w ), base );
133 }
134}
135
136template<class T> void test_roundtrip_uint16( int base )
137{
138 test_roundtrip_uint8<T>( base );
139
140 for( int i = 0; i < N; ++i )
141 {
142 std::uint16_t w = static_cast<std::uint16_t>( rng() );
143 test_roundtrip( static_cast<T>( w ), base );
144 }
145}
146
147template<class T> void test_roundtrip_int32( int base )
148{
149 test_roundtrip_int16<T>( base );
150
151 for( int i = 0; i < N; ++i )
152 {
153 std::int32_t w = static_cast<std::int32_t>( rng() );
154 test_roundtrip( static_cast<T>( w ), base );
155 }
156}
157
158template<class T> void test_roundtrip_uint32( int base )
159{
160 test_roundtrip_uint16<T>( base );
161
162 for( int i = 0; i < N; ++i )
163 {
164 std::uint32_t w = static_cast<std::uint32_t>( rng() );
165 test_roundtrip( static_cast<T>( w ), base );
166 }
167}
168
169template<class T> void test_roundtrip_int64( int base )
170{
171 test_roundtrip_int32<T>( base );
172
173 for( int i = 0; i < N; ++i )
174 {
175 std::int64_t w = static_cast<std::int64_t>( rng() );
176 test_roundtrip( static_cast<T>( w ), base );
177 }
178}
179
180template<class T> void test_roundtrip_uint64( int base )
181{
182 test_roundtrip_uint32<T>( base );
183
184 for( int i = 0; i < N; ++i )
185 {
186 std::uint64_t w = static_cast<std::uint64_t>( rng() );
187 test_roundtrip( static_cast<T>( w ), base );
188 }
189}
190
191#ifdef BOOST_CHARCONV_HAS_INT128
192
193inline boost::uint128_type concatenate(std::uint64_t word1, std::uint64_t word2)
194{
195 return static_cast<boost::uint128_type>(word1) << 64 | word2;
196}
197
198template<class T> void test_roundtrip_int128( int base )
199{
200 for( int i = 0; i < N; ++i )
201 {
202 boost::int128_type w = static_cast<boost::int128_type>( concatenate(word1: rng(), word2: rng()) );
203 test_roundtrip( static_cast<T>( w ), base );
204 }
205}
206
207template<class T> void test_roundtrip_uint128( int base )
208{
209 for( int i = 0; i < N; ++i )
210 {
211 boost::uint128_type w = static_cast<boost::uint128_type>( concatenate(word1: rng(), word2: rng()) );
212 test_roundtrip( static_cast<T>( w ), base );
213 }
214}
215
216#endif // #ifdef BOOST_CHARCONV_HAS_INT128
217
218// integral types, boundary values
219
220template<class T> void test_roundtrip_bv( int base )
221{
222 test_roundtrip( std::numeric_limits<T>::min(), base );
223 test_roundtrip( std::numeric_limits<T>::max(), base );
224}
225
226#ifdef BOOST_CHARCONV_HAS_INT128
227template <> void test_roundtrip_bv<boost::int128_type>(int base)
228{
229 test_roundtrip( BOOST_CHARCONV_INT128_MIN, base );
230 test_roundtrip( BOOST_CHARCONV_INT128_MAX, base );
231}
232
233template <> void test_roundtrip_bv<boost::uint128_type>(int base)
234{
235 test_roundtrip( value: 0, base );
236 test_roundtrip( BOOST_CHARCONV_UINT128_MAX, base );
237}
238#endif
239
240// floating point types, random values
241
242template<class T> void test_roundtrip( T value )
243{
244 char buffer[ 256 ];
245
246 auto r = boost::charconv::to_chars( buffer, buffer + sizeof( buffer ), value );
247
248 BOOST_TEST( r.ec == std::errc() );
249
250 T v2 = 0;
251 auto r2 = boost::charconv::from_chars( buffer, r.ptr, v2 );
252
253 if( BOOST_TEST( r2.ec == std::errc() ) && BOOST_TEST_EQ( v2, value ) && BOOST_TEST( r2.ptr == r.ptr) )
254 {
255 }
256 else
257 {
258 // LCOV_EXCL_START
259 #ifdef BOOST_CHARCONV_DEBUG_ROUNDTRIP
260 std::cerr << std::setprecision(std::numeric_limits<T>::digits10 + 1)
261 << " Value: " << value
262 << "\n To chars: " << std::string( buffer, r.ptr )
263 << "\nFrom chars: " << v2 << std::endl
264 << std::hexfloat
265 << "\n Value: " << value
266 << "\nFrom chars: " << v2
267 << "\n R1 offset: " << (r.ptr - buffer)
268 << "\n R2 offset: " << (r2.ptr - buffer) << std::endl << std::scientific;
269 #else
270 std::cerr << "... test failure for value=" << value << "; buffer='" << std::string( buffer, r.ptr ) << "'" << std::endl;
271 #endif
272 // LCOV_EXCL_STOP
273 }
274}
275
276// https://stackoverflow.com/questions/62074229/float-distance-for-80-bit-long-double
277/* Return the signed distance from 0 to x, measuring distance as one unit per
278 number representable in FPType. x must be a finite number.
279*/
280
281#if defined(__GNUC__) && (__GNUC__ >= 5)
282# pragma GCC diagnostic push
283# pragma GCC diagnostic ignored "-Wattributes"
284#endif
285
286template<typename FPType>
287#if !defined(BOOST_MSVC) && !(defined(__clang__) && (__clang_major__ == 3) && (__clang_minor__ < 7))
288__attribute__((no_sanitize("undefined")))
289#endif
290int64_t ToOrdinal(FPType x)
291{
292 static constexpr int
293 Radix = std::numeric_limits<FPType>::radix,
294 SignificandDigits = std::numeric_limits<FPType>::digits,
295 MinimumExponent = std::numeric_limits<FPType>::min_exponent;
296
297 // Number of normal representable numbers for each exponent.
298 static const auto
299 NumbersPerExponent = static_cast<uint64_t>(scalbn(x: Radix-1, n: SignificandDigits-1));
300
301 if (x == 0)
302 return 0;
303
304 // Separate the sign.
305 int sign = std::signbit(x) ? -1 : +1;
306 x = std::fabs(x);
307
308 // Separate the significand and exponent.
309 int exponent = std::ilogb(x)+1;
310 FPType fraction = std::scalbn(x, -exponent);
311
312 if (exponent < MinimumExponent)
313 {
314 // For subnormal x, adjust to its subnormal representation.
315 fraction = std::scalbn(fraction, exponent - MinimumExponent);
316 exponent = MinimumExponent;
317 }
318
319 /* Start with the number of representable numbers in preceding normal
320 exponent ranges.
321 */
322 auto count = static_cast<int64_t>(static_cast<uint64_t>(exponent - MinimumExponent) * NumbersPerExponent);
323
324 /* For subnormal numbers, fraction * radix ** SignificandDigits is the
325 number of representable numbers from 0 to x. For normal numbers,
326 (fraction-1) * radix ** SignificandDigits is the number of
327 representable numbers from the start of x's exponent range to x, and
328 1 * radix ** SignificandDigits is the number of representable subnormal
329 numbers (which we have not added into count yet). So, in either case,
330 adding fraction * radix ** SignificandDigits is the desired amount to
331 add to count.
332 */
333 count += static_cast<int64_t>(std::scalbn(fraction, SignificandDigits));
334
335 return sign * count;
336}
337
338#if defined(__GNUC__) && (__GNUC__ >= 5)
339# pragma GCC diagnostic pop
340#endif
341
342/* Return the number of representable numbers from x to y, including one
343 endpoint.
344*/
345template<typename FPType> int64_t Distance(FPType y, FPType x)
346{
347 return ToOrdinal(y) - ToOrdinal(x);
348}
349
350template <> void test_roundtrip<long double>(long double value)
351{
352 char buffer[ 256 ];
353
354 auto r = boost::charconv::to_chars( first: buffer, last: buffer + sizeof( buffer ), value );
355
356 BOOST_TEST( r.ec == std::errc() );
357
358 long double v2 = 0;
359 auto r2 = boost::charconv::from_chars( first: buffer, last: r.ptr, value&: v2 );
360
361 if( BOOST_TEST( r2.ec == std::errc() ) && BOOST_TEST( std::abs(Distance(v2, value)) < INT64_C(1) ) )
362 {
363 }
364 else
365 {
366 // LCOV_EXCL_START
367 #ifdef BOOST_CHARCONV_DEBUG_ROUNDTRIP
368 std::cerr << std::setprecision(std::numeric_limits<long double>::digits10 + 1)
369 << " Value: " << value
370 << "\n To chars: " << std::string( buffer, r.ptr )
371 << "\nFrom chars: " << v2 << std::endl
372 << std::hexfloat
373 << "\n Value: " << value
374 << "\nFrom chars: " << v2 << std::endl << std::scientific;
375 #else
376 std::cerr << "... test failure for value=" << value
377 << "; buffer='" << std::string( buffer, r.ptr ) << "'"
378 << "; ulp distance=" << Distance(y: v2, x: value)
379 << "; error code=" << static_cast<int>(r2.ec) << std::endl;
380 #endif
381 // LCOV_EXCL_STOP
382 }
383}
384
385// floating point types, boundary values
386
387template<class T> void test_roundtrip_bv()
388{
389 test_roundtrip( std::numeric_limits<T>::min() );
390 test_roundtrip( -std::numeric_limits<T>::min() );
391 test_roundtrip( std::numeric_limits<T>::max() );
392 test_roundtrip( +std::numeric_limits<T>::max() );
393}
394
395//
396
397template <typename T>
398void test_extreme_values()
399{
400 T current_pos = (std::numeric_limits<T>::max)();
401 for (int i = 0; i < 10000; ++i)
402 {
403 test_roundtrip<T>(current_pos);
404 current_pos = std::nexttoward(current_pos, T(0));
405 }
406
407 current_pos = (std::numeric_limits<T>::min)();
408 for (int i = 0; i < 10000; ++i)
409 {
410 test_roundtrip<T>(current_pos);
411 current_pos = std::nextafter(current_pos, T(1));
412 }
413}
414
415int main()
416{
417 // integral types, random values
418
419 for( int base = 2; base <= 36; ++base )
420 {
421 test_roundtrip_int8<std::int8_t>( base );
422 test_roundtrip_uint8<std::uint8_t>( base );
423
424 test_roundtrip_int16<std::int16_t>( base );
425 test_roundtrip_uint16<std::uint16_t>( base );
426
427 test_roundtrip_int32<std::int32_t>( base );
428 test_roundtrip_uint32<std::uint32_t>( base );
429
430 test_roundtrip_int64<std::int64_t>( base );
431 test_roundtrip_uint64<std::uint64_t>( base );
432
433#ifdef BOOST_CHARCONV_HAS_INT128
434
435 test_roundtrip_int128<boost::int128_type>( base );
436 test_roundtrip_uint128<boost::uint128_type>( base );
437
438#endif
439 }
440
441 // integral types, boundary values
442
443 for( int base = 2; base <= 36; ++base )
444 {
445 test_roundtrip_bv<char>( base );
446 test_roundtrip_bv<signed char>( base );
447 test_roundtrip_bv<unsigned char>( base );
448
449 test_roundtrip_bv<short>( base );
450 test_roundtrip_bv<unsigned short>( base );
451
452 test_roundtrip_bv<int>( base );
453 test_roundtrip_bv<unsigned int>( base );
454
455 test_roundtrip_bv<long>( base );
456 test_roundtrip_bv<unsigned long>( base );
457
458 test_roundtrip_bv<long long>( base );
459 test_roundtrip_bv<unsigned long long>( base );
460
461#ifdef BOOST_CHARCONV_HAS_INT128
462
463 test_roundtrip_bv<boost::int128_type>( base );
464 test_roundtrip_bv<boost::uint128_type>( base );
465
466#endif
467 }
468
469 // 16-bit types
470
471 double const q = std::pow( x: 1.0, y: -64 );
472
473 #ifdef BOOST_CHARCONV_HAS_FLOAT16
474 {
475 std::float16_t const small_q = std::pow(1.0F16, -16.0F16);
476
477 for( int i = 0; i < N; ++i )
478 {
479 std::float16_t w0 = static_cast<std::float16_t>( rng() ); // 0 .. 2^64
480 test_roundtrip( w0 );
481
482 std::float16_t w1 = static_cast<std::float16_t>( rng() ) * small_q ; // 0.0 .. 1.0
483 test_roundtrip( w1 );
484
485 std::float16_t w2 = (std::numeric_limits<std::float16_t>::max)() / static_cast<std::float16_t>( rng() ); // large values
486 test_roundtrip( w2 );
487
488 std::float16_t w3 = (std::numeric_limits<std::float16_t>::min)() * static_cast<std::float16_t>( rng() ); // small values
489 test_roundtrip( w3 );
490 }
491
492 test_roundtrip_bv<std::float16_t>();
493 }
494 #endif
495
496 #ifdef BOOST_CHARCONV_HAS_BRAINFLOAT16
497 {
498 std::bfloat16_t const small_q = std::pow(1.0BF16, -16.0BF16);
499
500 for( int i = 0; i < N; ++i )
501 {
502 std::bfloat16_t w0 = static_cast<std::bfloat16_t>( rng() ); // 0 .. 2^64
503 test_roundtrip( w0 );
504
505 std::bfloat16_t w1 = static_cast<std::bfloat16_t>( rng() ) * small_q ; // 0.0 .. 1.0
506 test_roundtrip( w1 );
507
508 std::bfloat16_t w2 = (std::numeric_limits<std::bfloat16_t>::max)() / static_cast<std::bfloat16_t>( rng() ); // large values
509 test_roundtrip( w2 );
510
511 std::bfloat16_t w3 = (std::numeric_limits<std::bfloat16_t>::min)() * static_cast<std::bfloat16_t>( rng() ); // small values
512 test_roundtrip( w3 );
513 }
514
515 test_roundtrip_bv<std::bfloat16_t>();
516 }
517 #endif
518
519 // float
520
521 {
522 for( int i = 0; i < N; ++i )
523 {
524 float w0 = static_cast<float>( rng() ); // 0 .. 2^64
525 test_roundtrip( value: w0 );
526
527 float w1 = static_cast<float>( rng() ) * static_cast<float>( q ); // 0.0 .. 1.0
528 test_roundtrip( value: w1 );
529
530 float w2 = FLT_MAX / static_cast<float>( rng() ); // large values
531 test_roundtrip( value: w2 );
532
533 float w3 = FLT_MIN * static_cast<float>( rng() ); // small values
534 test_roundtrip( value: w3 );
535 }
536
537 test_roundtrip_bv<float>();
538 }
539
540 #ifdef BOOST_CHARCONV_HAS_FLOAT32
541 {
542 for( int i = 0; i < N; ++i )
543 {
544 std::float32_t w0 = static_cast<std::float32_t>( rng() ); // 0 .. 2^64
545 test_roundtrip( w0 );
546
547 std::float32_t w1 = static_cast<std::float32_t>( rng() ) * static_cast<std::float32_t>(q) ; // 0.0 .. 1.0
548 test_roundtrip( w1 );
549
550 std::float32_t w2 = (std::numeric_limits<std::float32_t>::max)() / static_cast<std::float32_t>( rng() ); // large values
551 test_roundtrip( w2 );
552
553 std::float32_t w3 = (std::numeric_limits<std::float32_t>::min)() * static_cast<std::float32_t>( rng() ); // small values
554 test_roundtrip( w3 );
555 }
556
557 test_roundtrip_bv<std::float32_t>();
558 }
559 #endif
560
561 // double
562
563 {
564 for( int i = 0; i < N; ++i )
565 {
566 double w0 = static_cast<double>( rng() ) * 1.0; // 0 .. 2^64
567 test_roundtrip( value: w0 );
568
569 double w1 = static_cast<double>( rng() ) * q; // 0.0 .. 1.0
570 test_roundtrip( value: w1 );
571
572 double w2 = DBL_MAX / static_cast<double>( rng() ); // large values
573 test_roundtrip( value: w2 );
574
575 double w3 = DBL_MIN * static_cast<double>( rng() ); // small values
576 test_roundtrip( value: w3 );
577 }
578
579 test_roundtrip_bv<double>();
580 }
581
582 #ifdef BOOST_CHARCONV_HAS_FLOAT64
583 {
584 for( int i = 0; i < N; ++i )
585 {
586 std::float64_t w0 = static_cast<std::float64_t>( rng() ); // 0 .. 2^64
587 test_roundtrip( w0 );
588
589 std::float64_t w1 = static_cast<std::float64_t>( rng() ) * static_cast<std::float64_t>(q) ; // 0.0 .. 1.0
590 test_roundtrip( w1 );
591
592 std::float64_t w2 = (std::numeric_limits<std::float64_t>::max)() / static_cast<std::float64_t>( rng() ); // large values
593 test_roundtrip( w2 );
594
595 std::float64_t w3 = (std::numeric_limits<std::float64_t>::min)() * static_cast<std::float64_t>( rng() ); // small values
596 test_roundtrip( w3 );
597 }
598
599 test_roundtrip_bv<std::float64_t>();
600 }
601 #endif
602
603 // long double
604 #if !(BOOST_CHARCONV_LDBL_BITS == 128)
605
606 {
607 long double const ql = std::pow( x: 1.0L, y: -64 );
608
609 for( int i = 0; i < N; ++i )
610 {
611 long double w0 = static_cast<long double>( rng() ) * 1.0L; // 0 .. 2^64
612 test_roundtrip( value: w0 );
613
614 long double w1 = static_cast<long double>( rng() ) * ql; // 0.0 .. 1.0
615 test_roundtrip( value: w1 );
616
617 long double w2 = LDBL_MAX / static_cast<long double>( rng() ); // large values
618 test_roundtrip( value: w2 );
619
620 long double w3 = LDBL_MIN * static_cast<long double>( rng() ); // small values
621 test_roundtrip( value: w3 );
622
623 long double w4 = -static_cast<long double>( rng() ) * 1.0L; // -0 .. 2^64
624 test_roundtrip( value: w4 );
625 }
626
627 test_roundtrip_bv<long double>();
628 }
629
630 #endif
631
632 // Selected additional values
633 //
634 test_roundtrip<double>(value: 1.10393929655481808e+308);
635 test_roundtrip<double>(value: -1.47902377240341038e+308);
636 test_roundtrip<double>(value: -2.13177235460600904e+307);
637 test_roundtrip<double>(value: 8.60473951619578187e+307);
638 test_roundtrip<double>(value: -2.97613696314797352e+306);
639
640 test_roundtrip<float>(value: 3.197633022e+38F);
641 test_roundtrip<float>(value: 2.73101834e+38F);
642 test_roundtrip<float>(value: 3.394053352e+38F);
643 test_roundtrip<float>(value: 5.549256619e+37F);
644 test_roundtrip<float>(value: 8.922125027e+34F);
645
646 test_extreme_values<float>();
647 test_extreme_values<double>();
648
649 return boost::report_errors();
650}
651

source code of boost/libs/charconv/test/roundtrip.cpp