Skip to content

About

The C++ standard library's <charconv> is already fast. void-numerics is faster — substantially so on hot integer-conversion paths — without sacrificing correctness, portability, or API compatibility.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

void-numerics

SWAR-optimized integer conversion for C++23.

vn::to_chars and vn::from_chars - drop-in replacements for the standard library equivalents*, engineered for performance. Header-only, zero-allocation, no exceptions, no RTTI.

*For integers, float/double parsing/serializing is a WIP

Why

The C++ standard library's <charconv> is already fast. void-numerics is faster - substantially so on hot integer-conversion paths - without sacrificing correctness, portability, or API compatibility.

Every conversion is exhaustively unit-tested against std::to_chars / std::from_chars (including the LLVM libc++ test suite) and benchmarked against std, jeaiii, fmt, and strtoll/strtoull across 8-, 16-, 32-, and 64-bit signed and unsigned integer types.


Compiler Support


MSVC GCC CLANG

Operating System Support


Windows Linux Mac


Features

  • API-compatible with std::to_chars / std::from_chars - returns std::to_chars_result / std::from_chars_result
  • All integer types: int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t
  • Header-only - single #include <void-numerics>, no build step required to consume
  • constexpr - base-10 vn::to_chars and vn::from_chars can be evaluated at compile time
  • Zero allocation, zero exceptions, zero RTTI
  • C++23 - leverages concepts for type-correct dispatch
  • Compile-time tables for digit conversion and overflow bounds
  • Multiply-and-shift division replacement for hot paths
  • Fixed-length SWAR parsing - vn::from_chars dispatches to a parser sized to the digit count, validating and folding 8/4/2/1-byte chunks
  • Force-inlined helpers with manual ladder dispatch sized to digit count
  • Cross-platform CI: Linux Clang-20, Linux GCC-14, macOS Clang, macOS GCC-15, Windows MSVC
  • AddressSanitizer / UndefinedBehaviorSanitizer support out of the box

Quick Start

Warning: Include only <void-numerics>. Direct inclusion of internal headers may cause unrelated code in the including translation unit to become uncompilable.

Integer → string

#include <void-numerics>

int main() {
    char buffer[32];
    int64_t value = -9223372036854775807LL;

    auto result = vn::to_chars(buffer, buffer + sizeof(buffer), value);
    if (result.ec == std::errc{}) {
        // result.ptr points one past the last written character
        std::string_view text(buffer, result.ptr - buffer);
        // text == "-9223372036854775807"
    }
}

string → integer

#include <void-numerics>

int main() {
    const char* input = "42abc";
    uint32_t value{};

    auto result = vn::from_chars(input, input + 5, value);
    // result.ptr points to 'a' (first non-digit)
    // result.ec == std::errc{}
    // value == 42
}

Base support

vn::to_chars and vn::from_chars accept an optional base parameter (defaulting to 10). Non-base-10 conversions transparently dispatch to std::to_chars / std::from_chars for full standard-library compatibility:

char buf[32];
auto r = vn::to_chars(buf, buf + 32, 0xDEADBEEF, 16);
// r.ptr - buf == 8, buf == "deadbeef"

API

vn::to_chars

template<integer_types v_type>
std::to_chars_result to_chars(char* first, char* last, v_type value, int32_t base = 10) noexcept;

Writes the textual representation of value to [first, last). Returns {ptr, std::errc{}} on success, where ptr is one past the last character written. Returns {last, std::errc::value_too_large} if the buffer is too small.

vn::from_chars

template<integer_types v_type>
std::from_chars_result from_chars(const char* first, const char* last, v_type& value, int32_t base = 10) noexcept;

Parses an integer from [first, last) into value. Returns {ptr, std::errc{}} on success, where ptr points to the first character not consumed. Returns {first, std::errc::invalid_argument} if no characters could be parsed (empty input, a lone -, or - on an unsigned type). Returns {ptr, std::errc::result_out_of_range} if the value does not fit in v_type, where ptr points past the full run of digits and value is left unmodified.

bool is not accepted as an integer type.


Building

void-numerics is header-only - copy include/ into your project and #include <void-numerics>, or consume via CMake.

CMake

add_subdirectory(void-numerics)
target_link_libraries(your_target PRIVATE void-numerics::void-numerics)

CMake options

Option Default Description
VN_UNIT_TESTS OFF Build the unit test suite
VN_ASAN OFF Enable AddressSanitizer for the unit tests
VN_UBSAN OFF Enable UndefinedBehaviorSanitizer for the unit tests

Sanitizers are automatically disabled for GCC on macOS, and VN_UBSAN has no effect on MSVC.

Requirements

  • C++23-capable compiler (Clang ≥ 20, GCC ≥ 14, MSVC latest, AppleClang)
  • CMake ≥ 3.28

Testing

The unit tests use rt-ut, our header-only C++20 unit testing framework. CMake fetches it automatically through FetchContent when VN_UNIT_TESTS is on, so there is nothing to install.

The test suite covers:

  • Exhaustive value enumeration for int8/uint8/int16/uint16
  • Digit-length sweeps (1 through max digits) for all types
  • Powers of 2, powers of 10, all-1s, all-9s patterns
  • Limits: min, min+1, min/2, max, max-1, max/2, max/3, max/7
  • Round-trip verification (to_chars → from_chars → equality)
  • Leading-zero handling
  • Overflow detection for max()+1, all-9s, and long runs of leading zeros
  • Compile-time (constexpr) conversions
  • Stop-at-non-digit semantics (whitespace, alpha, dot, sign)
  • Lone-minus and empty-input edge cases
  • The full LLVM libc++ <charconv> test suite (to_chars, from_chars, integral-pass, roundtrip)
cmake -S . -B build -DVN_UNIT_TESTS=TRUE -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/bin/vn-unit-tests

Benchmarks

Benchmarks live in their own repository: stringint-benchmarks. They compare vn::to_chars against std::to_chars, jeaiii::to_text, and fmt::format_to, and vn::from_chars against std::from_chars and strtoll/strtoull across all integer types and digit lengths, including a dedicated leading-zero test.


Memory Footprint

vn::to_chars uses a precomputed lookup table of approximately 40KB to enable branch-free digit pair extraction. This is well-suited to any CPU with an L2 cache of 256KB or larger (all modern desktop, server, and laptop CPUs) - the table comfortably resides in L2 while streaming integer data flows through L1, with no contention between the two. Benchmarks demonstrate this behavior holds even under explicit cache-eviction pressure between runs.

vn::from_chars carries no significant lookup tables and is suitable for any target.

vn::to_chars is not recommended for:

  • Microcontrollers without L2 cache (Cortex-M0/M3/M4/M7, AVR, MSP430) - the table cannot fit in available cache or SRAM
  • Embedded targets where total flash/SRAM is smaller than the lookup table itself
  • Any environment where the 40KB constant data footprint is unacceptable for binary size reasons

For these targets, std::to_chars will be more appropriate.


License

MIT. See License.md.

Copyright © 2026 Nihilai Collective Corp.


About

The C++ standard library's <charconv> is already fast. void-numerics is faster — substantially so on hot integer-conversion paths — without sacrificing correctness, portability, or API compatibility.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages