Last Update:
How to Convert Numbers into Text with std::to_chars in C++17
Table of Contents
In this post, I’ll show you how to use the newest, low-level, conversion routines form C++17. With the new functionality, you can quickly transform numbers into text and have super performance compared to previous techniques.
Before C++17
Until C++17, we had several ways of converting numbers into strings:
sprintf
/snprintf
stringstream
to_string
itoa
- and 3rd-party libraries like boost - lexical cast
And with C++17 we get another option: std::to_chars
(along with the corresponding method from_chars
) ! The functions both reside in the <charconv>
header.
Why do we need new methods? Weren’t the old technique good enough?
In short: because to_chars
and from_chars
are low-level, and offers the best possible performance.
The new conversion routines are:
- non-throwing
- non-allocating
- no locale support
- memory safety
- error reporting gives additional information about the conversion outcome
- bound checked
- explicit round-trip guarantees - you can use
to_chars
andfrom_chars
to convert the number back and forth, and it will give you the exact binary representations. This is not guaranteed by other routines like printf/sscanf/itoa, etc.
A simple example:
std::string str { "xxxxxxxx" };
const int value = 1986;
std::to_chars(str.data(), str.data() + str.size(), value);
// str is "1986xxxx"
The new functions are available in the following compilers:
- Visual Studio 2019 16.4 - full support, and early floating-point support from VS 2017 15.7
- GCC - 11.0 - full support, and since GCC 8.0 - integer support only
- Clang 7.0 - still in progress, only integer support
The Series
This article is part of my series about C++17 Library Utilities. Here’s the list of the articles:
- Refactoring with
std::optional
- Using
std::optional
- Error handling and
std::optional
- Everything You Need to Know About
std::variant
from C++17 - Everything You Need to Know About
std::any
from C++17 std::string_view
Performance and followup- C++17 string searchers and followup
- Conversion utilities: on
std::from_chars
- from a string to a number and onstd::to_chars
- from numbers into strings - How to get File Size in C++? and std:filesystem::file_size Advantages and Differences
- How To Iterate Through Directories in C++17
Resources about C++17 STL:
- C++17 In Detail by Bartek!
- C++17 - The Complete Guide by Nicolai Josuttis
- C++ Fundamentals Including C++ 17 by Kate Gregory
- Practical C++14 and C++17 Features - by Giovanni Dicanio
- C++17 STL Cookbook by Jacek Galowicz
Using to_chars
to_chars
is a set of overloaded functions for integral and floating-point types.
For integral types there’s one declaration:
std::to_chars_result to_chars(char* first, char* last,
TYPE value, int base = 10);
Where TYPE
expands to all available signed and unsigned integer types and char
.
Since base
might range from 2 to 36, the output digits that are greater than 9 are represented as lowercase letters: a...z
.
For floating-point numbers, there are more options.
Firstly there’s a basic function:
std::to_chars_result to_chars(char* first, char* last, FLOAT_TYPE value);
FLOAT_TYPE
expands to float
, double
or long double
.
The conversion works the same as with printf
and in default (“C”) locale. It uses %f
or %e
format specifier favouring the representation that is the shortest.
The next function overload adds std::chars_format fmt
that let’s you specify the output format:
std::to_chars_result to_chars(char* first, char* last,
FLOAT_TYPE value,
std::chars_format fmt);
chars_format
is an enum with the following values: scientific
, fixed
, hex
and general
(which is a composition of fixed
and scientific
).
Then there’s the “full” version that allows also to specify precision
:
std::to_chars_result to_chars(char* first, char* last,
FLOAT_TYPE value,
std::chars_format fmt,
int precision);
The Output
When the conversion is successful, the range [first, last)
is filled with the converted string.
The returned value for all functions (for integer and floating-point support) is to_chars_result
, it’s defined as follows:
struct to_chars_result {
char* ptr;
std::errc ec;
};
The type holds information about the conversion process:
Return Condition | State of from_chars_result |
---|---|
Success | ec equals value-initialized std::errc and ptr is the one-past-the-end pointer of the characters written. Note that the string is not NULL-terminated. |
Out of range | ec equals std::errc::value_too_large the range [first, last) in unspecified state. |
As you can see, we have only two options: success or out of range - as there’s a chance your buffer doesn’t have enough size to hold the result.
An Example - Integer types
To sum up, here’s a basic demo of to_chars
.
#include <iostream>
#include <charconv> // from_chars, to_chars
#include <string>
int main() {
std::string str { "xxxxxxxx" };
const int value = 1986;
const auto res = std::to_chars(str.data(),
str.data() + str.size(),
value);
if (res.ec == std::errc()) {
std::cout << str << ", filled: "
<< res.ptr - str.data() << " characters\n";
}
else if (res.ec == std::errc::value_too_large) {
std::cout << "value too large!\n";
}
}
Below you can find a sample output for a set of numbers:
value value |
output |
---|---|
1986 |
1986xxxx, filled: 4 characters |
-1986 |
-1986xxx, filled: 5 characters |
19861986 |
19861986, filled: 8 characters |
-19861986 |
value too large! (the buffer is only 8 characters) |
An Example - Floating Point
On MSVC (starting from 15.9, full support in 16.0 + improvements later) and GCC 11.0 we can also try the floating-point support:
std::string str{ "xxxxxxxxxxxxxxx" }; // 15 chars for float
const auto res = std::to_chars(str.data(), str.data() + str.size(), value);
if (res.ec == std::errc()) {
std::cout << str << ", filled: "
<< res.ptr - str.data() << " characters\n";
}
else if (res.ec == std::errc::value_too_large) {
std::cout << "value too large!\n";
}
And here’s a working demo under GCC 11.0:
#include <iostream>
#include <charconv> // from_chars, to_chars
#include <string>
int main() {
std::string str { "xxxxxxxx" };
const double value = 1986.10;
const auto res = std::to_chars(str.data(), str.data() + str.size(), value);
if (res.ec == std::errc()) {
std::cout << str << ", filled: " << res.ptr - str.data() << " characters\n";
}
else {
std::cout << "value too large!\n";
}
}
Play with code @Compiler Explorer
Below you can find a sample output for a set of numbers:
value value |
format | output |
---|---|---|
0.1f |
- | 0.1xxxxxxxxxxxx, filled: 3 characters |
1986.1f |
general | 1986.1xxxxxxxxx, filled: 6 characters |
1986.1f |
scientific | 1.9861e+03xxxxx, filled: 10 characters |
Benchmark & Some numbers
In my book - C++17 in Detail - I did some perf experiments for integer conversions, and the new functionality is several times faster than to_string
or sprintf
and more than 10… or even 23x faster than stringstream versions!
I also have to check the floating-point support, but the results that I see from various places also claim order of magnitude speedup over the older techniques.
See the Stephan T. Lavavej’s talk (in references) about implementing charconv in MSVC where he shared some floating-point benchmark results.
C++20
In C++20, we have more methods that allow us to convert data into strings and format them.
The library is called std::format
and is based on a popular framework {fmt}
Have a look: https://en.cppreference.com/w/cpp/utility/format
As of today (June 2021) you can play with the library under MSVC 16.10 (VS 2019):
std::vector<char> buf;
std::format_to(std::back_inserter(buf), "{}", 42);
// buf contains "42"
You can also check out this blog post that nicely introduces you to the concepts of std::format
:
An Extraterrestrial Guide to C++20 Text Formatting - C++ Stories
As for the benchmarks you can read this one: Converting a hundred million integers to strings per second - it includes comparison to to_chars
and shows various results for integer conversions.
Summary
With C++17, we got new functionality that allows easy and low-level conversions between numbers and text. The new routines are potent and expose all the information you need to create advanced parsers or serialises. They won’t throw, won’t allocate, they bound check, and they offer super performance.
Read here about the corresponding from_chars
method
Extra: since CppCon 2019 Microsoft opened their STL implementation, so you can even have a look at the code of charconv!
I also highly suggest watching Stephan’s talk about the progress and the efforts for full charconv support. The feature looked very simple at first sight, but it appeared to be super complicated to support as the C library couldn’t be used, and everything had to be done from scratch.
Floating-Point <charconv>
: Making Your Code 10x Faster With C++17’s Final Boss by Stephan T. Lavavej
Your Turn
What do you think about the new conversion routines? Have you tried them?
What other conversion utilities do you use?
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here:
Similar Articles:
- How To Detect Function Overloads in C++17/20, std::from_chars Example
- [Quick Case] Surprising Conversions of const char* to bool
- Space Game: A std::variant-Based State Machine by Example
- std:filesystem::file_size Advantages and Differences
- How to Boost Performance with Intel Parallel STL and C++17 Parallel Algorithms