dconv 2.0
C++14 library for printing and parsing floating point numbers
Loading...
Searching...
No Matches
dconv

Test Status Security Status Codacy Badge Codacy Badge Codecov Coveralls Doxygen GitHub Releases GitHub License

dconv is a header-only C++20 library for printing and parsing floating point numbers.

Double to string conversion uses the Schubfach algorithm by default, described by Raffaello Giulietti in its publication The Schubfach way to render doubles. It always prints the shortest representation that reads back to the same double, and the closest one when several are possible.

The Grisu, Grisu2 and Grisu3 algorithms, described by Florian Loitsch in Printing Floating-Point Numbers Quickly and Accurately with Integers, are also available. Grisu prints up to 18 digits without looking for the shortest output, Grisu2 prints the shortest output for about 99.9% of the doubles, and Grisu3 detects the remaining cases and falls back to std::to_chars.

String to double conversion uses the Eisel-Lemire algorithm on the first 19 significant digits. Longer mantissas are bracketed between two Eisel-Lemire results; only when they disagree is the number rebuilt from the digits already read and passed to strtod. A stream is read once and never rewound; a buffer is only read again for mantissas longer than 19 digits.

Any help to improve speed, accuracy or code quality is welcome.

Build

Prerequisites

Install build tools, compilers and test dependencies (every preset builds the test suite):

sudo apt install gcc g++ clang clang-tools libclang-rt-dev cmake ninja-build libgtest-dev libgmock-dev

‍Compilers: GCC 11+ or Clang 16+ (C++20). Clang requires libclang-rt-dev for coverage instrumentation (--coverage).

Build from Source

git clone https://github.com/joinframework/dconv.git
cd dconv
cmake --preset gcc-release
cmake --build --preset gcc-release

Available presets are gcc-debug, gcc-release, gcc-asan, clang-debug, clang-release and clang-asan. Debug presets enable coverage, asan presets enable AddressSanitizer and UndefinedBehaviorSanitizer.

dconv is header-only: to install the headers without building the tests, configure without a preset:

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --install build

Build Options

Option Default Description
DCONV_ENABLE_TESTS OFF Build the test suite.
DCONV_ENABLE_COVERAGE OFF Enable code coverage instrumentation (requires Debug build).
DCONV_ENABLE_ASAN OFF Enable AddressSanitizer and UndefinedBehaviorSanitizer.

Run Tests

ctest --preset gcc-release

Usage

The printing API can be used this way:

#include <dconv/dtoa.hpp>
char value[25];
char* end = dconv::dtoa (value, -2.22507e-308);
char * dtoa(char *buffer, double value) noexcept
double to string conversion.
Definition dtoa.hpp:40

Each algorithm has its own header and namespace:

#include <dconv/grisu3.hpp>
char value[25];
char* end = dconv::grisu3::dtoa (value, -2.22507e-308);
char * dtoa(char *buffer, double value) noexcept
double to string conversion using Grisu3.
Definition grisu3.hpp:181
Header Function
dconv/schubfach.hpp dconv::schubfach::dtoa
dconv/grisu3.hpp dconv::grisu3::dtoa
dconv/grisu2.hpp dconv::grisu2::dtoa
dconv/grisu.hpp dconv::grisu::dtoa

The parsing API can be used this way:

#include <dconv/atod.hpp>
double value;
const char* end = dconv::atod ("-2.22507e-308", value);
int atod(StreamProbe &probe, double &value, uint64_t &significand, int64_t &exponent, bool &negative, std::error_code &ec)
stream to double conversion that stops on integers.
Definition atod.hpp:710

atod also accepts a std::string or a std::string_view, and a (first, length) or (first, last) range. A temporary std::string is rejected, since the returned position would point into it.

It can also read from a stream, without buffering it:

#include <dconv/atod.hpp>
#include <sstream>
double value;
std::istringstream in ("-2.22507e-308");
bool ok = dconv::atod (in, value);

A variant reads from a probe and stops on integers, so that integers can be kept as integers:

#include <dconv/atod.hpp>
double value;
uint64_t significand;
int64_t exponent;
bool negative;
std::error_code ec;
dconv::StringProbe probe ("18446744073709551615");
if (dconv::atod (probe, value, significand, exponent, negative, ec) == -1 && ec == dconv::Errc::NotDouble)
{
// significand holds the integer, negative its sign.
}
string probe.
Definition probe.hpp:47

It returns 0 and the converted value for a real number, or -1 with Errc::NotDouble for an integer without fraction nor exponent whose magnitude fits in 64 bits, or -1 with Errc::InvalidNumber when parsing fails. significand, exponent and negative hold what was read until the end or the error.

By default atod reads the general format of std::from_chars: an optional -, digits with an optional point (leading zeros, 1. and .5 are accepted), an optional exponent, and inf, infinity or nan in any case. Leading whitespaces, a leading + and hexadecimal numbers are rejected. A value too large for a double is read as infinity.

A conversion mode, given as a template argument, restricts what is accepted:

#include <dconv/atod.hpp>
double value;
const char* end = dconv::atod<dconv::AtodMode::Json> ("-2.22507e-308", value);
Mode Accepted
AtodMode::General general format (default).
AtodMode::Json JSON numbers only (RFC 8259): no leading zeros, digits on both sides of the point, no inf nor nan.
AtodMode::ParseNanAndInf JSON numbers, inf, infinity and nan.
AtodMode::ParseRelaxedNumber JSON numbers, leading zeros and missing digits around the point.

Modes are bit flags: AtodMode::General is AtodMode::ParseNanAndInf | AtodMode::ParseRelaxedNumber. In general format, an exponent mark not followed by digits (1e+) or a truncated infinity (infinit) is left unread when parsing a buffer; a stream cannot be rewound, so the conversion fails there.

dtoa needs a buffer of at least 25 bytes (26 bytes for grisu::dtoa) and returns the end of the written characters. atod returns the end of the parsed characters, or nullptr on failure. The probe and stream overloads return false on failure and stop right after the number on success. They read the stream buffer directly: whitespaces are not skipped, the stream state is left unchanged and the characters read are consumed even on failure. atod never changes errno.

License

MIT