diff --git a/src/module.c b/src/module.c index f9f654b42..68d1d023d 100644 --- a/src/module.c +++ b/src/module.c @@ -3716,6 +3716,31 @@ loaderr: return 0; } +/* In the context of the rdb_save method of a module data type, saves a long double + * value to the RDB file. The double can be a valid number, a NaN or infinity. + * It is possible to load back the value with RedisModule_LoadLongDouble(). */ +void RM_SaveLongDouble(RedisModuleIO *io, long double value) { + if (io->error) return; + char buf[MAX_LONG_DOUBLE_CHARS]; + /* Long double has different number of bits in different platforms, so we + * save it as a string type. */ + size_t len = ld2string(buf,sizeof(buf),value,LD_STR_HEX); + RM_SaveStringBuffer(io,buf,len+1); /* len+1 for '\0' */ +} + +/* In the context of the rdb_save method of a module data type, loads back the + * long double value saved by RedisModule_SaveLongDouble(). */ +long double RM_LoadLongDouble(RedisModuleIO *io) { + if (io->error) return 0; + long double value; + size_t len; + char* str = RM_LoadStringBuffer(io,&len); + if (!str) return 0; + string2ld(str,len,&value); + RM_Free(str); + return value; +} + /* Iterate over modules, and trigger rdb aux saving for the ones modules types * who asked for it. */ ssize_t rdbSaveModulesAux(rio *rdb, int when) { @@ -6669,6 +6694,8 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(LoadDouble); REGISTER_API(SaveFloat); REGISTER_API(LoadFloat); + REGISTER_API(SaveLongDouble); + REGISTER_API(LoadLongDouble); REGISTER_API(EmitAOF); REGISTER_API(Log); REGISTER_API(LogIOError); diff --git a/src/networking.c b/src/networking.c index e7cc561fa..428ab14ce 100644 --- a/src/networking.c +++ b/src/networking.c @@ -530,7 +530,7 @@ void addReplyHumanLongDouble(client *c, long double d) { decrRefCount(o); } else { char buf[MAX_LONG_DOUBLE_CHARS]; - int len = ld2string(buf,sizeof(buf),d,1); + int len = ld2string(buf,sizeof(buf),d,LD_STR_HUMAN); addReplyProto(c,",",1); addReplyProto(c,buf,len); addReplyProto(c,"\r\n",2); diff --git a/src/object.c b/src/object.c index 70022f897..53ad518a9 100644 --- a/src/object.c +++ b/src/object.c @@ -178,7 +178,7 @@ robj *createStringObjectFromLongLongForValue(long long value) { * The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */ robj *createStringObjectFromLongDouble(long double value, int humanfriendly) { char buf[MAX_LONG_DOUBLE_CHARS]; - int len = ld2string(buf,sizeof(buf),value,humanfriendly); + int len = ld2string(buf,sizeof(buf),value,humanfriendly? LD_STR_HUMAN: LD_STR_AUTO); return createStringObject(buf,len); } diff --git a/src/redismodule.h b/src/redismodule.h index ea0d6a139..54d198592 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -457,6 +457,8 @@ void REDISMODULE_API_FUNC(RedisModule_SaveDouble)(RedisModuleIO *io, double valu double REDISMODULE_API_FUNC(RedisModule_LoadDouble)(RedisModuleIO *io); void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value); float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io); +void REDISMODULE_API_FUNC(RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value); +long double REDISMODULE_API_FUNC(RedisModule_LoadLongDouble)(RedisModuleIO *io); void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...); void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...); void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line); @@ -658,6 +660,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(LoadDouble); REDISMODULE_GET_API(SaveFloat); REDISMODULE_GET_API(LoadFloat); + REDISMODULE_GET_API(SaveLongDouble); + REDISMODULE_GET_API(LoadLongDouble); REDISMODULE_GET_API(EmitAOF); REDISMODULE_GET_API(Log); REDISMODULE_GET_API(LogIOError); diff --git a/src/t_hash.c b/src/t_hash.c index e6ed33819..b9f0db7fc 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -621,7 +621,7 @@ void hincrbyfloatCommand(client *c) { } char buf[MAX_LONG_DOUBLE_CHARS]; - int len = ld2string(buf,sizeof(buf),value,1); + int len = ld2string(buf,sizeof(buf),value,LD_STR_HUMAN); new = sdsnewlen(buf,len); hashTypeSet(o,c->argv[2]->ptr,new,HASH_SET_TAKE_VALUE); addReplyBulkCBuffer(c,buf,len); diff --git a/src/util.c b/src/util.c index 783bcf83b..062a572d4 100644 --- a/src/util.c +++ b/src/util.c @@ -510,15 +510,17 @@ int d2string(char *buf, size_t len, double value) { return len; } -/* Convert a long double into a string. If humanfriendly is non-zero - * it does not use exponential format and trims trailing zeroes at the end, - * however this results in loss of precision. Otherwise exp format is used - * and the output of snprintf() is not modified. +/* Create a string object from a long double. + * If mode is humanfriendly it does not use exponential format and trims trailing + * zeroes at the end (may result in loss of precision). + * If mode is default exp format is used and the output of snprintf() + * is not modified (may result in loss of precision). + * If mode is hex hexadecimal format is used (no loss of precision) * * The function returns the length of the string or zero if there was not * enough buffer room to store it. */ -int ld2string(char *buf, size_t len, long double value, int humanfriendly) { - size_t l; +int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) { + size_t l = 0; if (isinf(value)) { /* Libc in odd systems (Hi Solaris!) will format infinite in a @@ -531,26 +533,36 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) { memcpy(buf,"-inf",4); l = 4; } - } else if (humanfriendly) { - /* We use 17 digits precision since with 128 bit floats that precision - * after rounding is able to represent most small decimal numbers in a - * way that is "non surprising" for the user (that is, most small - * decimal numbers will be represented in a way that when converted - * back into a string are exactly the same as what the user typed.) */ - l = snprintf(buf,len,"%.17Lf", value); - if (l+1 > len) return 0; /* No room. */ - /* Now remove trailing zeroes after the '.' */ - if (strchr(buf,'.') != NULL) { - char *p = buf+l-1; - while(*p == '0') { - p--; - l--; - } - if (*p == '.') l--; - } } else { - l = snprintf(buf,len,"%.17Lg", value); - if (l+1 > len) return 0; /* No room. */ + switch (mode) { + case LD_STR_AUTO: + l = snprintf(buf,len,"%.17Lg",value); + if (l+1 > len) return 0; /* No room. */ + break; + case LD_STR_HEX: + l = snprintf(buf,len,"%La",value); + if (l+1 > len) return 0; /* No room. */ + break; + case LD_STR_HUMAN: + /* We use 17 digits precision since with 128 bit floats that precision + * after rounding is able to represent most small decimal numbers in a + * way that is "non surprising" for the user (that is, most small + * decimal numbers will be represented in a way that when converted + * back into a string are exactly the same as what the user typed.) */ + l = snprintf(buf,len,"%.17Lf",value); + if (l+1 > len) return 0; /* No room. */ + /* Now remove trailing zeroes after the '.' */ + if (strchr(buf,'.') != NULL) { + char *p = buf+l-1; + while(*p == '0') { + p--; + l--; + } + if (*p == '.') l--; + } + break; + default: return 0; /* Invalid mode. */ + } } buf[l] = '\0'; return l; diff --git a/src/util.h b/src/util.h index b6c01aa59..a91addb80 100644 --- a/src/util.h +++ b/src/util.h @@ -38,6 +38,13 @@ * This should be the size of the buffer given to ld2string */ #define MAX_LONG_DOUBLE_CHARS 5*1024 +/* long double to string convertion options */ +typedef enum { + LD_STR_AUTO, /* %.17Lg */ + LD_STR_HUMAN, /* %.17Lf + Trimming of trailing zeros */ + LD_STR_HEX /* %La */ +} ld2string_mode; + int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase); int stringmatch(const char *p, const char *s, int nocase); int stringmatchlen_fuzz_test(void); @@ -49,7 +56,7 @@ int string2ll(const char *s, size_t slen, long long *value); int string2l(const char *s, size_t slen, long *value); int string2ld(const char *s, size_t slen, long double *dp); int d2string(char *buf, size_t len, double value); -int ld2string(char *buf, size_t len, long double value, int humanfriendly); +int ld2string(char *buf, size_t len, long double value, ld2string_mode mode); sds getAbsolutePath(char *filename); unsigned long getTimeZone(void); int pathIsBaseName(char *path); diff --git a/tests/modules/testrdb.c b/tests/modules/testrdb.c index eb8d1a999..8a262e8a7 100644 --- a/tests/modules/testrdb.c +++ b/tests/modules/testrdb.c @@ -15,11 +15,16 @@ RedisModuleString *after_str = NULL; void *testrdb_type_load(RedisModuleIO *rdb, int encver) { int count = RedisModule_LoadSigned(rdb); + RedisModuleString *str = RedisModule_LoadString(rdb); + float f = RedisModule_LoadFloat(rdb); + long double ld = RedisModule_LoadLongDouble(rdb); if (RedisModule_IsIOError(rdb)) return NULL; + /* Using the values only after checking for io errors. */ assert(count==1); assert(encver==1); - RedisModuleString *str = RedisModule_LoadString(rdb); + assert(f==1.5f); + assert(ld==0.333333333333333333L); return str; } @@ -27,6 +32,8 @@ void testrdb_type_save(RedisModuleIO *rdb, void *value) { RedisModuleString *str = (RedisModuleString*)value; RedisModule_SaveSigned(rdb, 1); RedisModule_SaveString(rdb, str); + RedisModule_SaveFloat(rdb, 1.5); + RedisModule_SaveLongDouble(rdb, 0.333333333333333333L); } void testrdb_aux_save(RedisModuleIO *rdb, int when) {