"Though this be madness, yet there is method in ’t." ― Polonius
1. Introduction
C++11 introduced a set of
overloads for integral and
floating-point types. Fortunately for integral and unfortunately for
floating-point overloads they are all defined in terms of
inconsistently with C++ formatted output functions ([N4910]). Additionally,
the choice of the floating-point format makes
of very limited
use in practice. This paper proposes fixing these issues while retaining
existing semantics of integral overloads.
2. Revision history
Changes since R0:
-
Added an entry to Annex C.
-
Added a feature test macro.
-
Applied the same changes to
in the wording.to_wstring
3. Examples
Consider the following example:
auto loc = std :: locale ( "uk_UA.UTF-8" ); std :: locale :: global ( loc ); std :: cout . imbue ( loc ); setlocale ( LC_ALL , "C" ); std :: cout << "iostreams: \n " ; std :: cout << 1234 << " \n " ; std :: cout << 1234.5 << " \n " ; std :: cout << " \n to_string: \n " ; std :: cout << std :: to_string ( 1234 ) << " \n " ; std :: cout << std :: to_string ( 1234.5 ) << " \n " ; setlocale ( LC_ALL , "uk_UA.UTF-8" ); std :: cout << " \n to_string (uk_UA.UTF-8 C locale): \n " ; std :: cout << std :: to_string ( 1234 ) << " \n " ; std :: cout << std :: to_string ( 1234.5 ) << " \n " ;
It prints:
iostreams: 1 234 1 234,5 to_string: 1234 1234.500000 to_string (uk_UA.UTF-8 C locale): 1234 1234,500000
Since
uses the global C locale and no grouping the integral
overloads are effectively unlocalized. The output of floating-point overloads is
inconsistent with that of iostreams because the former takes the decimal
point from the global C locale and doesn’t do grouping.
Additionally, due to an unfortunate choice of the fixed format in the floating-point overloads they are only useful for numbers in a limited exponent range. For example:
std :: cout << std :: to_string ( std :: numeric_limits < double >:: max ());
prints
(line breaks inserted for readability)1797693134862315708145274237317043567980705675258449965989174768031572607800285 3876058955863276687817154045895351438246423432132688946418276846754670353751698 6049910576551282076245490090389328944075868508455133942304583236903222948165808 559332123348274797826204144723168738177180919299881250404026184124858368.000000
Here only the first 17 digits are meaningful, the next 292 are so-called "garbage" digits ([DRAGON]). And finally we have 6 meaningless zeros after a possibly localized decimal point.
Formatting of small floating-point numbers is even less useful. For example:
std :: cout << std :: to_string ( -1e-7 );
prints
-0.000000
In fact almost half of floating-point numbers are formatted as zero and there is a precision loss or garbage digit output in many other cases.
Unfortunately issues with floating-point overloads of
have not
been discussed in the paper that proposed this facility ([N1803], [N2408]).
However, they were brought up as early as 2015 in a mailing list discussion
([STD_DISCUSSION]) which proposed deprecating floating-point overloads of
but no paper came out of that.
4. Proposal
Redefine
in terms of
which in turn uses
making more explicit the fact that integral overloads are
unlocalized and changing the format of floating-point overloads to also be
unlocalized and use the shortest decimal representation.
The following table shows the changes in output for the following code:
setlocale ( LC_ALL , "C" ); auto output = std :: to_string ( input );
|
| |
before | after | |
42 | 42 | 42 |
0.42 | 0.420000 | 0.42 |
-1e-7 | -0.000000 | -1e-7 |
1.7976931348623157e+308 | 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000 | 1.7976931348623157e+308 |
and similarly with the global C locale set:
setlocale ( LC_ALL , "uk_UA.UTF-8" ); auto output = std :: to_string ( input );
|
| |
before | after | |
12345 | 12345 | 12345 |
1234.5 | 1234,500000 | 1234.5 |
5. Impact on existing code
This change will affect the output of
with floating-point
arguments. In most cases it will result in a more precise and/or shorter output.
In cases where the C locale is explicitly set the decimal point will no longer
be localized.
6. Implementation
{fmt} implements proposed changes in
.
7. Wording
Add an entry for
to section "Header
synopsis [version.syn]",
in a place that respects the table’s current alphabetic order:
#define __cpp_lib_to_string **placeholder** // also in <string>
Modify subsection "Numeric conversions [string.conversions]":
string to_string ( int val ); string to_string ( unsigned val ); string to_string ( long val ); string to_string ( unsigned long val ); string to_string ( long long val ); string to_string ( unsigned long long val ); string to_string ( float val ); string to_string ( double val ); string to_string ( long double val );
sprintf ( buf , fmt , val )
with a format specifier of "%d"
, "%u"
, "%ld"
, "%lu"
, "%lld"
, "%llu"
, "%f"
, "%f"
, or "%Lf"
, respectively, where buf
designates an internal character buffer of sufficient size.
7 Returns:
.
wstring to_wstring ( int val ); wstring to_wstring ( unsigned val ); wstring to_wstring ( long val ); wstring to_wstring ( unsigned long val ); wstring to_wstring ( long long val ); wstring to_wstring ( unsigned long long val ); wstring to_wstring ( float val ); wstring to_wstring ( double val ); wstring to_wstring ( long double val );
wstring
object holding the character
representation of the value of its argument that would be generated by calling swprintf ( buf , buffsz , fmt , val )
with a format specifier of L"%d"
, L"%u"
, L"%ld"
, L"%lu"
, L"%lld"
, L"%llu"
, L"%f"
, L"%f"
, or L"%Lf"
,
respectively, where buf
designates an internal character buffer of sufficient
size buffsz
.
14 Returns:
.
Add to Annex C (informative) Compatibility [diff] the following new subclause:
C.? C++ and ISO C++ 2026 [diff.cpp23]
This subclause lists the differences between C++ and ISO C++ 2023 (ISO/IEC 14882:2023, Programming Languages — C++), by the chapters of this document.
C.?.1 Clause 23: strings library [diff.cpp23.strings]
Affected subclauses: 23.4
Change: Output of floating-point overloads of
and
.
Rationale: Prevent loss of information and improve consistentcy with other
formatting facilities.
Effect on original feature:
and
function calls that
take floating-point arguments may produce a different output. For example:
auto s = std :: to_string ( 1e-7 ); // "1e-7" // previously "0.000000" with '.' possibly // changed according to the global C locale