redis中存储小数

 在做一个活动的需求时,需要往redis中有序的集合中存储一个小数,结果发现取出数据和存储时的数据不一致

zadd test_2017 1.1 tom
(integer) 1
zrevrange test_2017 0 -1 withscores
1) "tom"
2) "1.1000000000000001"


zadd test_2017 1.2 sam
(integer) 1
zrevrange test_2017 0 -1 withscores
1) "sam"
2) "1.2"
3) "tom"
4) "1.1000000000000001"

  是不是很奇怪, 存储tom的score 为1.1,结果为 1.1000000000000001,存储 sam的score为1.2,结果就是1.2

1.19999999999999995559 第16位是9,第17位是5, 四舍五入1.2

1.10000000000000008882 第16位是0,  第17位是8,四舍五入1.10000000000000001

但在php中

<?php
 echo 1.1;

确实显示1.1,是不是有点奇怪,后来在php.ini中找到到precision这个选项, 指

; The number of significant digits displayed in floating point numbers.
; http://php.net/precision
precision = 14

因为在php中,小数都是以double形式存储的,那么1.10000000000000008882中第14位为0,第15位也为0,四舍五入,为1.1

 解决方法:php在处理时使用bcmul函数,将小数*100,再存入redis,待取出来时,再除以100

看了下redis  zadd的源码,发现 zadd key score name 这个命令,redis利用strtod这个函数 将score 转为double浮点数

/* This generic command implements both ZADD and ZINCRBY. */
void zaddGenericCommand(redisClient *c, int incr) {
    static char *nanerr = "resulting score is not a number (NaN)";
    robj *key = c->argv[1];
    robj *ele;
    robj *zobj;
    robj *curobj;
    double score = 0, *scores, curscore = 0.0;
    int j, elements = (c->argc-2)/2;
    int added = 0;

    if (c->argc % 2) {
        addReply(c,shared.syntaxerr);
        return;
    }

    /* Start parsing all the scores, we need to emit any syntax error
     * before executing additions to the sorted set, as the command should
     * either execute fully or nothing at all. */
    scores = zmalloc(sizeof(double)*elements);
    for (j = 0; j < elements; j++) {
        if (getDoubleFromObjectOrReply(c,c->argv[2+j*2],&scores[j],NULL)
            != REDIS_OK)
        {
            zfree(scores);
            return;
        }
    }

。。。。
}
int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg) {
    double value;
    if (getDoubleFromObject(o, &value) != REDIS_OK) {
        if (msg != NULL) {
            addReplyError(c,(char*)msg);
        } else {
            addReplyError(c,"value is not a valid float");
        }
        return REDIS_ERR;
    }
    *target = value;
    return REDIS_OK;
}

int getDoubleFromObject(robj *o, double *target) {
    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 = strtod(o->ptr, &eptr);
            if (isspace(((char*)o->ptr)[0]) || eptr[0] != '