Replace fast_float (C++) with ffc.h (#3329)

There is now a port of fast_float in C. So instead of having an optional
fast_float dependency, we can just use ffc instead, unconditionally.

https://github.com/kolemannix/ffc.h

It is a high quality port. The performance should be the same or
improved.

Note : I am the maintainer and main author of fast_float.

---------

Signed-off-by: Daniel Lemire <daniel@lemire.me>
This commit is contained in:
Daniel Lemire
2026-03-11 07:26:44 -04:00
committed by GitHub
parent 8051de740d
commit 6414720504
24 changed files with 3393 additions and 4075 deletions
+6 -6
View File
@@ -37,7 +37,7 @@ jobs:
# build with TLS just for compilation coverage
run: |
sudo apt-get install pkg-config libgtest-dev libgmock-dev
make -j4 all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
make -j4 all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes USE_LIBBACKTRACE=yes
- name: test
run: |
sudo apt-get install tcl8.6 tclx
@@ -79,7 +79,7 @@ jobs:
- name: make
# Fail build if there are warnings
# build with TLS just for compilation coverage
run: make -j4 all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
run: make -j4 all-with-unit-tests SERVER_CFLAGS='-Werror' BUILD_TLS=yes USE_LIBBACKTRACE=yes
- name: Install old server (${{ matrix.server.version }}) for compatibility testing
run: |
@@ -238,7 +238,7 @@ jobs:
export CXX=/opt/homebrew/opt/llvm/bin/clang++
export AR=/opt/homebrew/opt/llvm/bin/llvm-ar
export RANLIB=/opt/homebrew/opt/llvm/bin/llvm-ranlib
make -j3 all-with-unit-tests SERVER_CFLAGS='-Werror' USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes LIBBACKTRACE_PREFIX=/usr/local
make -j3 all-with-unit-tests SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes LIBBACKTRACE_PREFIX=/usr/local
build-32bit:
runs-on: ubuntu-latest
@@ -278,7 +278,7 @@ jobs:
# suffixed version of g++. g++-multilib generally includes libstdc++.
# *cross version as well, but it is also added explicitly just in case.
run: |
make -j4 SERVER_CFLAGS='-Werror' 32bit USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes LIBBACKTRACE_PREFIX=/usr/local/libbacktrace32 \
make -j4 SERVER_CFLAGS='-Werror' 32bit USE_LIBBACKTRACE=yes LIBBACKTRACE_PREFIX=/usr/local/libbacktrace32 \
GTEST_CFLAGS="-I/usr/src/googletest/googletest/include -I/usr/src/googletest/googlemock/include" \
GTEST_LIBS="/usr/lib32/libgtest.a /usr/lib32/libgmock.a"
- name: unit tests
@@ -298,7 +298,7 @@ jobs:
- name: Checkout Valkey
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: make
run: make -j4 SERVER_CFLAGS='-Werror' MALLOC=libc USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
run: make -j4 SERVER_CFLAGS='-Werror' MALLOC=libc USE_LIBBACKTRACE=yes
build-almalinux8-jemalloc:
runs-on: ubuntu-latest
@@ -317,7 +317,7 @@ jobs:
- name: Checkout Valkey
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: make
run: make -j4 SERVER_CFLAGS='-Werror' USE_FAST_FLOAT=yes USE_LIBBACKTRACE=yes
run: make -j4 SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
format-yaml:
runs-on: ubuntu-latest
+1 -1
View File
@@ -539,7 +539,7 @@ jobs:
path: libbacktrace
- run: cd libbacktrace && ./configure && make && sudo make install
- name: make
run: make BUILD_TLS=yes SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes USE_FAST_FLOAT=yes
run: make BUILD_TLS=yes SERVER_CFLAGS='-Werror' USE_LIBBACKTRACE=yes
- name: testprep
run: |
sudo apt-get install tcl8.6 tclx tcl-tls
-7
View File
@@ -53,13 +53,6 @@ as libsystemd-dev on Debian/Ubuntu or systemd-devel on CentOS) and run:
% make USE_SYSTEMD=yes
Since Valkey version 8.1, `fast_float` has been introduced as an optional
dependency, which can speed up sorted sets and other commands that use
the double datatype. To build with `fast_float` support, you'll need a
C++ compiler and run:
% make USE_FAST_FLOAT=yes
To build with enhanced stack traces that include file names and line numbers
for all functions (including static functions), use libbacktrace:
+3
View File
@@ -69,6 +69,7 @@ set(VALKEY_SERVER_SRCS
${CMAKE_SOURCE_DIR}/src/sparkline.c
${CMAKE_SOURCE_DIR}/src/valkey-check-rdb.c
${CMAKE_SOURCE_DIR}/src/valkey-check-aof.c
${CMAKE_SOURCE_DIR}/src/valkey_strtod.c
${CMAKE_SOURCE_DIR}/src/geo.c
${CMAKE_SOURCE_DIR}/src/lazyfree.c
${CMAKE_SOURCE_DIR}/src/module.c
@@ -132,6 +133,7 @@ set(VALKEY_CLI_SRCS
${CMAKE_SOURCE_DIR}/src/sha256.c
${CMAKE_SOURCE_DIR}/src/util.c
${CMAKE_SOURCE_DIR}/src/valkey-cli.c
${CMAKE_SOURCE_DIR}/src/valkey_strtod.c
${CMAKE_SOURCE_DIR}/src/zmalloc.c
${CMAKE_SOURCE_DIR}/src/release.c
${CMAKE_SOURCE_DIR}/src/ae.c
@@ -155,6 +157,7 @@ set(VALKEY_BENCHMARK_SRCS
${CMAKE_SOURCE_DIR}/src/sha256.c
${CMAKE_SOURCE_DIR}/src/util.c
${CMAKE_SOURCE_DIR}/src/valkey-benchmark.c
${CMAKE_SOURCE_DIR}/src/valkey_strtod.c
${CMAKE_SOURCE_DIR}/src/adlist.c
${CMAKE_SOURCE_DIR}/src/dict.c
${CMAKE_SOURCE_DIR}/src/zmalloc.c
+1
View File
@@ -33,6 +33,7 @@ add_subdirectory(libvalkey)
add_subdirectory(linenoise)
add_subdirectory(fpconv)
add_subdirectory(hdr_histogram)
add_subdirectory(fast_float)
# Clear any cached variables passed to libvalkey from the cache
unset(BUILD_SHARED_LIBS CACHE)
-7
View File
@@ -42,7 +42,6 @@ distclean:
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
-(cd fpconv && $(MAKE) clean) > /dev/null || true
-(cd fast_float_c_interface && $(MAKE) clean) > /dev/null || true
-(rm -f .make-*)
.PHONY: distclean
@@ -126,12 +125,6 @@ jemalloc: .make-prerequisites
.PHONY: jemalloc
fast_float_c_interface: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd fast_float_c_interface && $(MAKE)
.PHONY: fast_float_c_interface
gtest-parallel: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
@if [ ! -f gtest-parallel/gtest_parallel.py ]; then \
+6 -12
View File
@@ -6,7 +6,7 @@ should be provided by the operating system.
* **linenoise** is a readline replacement. It is developed by the same authors of Valkey but is managed as a separated project and updated as needed.
* **lua** is Lua 5.1 with minor changes for security and additional libraries.
* **hdr_histogram** Used for per-command latency tracking histograms.
* **fast_float** is a replacement for strtod to convert strings to floats efficiently.
* **ffc.h** is a C99 port of the fast_float library, used as a replacement for strtod to convert strings to floats efficiently.
* **gtest-parallel** is a script for running googletest tests in parallel.
How to upgrade the above dependencies
@@ -108,20 +108,14 @@ We use a customized version based on master branch commit e4448cf6d1cd08fff51981
2. Copy updated files from newer version onto files in /hdr_histogram.
3. Apply the changes from 1 above to the updated files.
fast_float
ffc.h
---
The fast_float library provides fast header-only implementations for the C++ from_chars functions for `float` and `double` types as well as integer types. These functions convert ASCII strings representing decimal values (e.g., `1.3e10`) into binary types. The functions are much faster than comparable number-parsing functions from existing C++ standard libraries.
Specifically, `fast_float` provides the following function to parse floating-point numbers with a C++17-like syntax (the library itself only requires C++11):
template <typename T, typename UC = char, typename = FASTFLOAT_ENABLE_IF(is_supported_float_type<T>())>
from_chars_result_t<UC> from_chars(UC const *first, UC const *last, T &value, chars_format fmt = chars_format::general);
ffc.h is a pure C99 port of the fast_float library, providing fast string-to-double
conversion without requiring a C++ compiler.
To upgrade the library,
1. Check out https://github.com/fastfloat/fast_float/tree/main
2. cd fast_float
3. Invoke "python3 ./script/amalgamate.py --output fast_float.h"
4. Copy fast_float.h file to "deps/fast_float/".
1. Download the latest ffc.h from https://github.com/kolemannix/ffc.h/releases
2. Copy ffc.h to "deps/fast_float/".
gtest-parallel
---
+2
View File
@@ -0,0 +1,2 @@
add_library(ffc INTERFACE)
target_include_directories(ffc INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
-3912
View File
File diff suppressed because it is too large Load Diff
+3235
View File
File diff suppressed because one or more lines are too long
-37
View File
@@ -1,37 +0,0 @@
CCCOLOR:="\033[34m"
SRCCOLOR:="\033[33m"
ENDCOLOR:="\033[0m"
CXX?=c++
# we need = instead of := so that $@ in QUIET_CXX gets evaluated in the rule and is assigned appropriate value.
TEMP:=$(CXX)
QUIET_CXX=@printf ' %b %b\n' $(CCCOLOR)C++$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2;
CXX=$(QUIET_CXX)$(TEMP)
WARN=-Wall -W -Wno-missing-field-initializers
STD=-pedantic -std=c++11
OPT?=-O3
CLANG := $(findstring clang,$(shell sh -c '$(CC) --version | head -1'))
ifeq ($(OPT),-O3)
ifeq (clang,$(CLANG))
OPT+=-flto
else
OPT+=-flto=auto -ffat-lto-objects
endif
endif
# 1) Today src/Makefile passes -m32 flag for explicit 32-bit build on 64-bit machine, via CFLAGS. For 32-bit build on
# 32-bit machine and 64-bit on 64-bit machine, CFLAGS are empty. No other flags are set that can conflict with C++,
# therefore let's use CFLAGS without changes for now.
# 2) FASTFLOAT_ALLOWS_LEADING_PLUS allows +inf to be parsed as inf, instead of error.
CXXFLAGS=$(STD) $(OPT) $(WARN) -static -fPIC -fno-exceptions $(CFLAGS) -D FASTFLOAT_ALLOWS_LEADING_PLUS
.PHONY: all clean
all: fast_float_strtod.o
clean:
rm -f *.o || true;
-24
View File
@@ -1,24 +0,0 @@
/*
* Copyright (c) Valkey Contributors
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "../fast_float/fast_float.h"
#include <cerrno>
extern "C"
{
double fast_float_strtod(const char *str, const char** endptr)
{
double temp = 0;
auto answer = fast_float::from_chars(str, str + strlen(str), temp);
if (answer.ec != std::errc()) {
errno = (answer.ec == std::errc::result_out_of_range) ? ERANGE : EINVAL;
}
if (endptr) {
*endptr = answer.ptr;
}
return temp;
}
}
+3
View File
@@ -10,6 +10,7 @@ message(STATUS "CFLAGS: ${CMAKE_C_FLAGS}")
get_valkey_server_linker_option(VALKEY_SERVER_LDFLAGS)
list(APPEND SERVER_LIBS "fpconv")
list(APPEND SERVER_LIBS "hdr_histogram")
list(APPEND SERVER_LIBS "ffc")
valkey_build_and_install_bin(valkey-server "${VALKEY_SERVER_SRCS}" "${VALKEY_SERVER_LDFLAGS}" "${SERVER_LIBS}"
"redis-server")
add_dependencies(valkey-server generate_commands_def)
@@ -60,6 +61,7 @@ unset(BUILD_SANITIZER CACHE)
# Target: valkey-cli
list(APPEND CLI_LIBS "fpconv")
list(APPEND CLI_LIBS "linenoise")
list(APPEND CLI_LIBS "ffc")
valkey_build_and_install_bin(valkey-cli "${VALKEY_CLI_SRCS}" "${VALKEY_SERVER_LDFLAGS}" "${CLI_LIBS}" "redis-cli")
add_dependencies(valkey-cli generate_commands_def)
add_dependencies(valkey-cli generate_fmtargs_h)
@@ -67,6 +69,7 @@ add_dependencies(valkey-cli generate_fmtargs_h)
# Target: valkey-benchmark
list(APPEND BENCH_LIBS "fpconv")
list(APPEND BENCH_LIBS "hdr_histogram")
list(APPEND BENCH_LIBS "ffc")
valkey_build_and_install_bin(valkey-benchmark "${VALKEY_BENCHMARK_SRCS}" "${VALKEY_SERVER_LDFLAGS}" "${BENCH_LIBS}"
"redis-benchmark")
add_dependencies(valkey-benchmark generate_commands_def)
+4 -13
View File
@@ -251,7 +251,7 @@ ifdef OPENSSL_PREFIX
endif
# Include paths to dependencies
FINAL_CFLAGS+= -I../deps/libvalkey/include -I../deps/linenoise -I../deps/hdr_histogram -I../deps/fpconv
FINAL_CFLAGS+= -I../deps/libvalkey/include -I../deps/linenoise -I../deps/hdr_histogram -I../deps/fpconv -I../deps/fast_float
# Lua scripting engine module
LUA_MODULE_NAME:=modules/lua/libvalkeylua.so
@@ -556,6 +556,7 @@ ENGINE_SERVER_OBJ = \
util.o \
valkey-check-aof.o \
valkey-check-rdb.o \
valkey_strtod.o \
vector.o \
vset.o \
ziplist.o \
@@ -584,6 +585,7 @@ ENGINE_CLI_OBJ = \
strl.o \
util.o \
valkey-cli.o \
valkey_strtod.o \
zmalloc.o
ENGINE_BENCHMARK_NAME=$(ENGINE_NAME)-benchmark$(PROG_SUFFIX)
ENGINE_BENCHMARK_OBJ = \
@@ -609,6 +611,7 @@ ENGINE_BENCHMARK_OBJ = \
strl.o \
util.o \
valkey-benchmark.o \
valkey_strtod.o \
zmalloc.o
ENGINE_CHECK_RDB_NAME=$(ENGINE_NAME)-check-rdb$(PROG_SUFFIX)
ENGINE_CHECK_AOF_NAME=$(ENGINE_NAME)-check-aof$(PROG_SUFFIX)
@@ -616,17 +619,6 @@ ENGINE_LIB_NAME=lib$(ENGINE_NAME).a
ENGINE_UNIT_GTESTS:=$(ENGINE_NAME)-unit-gtests$(PROG_SUFFIX)
ALL_SOURCES=$(sort $(patsubst %.o,%.c,$(ENGINE_SERVER_OBJ) $(ENGINE_CLI_OBJ) $(ENGINE_BENCHMARK_OBJ)))
USE_FAST_FLOAT?=no
ifeq ($(USE_FAST_FLOAT),yes)
# valkey_strtod.h uses this flag to switch valkey_strtod function to fast_float_strtod,
# therefore let's pass it to compiler for preprocessing.
FINAL_CFLAGS += -D USE_FAST_FLOAT
# next, let's build and add actual library containing fast_float_strtod function for linking.
DEPENDENCY_TARGETS += fast_float_c_interface
FAST_FLOAT_STRTOD_OBJECT := ../deps/fast_float_c_interface/fast_float_strtod.o
FINAL_LIBS += $(FAST_FLOAT_STRTOD_OBJECT)
endif
USE_LIBBACKTRACE?=no
ifeq ($(USE_LIBBACKTRACE),yes)
FINAL_CFLAGS += -DUSE_LIBBACKTRACE
@@ -668,7 +660,6 @@ persist-settings: distclean
echo BUILD_RDMA=$(BUILD_RDMA) >> .make-settings
echo USE_SYSTEMD=$(USE_SYSTEMD) >> .make-settings
echo BUILD_LUA=$(BUILD_LUA) >> .make-settings
echo USE_FAST_FLOAT=$(USE_FAST_FLOAT) >> .make-settings
echo USE_LIBBACKTRACE=$(USE_LIBBACKTRACE) >> .make-settings
echo LIBBACKTRACE_PREFIX=$(LIBBACKTRACE_PREFIX) >> .make-settings
echo CFLAGS=$(CFLAGS) >> .make-settings
+1 -1
View File
@@ -880,7 +880,7 @@ void debugCommand(client *c) {
"string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false");
}
} else if (!strcasecmp(objectGetVal(c->argv[1]), "sleep") && c->argc == 3) {
double dtime = valkey_strtod(objectGetVal(c->argv[2]), NULL);
double dtime = valkey_strtod_sds(objectGetVal(c->argv[2]), NULL);
long long utime = dtime * 1000000;
struct timespec tv;
+1 -4
View File
@@ -150,13 +150,10 @@ static int parseDouble(ReplyParser *parser, void *p_ctx) {
const char *proto = parser->curr_location;
const char *p = strchr(proto + 1, '\r');
parser->curr_location = p + 2; /* for \r\n */
char buf[MAX_LONG_DOUBLE_CHARS + 1];
size_t len = p - proto - 1;
double d = 0;
if (len <= MAX_LONG_DOUBLE_CHARS) {
memcpy(buf, proto + 1, len);
buf[len] = '\0';
d = valkey_strtod(buf, NULL); /* We expect a valid representation. */
d = valkey_strtod_n(proto + 1, len, NULL); /* We expect a valid representation. */
}
parser->callbacks.double_callback(p_ctx, d, proto, parser->curr_location - proto);
return C_OK;
+1 -1
View File
@@ -484,7 +484,7 @@ void sortCommandGeneric(client *c, int readonly) {
if (sdsEncodedObject(byval)) {
char *eptr;
errno = 0;
vector[j].u.score = valkey_strtod(objectGetVal(byval), &eptr);
vector[j].u.score = valkey_strtod_sds(objectGetVal(byval), &eptr);
if (eptr[0] != '\0' || errno == ERANGE || errno == EINVAL || isnan(vector[j].u.score)) {
int_conversion_error = 1;
}
+11 -11
View File
@@ -630,24 +630,28 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
if (min->encoding == OBJ_ENCODING_INT) {
spec->min = (long)objectGetVal(min);
} else {
if (((char *)objectGetVal(min))[0] == '(') {
spec->min = valkey_strtod((char *)objectGetVal(min) + 1, &eptr);
char *s = objectGetVal(min);
size_t len = sdslen(s);
if (s[0] == '(') {
spec->min = valkey_strtod_n(s + 1, len - 1, &eptr);
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
spec->minex = 1;
} else {
spec->min = valkey_strtod((char *)objectGetVal(min), &eptr);
spec->min = valkey_strtod_n(s, len, &eptr);
if (eptr[0] != '\0' || isnan(spec->min)) return C_ERR;
}
}
if (max->encoding == OBJ_ENCODING_INT) {
spec->max = (long)objectGetVal(max);
} else {
if (((char *)objectGetVal(max))[0] == '(') {
spec->max = valkey_strtod((char *)objectGetVal(max) + 1, &eptr);
char *s = objectGetVal(max);
size_t len = sdslen(s);
if (s[0] == '(') {
spec->max = valkey_strtod_n(s + 1, len - 1, &eptr);
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
spec->maxex = 1;
} else {
spec->max = valkey_strtod((char *)objectGetVal(max), &eptr);
spec->max = valkey_strtod_n(s, len, &eptr);
if (eptr[0] != '\0' || isnan(spec->max)) return C_ERR;
}
}
@@ -841,11 +845,7 @@ zskiplistNode *zslNthInLexRange(zskiplist *zsl, zlexrangespec *range, long n) {
*----------------------------------------------------------------------------*/
static double zzlStrtod(unsigned char *vstr, unsigned int vlen) {
char buf[128];
if (vlen > sizeof(buf) - 1) vlen = sizeof(buf) - 1;
memcpy(buf, vstr, vlen);
buf[vlen] = '\0';
return valkey_strtod(buf, NULL);
return valkey_strtod_n((const char *)vstr, vlen, NULL);
}
double zzlGetScore(unsigned char *sptr) {
+1
View File
@@ -43,6 +43,7 @@ add_library(valkeylib-gtest STATIC ${VALKEY_SERVER_SRCS})
target_compile_options(valkeylib-gtest PRIVATE -Og -g -fno-lto)
target_compile_definitions(valkeylib-gtest PRIVATE "${COMPILE_DEFINITIONS}")
target_include_directories(valkeylib-gtest PRIVATE ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/deps)
target_link_libraries(valkeylib-gtest ffc)
# Generate wrapper files from wrappers.h
set(GENERATED_WRAPPERS_DIR ${CMAKE_BINARY_DIR}/gtest_generated)
-6
View File
@@ -105,7 +105,6 @@ endif
ifeq ($(strip $(GTEST_LIBS)),)
$(error googletest not found. Install gtest or set GTEST_CFLAGS/GTEST_LIBS explicitly)
endif
FAST_FLOAT_LIB := ../../deps/fast_float_c_interface/fast_float_strtod.o
# On Darwin (macOS), the linker does not support -Wl,--wrap,
# so we manually define __wrap_* and __real_* for each function in wrappers.h.
@@ -167,11 +166,6 @@ ifeq ($(USE_LIBBACKTRACE),yes)
LD_LIBS += -lbacktrace
endif
# Add fast_float support if enabled
ifeq ($(USE_FAST_FLOAT),yes)
LD_LIBS += $(FAST_FLOAT_LIB)
endif
# Add TLS libraries if enabled
ifeq ($(BUILD_TLS),yes)
LD_LIBS += $(TLS_LIBS)
+19
View File
@@ -33,3 +33,22 @@ TEST_F(ValkeyStrtodTest, TestValkeyStrtod) {
ASSERT_TRUE(isinf(value));
ASSERT_EQ(errno, 0);
}
TEST_F(ValkeyStrtodTest, TestValkeyStrtodN) {
errno = 0;
double value = valkey_strtod_n("231.2341234", 11, NULL);
ASSERT_DOUBLE_EQ(value, 231.2341234);
ASSERT_EQ(errno, 0);
value = valkey_strtod_n("+inf", 4, NULL);
ASSERT_TRUE(isinf(value));
ASSERT_EQ(errno, 0);
value = valkey_strtod_n("-inf", 4, NULL);
ASSERT_TRUE(isinf(value));
ASSERT_EQ(errno, 0);
value = valkey_strtod_n("inf", 3, NULL);
ASSERT_TRUE(isinf(value));
ASSERT_EQ(errno, 0);
}
+1 -1
View File
@@ -768,7 +768,7 @@ int string2ld(const char *s, size_t slen, long double *dp) {
int string2d(const char *s, size_t slen, double *dp) {
errno = 0;
char *eptr;
*dp = valkey_strtod(s, &eptr);
*dp = valkey_strtod_n(s, slen, &eptr);
if (slen == 0 || isspace(((const char *)s)[0]) || (size_t)(eptr - (char *)s) != slen ||
(errno == ERANGE && (*dp == HUGE_VAL || *dp == -HUGE_VAL || fpclassify(*dp) == FP_ZERO)) || isnan(*dp) || errno == EINVAL) {
errno = 0;
+64
View File
@@ -0,0 +1,64 @@
#include "valkey_strtod.h"
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include "sds.h"
#define FFC_IMPL
#define FFC_DEBUG 0
#include "ffc.h"
const ffc_parse_options valkey_strtod_options = {
FFC_PRESET_GENERAL | FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS,
'.'};
/**
* Converts a null-terminated string to a double-precision floating-point number.
* On success, returns the converted value and sets *endptr to point past the
* last parsed character. On failure, returns 0.0 and sets errno appropriately.
*/
double valkey_strtod(const char *str, char **endptr) {
errno = 0;
double temp = 0.0;
ffc_result answer = ffc_from_chars_double_options(str, str + strlen(str), &temp, valkey_strtod_options);
if (answer.outcome != FFC_OUTCOME_OK) {
errno = (answer.outcome == FFC_OUTCOME_OUT_OF_RANGE) ? ERANGE : EINVAL;
}
if (endptr) {
*endptr = (char *)answer.ptr;
}
return temp;
}
/**
* Converts a string of specified length to a double-precision floating-point number.
* Unlike valkey_strtod, this function does not require the string to be null-terminated,
* making it suitable for parsing substrings. On success, returns the converted value
* and sets *endptr to point past the last parsed character. On failure, returns 0.0
* and sets errno appropriately.
*/
double valkey_strtod_n(const char *str, size_t len, char **endptr) {
errno = 0;
double temp = 0.0;
ffc_result answer = ffc_from_chars_double_options(str, str + len, &temp, valkey_strtod_options);
if (answer.outcome != FFC_OUTCOME_OK) {
errno = (answer.outcome == FFC_OUTCOME_OUT_OF_RANGE) ? ERANGE : EINVAL;
}
if (endptr) {
*endptr = (char *)answer.ptr;
}
return temp;
}
/**
* Converts an SDS string to a double-precision floating-point number.
* This is a convenience wrapper around valkey_strtod_n that automatically
* determines the string length using sdslen(). On success, returns the converted
* value and sets *endptr to point past the last parsed character. On failure,
* returns 0.0 and sets errno appropriately.
*/
double valkey_strtod_sds(sds str, char **endptr) {
errno = 0;
return valkey_strtod_n(str, sdslen(str), endptr);
}
+33 -32
View File
@@ -1,42 +1,43 @@
#ifndef FAST_FLOAT_STRTOD_H
#define FAST_FLOAT_STRTOD_H
#ifndef VALKEY_STRTOD_H
#define VALKEY_STRTOD_H
#ifdef USE_FAST_FLOAT
#include "errno.h"
#include "sds.h"
/**
* Converts a null-terminated byte string to a double using the fast_float library.
* Converts a string to a double using ffc.h (https://github.com/kolemannix/ffc.h),
* a C99 port of the fast_float library.
*
* This function provides a C-compatible wrapper around the fast_float library's string-to-double
* conversion functionality. It aims to offer a faster alternative to the standard strtod function.
* valkey_strtod: takes a null-terminated string.
* valkey_strtod_n: takes a pointer and length, avoiding strlen.
*
* str: A pointer to the null-terminated byte string to be converted.
* eptr: On success, stores char pointer pointing to '\0' at the end of the string.
* On failure, stores char pointer pointing to first invalid character in the string.
* returns: On success, the function returns the converted double value.
* On failure, it returns 0.0 and stores error code in errno to ERANGE or EINVAL.
*
* note: This function uses the fast_float library (https://github.com/fastfloat/fast_float) for
* the actual conversion, which can be significantly faster than standard library functions.
* Refer to "../deps/fast_float_c_interface" for more details.
* Refer to https://github.com/fastfloat/fast_float for more information on the underlying library.
* On success, returns the converted value and sets *endptr past the parsed characters.
* On failure, returns 0.0, sets errno to ERANGE or EINVAL, and sets *endptr to
* the first invalid character.
*/
double fast_float_strtod(const char *str, char **endptr);
static inline double valkey_strtod(const char *str, char **endptr) {
errno = 0;
return fast_float_strtod(str, endptr);
}
/**
* Converts a null-terminated string to a double-precision floating-point number.
* On success, returns the converted value and sets *endptr to point past the
* last parsed character. On failure, returns 0.0 and sets errno appropriately.
*/
double valkey_strtod(const char *str, char **endptr);
#else
/**
* Converts a string of specified length to a double-precision floating-point number.
* Unlike valkey_strtod, this function does not require the string to be null-terminated,
* making it suitable for parsing substrings. On success, returns the converted value
* and sets *endptr to point past the last parsed character. On failure, returns 0.0
* and sets errno appropriately.
*/
double valkey_strtod_n(const char *str, size_t len, char **endptr);
#include <stdlib.h>
/**
* Converts an SDS string to a double-precision floating-point number.
* This is a convenience wrapper around valkey_strtod_n that automatically
* determines the string length using sdslen(). On success, returns the converted
* value and sets *endptr to point past the last parsed character. On failure,
* returns 0.0 and sets errno appropriately.
*/
double valkey_strtod_sds(sds str, char **endptr);
static inline double valkey_strtod(const char *str, char **endptr) {
return strtod(str, endptr);
}
#endif
#endif // FAST_FLOAT_STRTOD_H
#endif // VALKEY_STRTOD_H