diff --git a/src/object.c b/src/object.c index fd7786ed6e463a8ec6b1e225857b166a4f307244..d74fc96507f1f8e03dd89b17d70e9fc14857c21d 100644 --- a/src/object.c +++ b/src/object.c @@ -44,6 +44,21 @@ robj *createStringObjectFromLongLong(long long value) { return o; } +/* Note: this function is defined into object.c since here it is where it + * belongs but it is actually designed to be used just for INCRBYFLOAT */ +robj *createStringObjectFromLongDouble(long double value) { + char buf[256]; + int len; + + /* We use 17 digits precision since with 128 bit floats that precision + * after rouding 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.) */ + len = snprintf(buf,sizeof(buf),"%.17Lg", value); + return createStringObject(buf,len); +} + robj *dupStringObject(robj *o) { redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW); return createStringObject(o->ptr,sdslen(o->ptr)); @@ -350,8 +365,10 @@ int getDoubleFromObject(robj *o, double *target) { } else { redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); if (o->encoding == REDIS_ENCODING_RAW) { + errno = 0; value = strtod(o->ptr, &eptr); - if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR; + if (eptr[0] != '\0' || errno == ERANGE || isnan(value)) + return REDIS_ERR; } else if (o->encoding == REDIS_ENCODING_INT) { value = (long)o->ptr; } else { @@ -369,7 +386,45 @@ int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const ch if (msg != NULL) { addReplyError(c,(char*)msg); } else { - addReplyError(c,"value is not a double"); + addReplyError(c,"value is not a valid float"); + } + return REDIS_ERR; + } + + *target = value; + return REDIS_OK; +} + +int getLongDoubleFromObject(robj *o, long double *target) { + long double value; + char *eptr; + + if (o == NULL) { + value = 0; + } else { + redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); + if (o->encoding == REDIS_ENCODING_RAW) { + errno = 0; + value = strtold(o->ptr, &eptr); + if (eptr[0] != '\0' || errno == ERANGE || isnan(value)) + return REDIS_ERR; + } else if (o->encoding == REDIS_ENCODING_INT) { + value = (long)o->ptr; + } else { + redisPanic("Unknown string encoding"); + } + } + *target = value; + return REDIS_OK; +} + +int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg) { + long double value; + if (getLongDoubleFromObject(o, &value) != REDIS_OK) { + if (msg != NULL) { + addReplyError(c,(char*)msg); + } else { + addReplyError(c,"value is not a valid float"); } return REDIS_ERR; } diff --git a/src/redis.c b/src/redis.c index 7d2094390c8a37363faf845200836c32d20cca5a..527b053852e1b14401837c4e645707985ebe5bc1 100644 --- a/src/redis.c +++ b/src/redis.c @@ -165,6 +165,7 @@ struct redisCommand redisCommandTable[] = { {"hexists",hexistsCommand,3,"r",0,NULL,1,1,1,0,0}, {"incrby",incrbyCommand,3,"wm",0,NULL,1,1,1,0,0}, {"decrby",decrbyCommand,3,"wm",0,NULL,1,1,1,0,0}, + {"incrbyfloat",incrbyfloatCommand,3,"wm",0,NULL,1,1,1,0,0}, {"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0}, {"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0}, {"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0}, diff --git a/src/redis.h b/src/redis.h index d660cae91b3b17f6257321814cd6021d0e76da60..0024792cc11850e722fa3820ef4b48aa5d47bfa9 100644 --- a/src/redis.h +++ b/src/redis.h @@ -811,6 +811,7 @@ robj *tryObjectEncoding(robj *o); robj *getDecodedObject(robj *o); size_t stringObjectLen(robj *o); robj *createStringObjectFromLongLong(long long value); +robj *createStringObjectFromLongDouble(long double value); robj *createListObject(void); robj *createZiplistObject(void); robj *createSetObject(void); @@ -823,6 +824,8 @@ int checkType(redisClient *c, robj *o, int type); int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg); int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg); int getLongLongFromObject(robj *o, long long *target); +int getLongDoubleFromObject(robj *o, long double *target); +int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg); char *strEncoding(int encoding); int compareStringObjects(robj *a, robj *b); int equalStringObjects(robj *a, robj *b); @@ -1004,6 +1007,7 @@ void incrCommand(redisClient *c); void decrCommand(redisClient *c); void incrbyCommand(redisClient *c); void decrbyCommand(redisClient *c); +void incrbyfloatCommand(redisClient *c); void selectCommand(redisClient *c); void randomkeyCommand(redisClient *c); void keysCommand(redisClient *c); diff --git a/src/t_string.c b/src/t_string.c index ce6233a98d3e8df653bc990f098eea0572d7f489..1f0b1fbcc44b49c60a4065c52f40bd53496e01d0 100644 --- a/src/t_string.c +++ b/src/t_string.c @@ -1,4 +1,5 @@ #include "redis.h" +#include /* isnan(), isinf() */ /*----------------------------------------------------------------------------- * String Commands @@ -382,6 +383,31 @@ void decrbyCommand(redisClient *c) { incrDecrCommand(c,-incr); } +void incrbyfloatCommand(redisClient *c) { + long double incr, value; + robj *o, *new; + + o = lookupKeyWrite(c->db,c->argv[1]); + if (o != NULL && checkType(c,o,REDIS_STRING)) return; + if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK || + getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK) + return; + + value += incr; + if (isnan(value) || isinf(value)) { + addReplyError(c,"increment would produce NaN or Infinity"); + return; + } + new = createStringObjectFromLongDouble(value); + if (o) + dbOverwrite(c->db,c->argv[1],new); + else + dbAdd(c->db,c->argv[1],new); + signalModifiedKey(c->db,c->argv[1]); + server.dirty++; + addReplyBulk(c,new); +} + void appendCommand(redisClient *c) { size_t totlen; robj *o, *append;