fast_float: exact from_chars parsing for C++ without strtod
Fast and exact implementation of the C++ from_chars functions for number types: 4x to 10x faster than strtod, part of GCC 12, MySQL, DuckDB, Chromium, Redis and WebKit/Safari
At a glance
- What is it?
- A header-only C++11 implementation of the standard from_chars functions for float, double and integer types, with round-to-even exactness, no allocation, and no exception handling.
- Who is it for?
- fast_float is a good default when you parse numbers out of text in C++ and care that the result is correctly rounded and that the parser cannot be made to throw or allocate. It is the wrong choice if you need `long double`, hexadecimal float strings, or the locale-aware behaviour of `strtod`, because all three are documented restrictions rather than gaps.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two functions, a result struct, no exceptions
The API is two functions, and the README shows their signatures before it says anything else:
from_chars_result from_chars(char const *first, char const *last, float &value, ...);
from_chars_result from_chars(char const *first, char const *last, double &value, ...);If the system provides them, fixed-width types such as `std::float64_t`, `std::float32_t`, `std::float16_t` and `std::bfloat16_t` are supported too. There is a second overload family for integer types, from `char` and `short` through the unsigned variants and the fixed width `int8_t` to `uint64_t`, and `bool` parses as 0 or 1. Integer parsing also accepts different bases, 2, 10 or 16.
The result type is a two-field struct, nothing more:
struct from_chars_result {
char const *ptr;
std::errc ec;
};On success, `ptr` points just past the parsed number and the referenced value has been written. On failure, `ec` holds a representative error, and otherwise it holds the default constructed `std::errc()`. That check is the whole error handling story: the implementation does not throw and does not allocate, whether through `new` or through `malloc`. For a parser that runs on untrusted input, both properties matter more than the speed claim.
The first program, and how the pointer drives the next call
Here is the README's example, which is also the shape of every call you will write:
#include "fast_float/fast_float.h"
#include <iostream>
#include <string>
int main() {
std::string input = "3.1416 xyz ";
double result;
auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result);
if (answer.ec != std::errc()) { std::cerr << "parsing failure\n"; return EXIT_FAILURE; }
std::cout << "parsed the number " << result << std::endl;
return EXIT_SUCCESS;
}Note that the input ends in `xyz` and the parse still succeeds, because success means a number was read, not that the whole range was consumed. That is why `ptr` is part of the result.
The README then shows the delimited case, which is where the design pays off. For input like `234532.3426362,7869234.9823,324562.645`, you call `from_chars` once, check that `answer.ptr[0]` is the comma you expected, then call it again starting at `answer.ptr + 1` with the same end pointer. No substring, no allocation, no stoi, no exception. The pattern generalises to any separator you want to validate yourself, and it is the reason a header-only parser is genuinely useful inside a hot loop.
There is a second calling convention as well. Prior to C++26, you have to compare `ec` to `std::errc()`, which is what the example above does. The library extends the API so the result converts to bool:
if(auto answer = fast_float::from_chars(input.data(), input.data() + input.size(), result)) {
std::cout << "parsed the number " << result << std::endl;
return EXIT_SUCCESS;
}If you are compiling against a standard library that has the C++26 form, this reads better and matches what your other `from_chars` calls will look like.
Where fast_float differs from the C++17 specification
The library aims to follow the C++17 specification and links the relevant clause, then lists its deviations, which is the most useful section in the README.
Leading whitespace is not skipped, unless `fast_float::chars_format::skip_white_space` is set. A leading `+` is forbidden, unless `fast_float::chars_format::allow_leading_plus` is set. Both are strictness choices rather than bugs, and both exist because silently accepting `+1` or a leading space is a common source of parse disagreements between tools.
The rounding rule is stated in the same breath: because a decimal value generally cannot be represented exactly in binary floating point, the library seeks the nearest value and rounds to an even mantissa when the input lands exactly between two representable numbers. That is exact parsing per the IEEE standard, and round to even is the part that matters for reproducibility.
The documented restrictions are where to look before you commit to it. There is no `long double` support. There is no hexadecimal string support, only decimal. A value like `1e9999` becomes an infinity with `ec` set to `std::errc::result_out_of_range`, and `1e-9999` becomes a signed zero with the same error, so out-of-range is detectable even though the value is written. Infinity and NaN inputs are parsed.
Platform coverage is broad and stated plainly: Visual Studio, macOS, Linux and FreeBSD, both endiannesses, and 32-bit and 64-bit systems. Topics on the repository add NEON, SSE2 and SIMD, which is consistent with the SWAR scanning described in the recent release notes.
chars_format as a bitset, and the ECMAScript format added in 8.3.0
The optional last argument is a `fast_float::chars_format` bitset. The library checks the `fixed` and `scientific` bits to decide whether each notation is allowed, and the default is `general`, which allows both.
Release v8.3.0, published on 2026-09-15, added two more formats aimed at JavaScript input. `chars_format::javascript` follows the ECMAScript DecimalLiteral production: like JSON, except the integer part may be empty so `.5` parses, and the fractional part may be empty so `5.` and `5.e3` parse. Leading zeros are rejected, and so are `inf` and `nan`. A second format, `chars_format::javascript_sloppy`, additionally accepts sloppy-mode `NonOctalDecimalIntegerLiteral`, so `08.5` reads as 8.5.
The interesting part of that release note is the legacy octal case. A legacy octal literal such as `0775` is rejected with a new error, `parse_error::legacy_octal_integer_part`, specifically so the caller can then parse it in base 8 deliberately rather than having the parser silently decide. This is the difference between a parser that has opinions and one that guesses.
The same release also carries a faster integer part scan, described as 4-digit SWAR on GCC, and a change that skips the rounding mode probe for long mantissas. Both are the kind of internal work that never shows up in an API and explains why a header-only library can stay competitive with the standard library across compiler versions.
Adoption evidence, licensing, and the rounding mode assumption
The repository description names the projects that ship this code: it is part of GCC 12, MySQL, DuckDB, Chromium, Redis and WebKit/Safari. That is the strongest signal available, because those are exactly the programs where a wrong rounding decision becomes a data bug rather than a performance detail. Nothing in the README is a benchmark result; the speed claim is stated as 4x to 10x faster than strtod in the description and as faster than comparable standard library functions in the README body.
Licensing is the unusually generous kind: three licence files sit in the tree, LICENSE-APACHE, LICENSE-MIT and LICENSE-BOOST. Taking Boost means you can vendor the header into a proprietary codebase without the Apache 2.0 patent and notice obligations. For a single-header library you drop into other people's builds, that is the licensing story that decides whether adoption is easy, and here it is easy.
The build surface is broad too, and mostly a sign of how many platforms it is expected to compile on. There is a `CMakeLists.txt` with a `cmake/` directory, `BUILD.bazel` and `MODULE.bazel` for Bazel, `.bazelrc`, `.cirrus.yml` for FreeBSD CI, `.travis.yml`, a `ci/` directory, and a GitHub Actions workflow for Ubuntu 22.04 with a badge in the README. There is also a `fuzz/` directory, which is the right thing to have next to a parser, and `benchmarks/`, `tests/`, `docs/` and `script/`.
The one assumption to internalise is at the end of the README: the rounding mode is assumed to be nearest, meaning `std::fegetround()` returns `FE_TONEAREST`. Nothing in the API lets you query that, and nothing detects a mismatch. Code that switches to directed rounding for interval arithmetic elsewhere in the same program will change fast_float's results without any signal from the library.
When strtod or the standard library is still the answer
The honest comparison with `strtod` is that the standard function is locale aware and fast_float is not, and that difference decides several cases outright. If your input uses a comma as the decimal separator, or if you depend on the current C locale to interpret a number, `strtod` is the correct tool and fast_float will reject or misread the input. The same applies to the standard library's own `from_chars`, which has no locale dependency either but ships with a different feature set and a different implementation history.
Against the standard `std::from_chars`, the argument is about three things: exact rounding that the README commits to, the delimited parsing pattern with no allocation, and the ECMAScript formats from v8.3.0 that a standard library will not have for a long time. Against a hand-rolled parser, the argument is the fuzz directory and the fix for non-digit wide code units in the uint8 and uint16 integer fast path, which is a bug class you would be rediscovering.
The cost of choosing it is mostly legal and packaging. You are vendoring three thousand lines of header code, or adding a dependency, to save work you might also solve with a simpler approach: if your numbers arrive in a fixed-width binary format rather than as text, no decimal parser is the right answer at all.
The README is also careful about its audience in a way worth noticing. It opens by saying this library is for C++ users and pointing C programmers at ffc.h, a high-performance port of fast_float to C. It is a narrow, well documented piece of infrastructure with a specification-linked deviation list, and the documentation quality is part of why it ended up inside GCC and Chromium.
Editorial conclusion
fast_float is a good default when you parse numbers out of text in C++ and care that the result is correctly rounded and that the parser cannot be made to throw or allocate. It is the wrong choice if you need `long double`, hexadecimal float strings, or the locale-aware behaviour of `strtod`, because all three are documented restrictions rather than gaps. The single most important line in the README is the assumption that the rounding mode is nearest: code that changes `std::fegetround()` breaks that assumption quietly. Include the header, pass `input.data()` and `input.data() + input.size()`, check `answer.ec` against `std::errc()`, and upgrade to the bool-conversion form once you are on the C++26 API.
Frequently asked questions
How do I use fast_float to parse a double?
Include `fast_float/fast_float.h` and call `fast_float::from_chars` with a pointer to the start and one past the end of the range. Check `answer.ec` against `std::errc()` for success, or use the bool conversion if your standard library has the C++26 form.
Is fast_float exact, and how does it round?
The README says it provides exact parsing according to the IEEE standard. Values land on the nearest representable number, and when an input sits exactly between two of them the mantissa is rounded to even.
Does fast_float support long double and hexadecimal floats?
No on both counts, and both are listed as restrictions. The library supports `float` and `double` plus fixed-width types such as `std::float16_t` and `std::bfloat16_t`, but not `long double`, and only the decimal format is supported, not hexadecimal strings.
How does fast_float compare with strtod?
The repository description claims 4x to 10x faster than strtod, and the README claims to be faster than comparable standard library parsing functions. Neither makes speed claims in the README body beyond that, and strtod remains the choice when you need locale aware parsing.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/fastfloat-fast-float)