Skip to content
6 changes: 6 additions & 0 deletions .github/workflows/c.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ jobs:
run: sudo apt-get update && sudo apt-get install -y clang gcc make python3
- name: make test
run: make test
- name: cmake configure
run: cmake -B build
- name: cmake build
run: cmake --build build -j 2
- name: cmake test
run: ctest --test-dir build -j 2
- name: make example
run: make example
- name: Check dirty single-header
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: VS17-CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
ci:
name: windows-vs17
runs-on: windows-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: Configure
run: |
cmake -B build
- name: Build Debug
run: cmake --build build --config Debug --verbose
- name: Build Release
run: cmake --build build --config Release --verbose
- name: Test Debug
run: ctest --test-dir build -C Debug
- name: Test Release
run: ctest --test-dir build -C Release
114 changes: 114 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
cmake_minimum_required(VERSION 3.15)

project(ffc VERSION 1.0.0 LANGUAGES C)

include(FetchContent)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

# Header-only library
add_library(ffc INTERFACE)
add_library(ffc::ffc ALIAS ffc)

target_include_directories(ffc INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)

# Internal header library from src/
add_library(ffc_src INTERFACE)
target_include_directories(ffc_src INTERFACE src/)

# Supplemental test data
add_library(supplemental-data INTERFACE)

# Install library and header
install(TARGETS ffc
EXPORT ffcTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)

install(EXPORT ffcTargets
FILE ffcTargets.cmake
NAMESPACE ffc::
DESTINATION lib/cmake/ffc
)

install(FILES ffc.h DESTINATION include)

# Tests
enable_testing()

# Copy CSV test files to build directory
file(GLOB csv_files test_src/*.csv)
file(COPY ${csv_files} DESTINATION ${CMAKE_BINARY_DIR}/test_src/)

if(NOT WIN32)
add_executable(test_runner test_src/test.c)
target_compile_definitions(test_runner PRIVATE FFC_IMPL
_POSIX_C_SOURCE=200809L _DEFAULT_SOURCE)
target_include_directories(test_runner PRIVATE . test_src)
target_link_libraries(test_runner PRIVATE m)

add_test(NAME test_runner COMMAND test_runner)
endif()

add_executable(test_int_runner test_src/test_int.c)
target_compile_definitions(test_int_runner PRIVATE FFC_IMPL)
target_include_directories(test_int_runner PRIVATE . test_src)
target_link_libraries(test_int_runner PRIVATE $<$<NOT:$<C_COMPILER_ID:MSVC>>:m>)

add_test(NAME test_int_runner COMMAND test_int_runner)

# Tests using src/ headers
if(NOT WIN32)
add_executable(test_runner_src test_src/test.c)
target_compile_definitions(test_runner_src PRIVATE FFC_IMPL
_POSIX_C_SOURCE=200809L _DEFAULT_SOURCE)
target_include_directories(test_runner_src PRIVATE test_src)
target_link_libraries(test_runner_src PRIVATE ffc_src m)

add_test(NAME test_runner_src COMMAND test_runner_src)
endif()

add_executable(test_int_runner_src test_src/test_int.c)
target_compile_definitions(test_int_runner_src PRIVATE FFC_IMPL)
target_include_directories(test_int_runner_src PRIVATE test_src)
target_link_libraries(test_int_runner_src PRIVATE ffc_src $<$<NOT:$<C_COMPILER_ID:MSVC>>:m>)

add_test(NAME test_int_runner_src COMMAND test_int_runner_src)

# Example
add_executable(example example.c)
target_include_directories(example PRIVATE .)


FetchContent_Declare(supplemental_test_files
GIT_REPOSITORY https://github.com/fastfloat/supplemental_test_files.git
GIT_TAG origin/main)
message(STATUS "Retrieving test files.")
FetchContent_MakeAvailable(supplemental_test_files)
message(STATUS "Supplemental test files retrieved.")
target_compile_definitions(supplemental-data INTERFACE SUPPLEMENTAL_TEST_DATA_DIR="${supplemental_test_files_BINARY_DIR}/data")



# Supplemental tests
add_executable(supplemental_tests test_src/supplemental_tests.c)
target_include_directories(supplemental_tests PRIVATE . test_src)
target_link_libraries(supplemental_tests PRIVATE $<$<NOT:$<C_COMPILER_ID:MSVC>>:m> supplemental-data)
target_compile_definitions(supplemental_tests PRIVATE FFC_IMPL)

add_test(NAME supplemental_tests COMMAND supplemental_tests)

# Supplemental tests using src/ headers
add_executable(supplemental_tests_src test_src/supplemental_tests.c)
target_include_directories(supplemental_tests_src PRIVATE src/ test_src)
target_link_libraries(supplemental_tests_src PRIVATE $<$<NOT:$<C_COMPILER_ID:MSVC>>:m> supplemental-data)
target_compile_definitions(supplemental_tests_src PRIVATE FFC_IMPL)

add_test(NAME supplemental_tests_src COMMAND supplemental_tests_src)
87 changes: 87 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,83 @@ int main(void) {
For use within a larger parser, where you don't expect to reach the end of input, use
the non-simple variants as the `ffc_result` includes the stopping point, just like in fast_float

## API

### Float Parsing

- `double ffc_parse_double_simple(size_t len, const char *s, ffc_outcome *outcome)`
Parses a double from a string of given length. Returns the parsed value, outcome indicates success/failure.

- `ffc_result ffc_parse_double(size_t len, const char *s, double *out)`
Parses a double from a string, storing result in `out`. Returns `ffc_result` with outcome and end pointer.

- `float ffc_parse_float_simple(size_t len, const char *s, ffc_outcome *outcome)`
Parses a float from a string of given length. Returns the parsed value.

- `ffc_result ffc_parse_float(size_t len, const char *s, float *out)`
Parses a float from a string, storing result in `out`.

### Integer Parsing

- `ffc_result ffc_parse_i64(size_t len, const char *s, int base, int64_t *out)`
Parses a signed 64-bit integer from string with given base.

- `ffc_result ffc_parse_u64(size_t len, const char *s, int base, uint64_t *out)`
Parses an unsigned 64-bit integer from string with given base.

### Types

- `ffc_outcome`: Enum indicating parse result (OK, OUT_OF_RANGE, INVALID_INPUT)
- `ffc_result`: Struct with `ptr` (end of parsed string) and `outcome`

## Building

### With Make

Use the provided Makefile:

```bash
make test
make example
```

### With CMake

ffc.h supports building with CMake as an installable single-header library.

```bash
cmake -B build
cmake --build build
ctest --test-dir build
cmake --install build # Install the library
```

The CMake build creates test executables for both the amalgamated header and the separate src/ headers.


To use ffc.h as a dependency in your CMake project, you have two options:

#### Using FetchContent

```cmake
include(FetchContent)
FetchContent_Declare(
ffc
GIT_REPOSITORY https://github.com/kolemannix/ffc.h.git
GIT_TAG main
)
FetchContent_MakeAvailable(ffc)
target_link_libraries(your_target ffc::ffc)
```

#### Using CPM.cmake

```cmake
include(cpm)
CPMAddPackage("gh:dlemire/ffc.h#main")
target_link_libraries(your_target ffc::ffc)
```

## Benchmarks

Generated using https://github.com/lemire/simple_fastfloat_benchmark on 2026-03-03
Expand Down Expand Up @@ -83,3 +160,13 @@ ffc : 1019.67 MB/s (+/- 7.3 %) 138.91 Mfl
## Caveats
- Does not support wide chars; only 1-byte strings (e.g., UTF8) are supported.
- The 32-bit architecture code is untested


## References

* Daniel Lemire, [Number Parsing at a Gigabyte per
Second](https://arxiv.org/abs/2101.11408), Software: Practice and Experience
51 (8), 2021.
* Noble Mushtak, Daniel Lemire, [Fast Number Parsing Without
Fallback](https://arxiv.org/abs/2212.06644), Software: Practice and Experience
53 (7), 2023.
Loading