summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOran Agra <oran@redislabs.com>2019-11-03 16:42:31 +0200
committerOran Agra <oran@redislabs.com>2019-11-03 16:42:31 +0200
commit779aebc91cd8d3043ab172e0bc5b8c988df88e33 (patch)
tree803c68ed0f77f8b9abd01e200022a310876d9712
parentfdaea2a7a7eed1499f46bb98552f8d8bb8dc7e9d (diff)
downloadredis-779aebc91cd8d3043ab172e0bc5b8c988df88e33.tar.gz
Module API for loading and saving long double
looks like each platform implements long double differently (different bit count) so we can't save them as binary, and we also want to avoid creating a new RDB format version, so we save these are hex strings using "%La". This commit includes a change in the arguments of ld2string to support this. as well as tests for coverage and short reads. coded by @guybe7
-rw-r--r--src/module.c27
-rw-r--r--src/networking.c2
-rw-r--r--src/object.c2
-rw-r--r--src/redismodule.h4
-rw-r--r--src/t_hash.c2
-rw-r--r--src/util.c60
-rw-r--r--src/util.h9
-rw-r--r--tests/modules/testrdb.c9
8 files changed, 86 insertions, 29 deletions
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--;
+ } else {
+ 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--;
}
- if (*p == '.') l--;
+ break;
+ default: return 0; /* Invalid mode. */
}
- } else {
- l = snprintf(buf,len,"%.17Lg", value);
- if (l+1 > len) return 0; /* No room. */
}
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) {