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

zadd test_2017 1.1 tom
(integer)
zrevrange test_2017 - withscores
) "tom"
) "1.1000000000000001" zadd test_2017 1.2 sam
(integer)
zrevrange test_2017 - withscores
) "sam"
) "1.2"
) "tom"
) "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 =

因为在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[];
robj *ele;
robj *zobj;
robj *curobj;
double score = , *scores, curscore = 0.0;
int j, elements = (c->argc-)/;
int added = ; if (c->argc % ) {
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 = ; j < elements; j++) {
if (getDoubleFromObjectOrReply(c,c->argv[+j*],&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 = ;
} else {
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
if (o->encoding == REDIS_ENCODING_RAW) {
errno = ;
value = strtod(o->ptr, &eptr);
if (isspace(((char*)o->ptr)[]) || eptr[] != '\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;
}

利用strtod写个小程序

#include <stdio.h>
#include <stdlib.h> int main()
{
char str[] = "1.1 This is test";
char *ptr;
double ret; ret = strtod(str, &ptr);
printf("%.51f",ret); return();
}

结果是 1.100000000000000088817841970012523233890533447265625

double双精度浮点数据的有效位是16位(针对10进制来说),

也就是 printf("%.16f", ret) 那面上面的数据就是 1.1000000000000001, 也就是根据第17位是8,四舍五入

看了下网上关于strtod源码,感觉返回的就是1.1,但赋值给double类型的 ret,才有了上面的值

浮点数和基本类型数据的存储差别比较大,这里不是说存储形式的差别,而是浮点数存放的时候是要经过运算后再转换成整数的4字节或8字节的形式,然后再存放到内存里。因此,只通过16进制数是看不出来和整数有什么差别

在内存中保存小数,使用的是科学计数法

比如 123.456 用十进制科学计数法可以表达为 1.23456 × 102 ,其中 1.23456 为尾数,10 为基数,2 为指数

在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的符号域,指数域和尾数域三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。这样,通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。具体的格式:

符号位  阶码  尾数  长度
float             1          8        23   32
double          1         11   52   64

http://blog.csdn.net/jjj19891128/article/details/22945441

http://www.cnblogs.com/dolphin0520/archive/2011/10/02/2198280.html

那么,我们先来看32位浮点数 的换算:

1. 从浮点数到16进制数

float  var = 5.2f;

就这个浮点数,我们一步一步将它转换为16进制数。

首先,整数部分5,4位二进制表示为:0101。

其次,小数部分0.2,我们应该学了小数转换为二进制的计算方法,那么就是依次乘以2,取整数部分作为二进制数,取小数部分继续乘以2,一直算到小数结果为0为止。那么对0.2进行计算:

0.2*2 = 0.4 * 2 = 0.8 * 2 = 1.6(0.6) * 2 = 1.2(0.2)*2 = 0.4 * 2 = 0.8 * 2 = 1.6(0.6) * 2 = 1.2 ... ...

                0              0            1                     1                  0             0             1                  1   ... ...

因此,这里把0.2的二进制就计算出来了,结果就为:0.00110011... ... 这里的省略号是你没有办法计算完。二进制序列无限循环,没有到达结果为0的那一天。那么此时我们该怎么办?这里就得取到一定的二进制位数后停止计算,然后舍入。我们知道,float是32位,后面尾数的长度只能最大23位。因此,计算结束的时候,整数部分加上小数部分的二进制一共23位二进制。因此5.2的二进制表示就为:

101.00110011001100110011

一共23位。

此时,使用科学计数法表示,结果为:

1.0100110011001100110011 * 22

由于我们规定,使用二进制科学计数法后,小数点左边必须为1(肯定为1嘛,为0的话那不就是0.xxxx*sxxx 了,这样没有什么意义),这里不能为0是有一个很大的好处的,为什么?因为规定为1,这样这个1就不用存储了,我们在从16进制数换算到浮点数的时候加上这个1就是了,因为我们知道这里应该有个1,省略到这个1的目的是为了后面的小数部分能够多表示一位,精度就更高一些了哟。那么省略到小数点前面的1后的结果为:

.01001100110011001100110 * 22

这里后面蓝色的0就是补上的,这里不是随便补的一个0,而是0.2的二进制在这一位上本来就应该为0,如果该为1,我们就得补上一个1.是不是这样多了一位后,实际上我们用23位表示了24位的数据量。有一个位是隐藏了,固定为1的。我们不必记录它。

但是,在对阶或向右规格化时,尾数要向右移位,这样被右移的尾数的低位部分会被丢掉,从而造成一定的误差,因此要进行舍入处理。 常用的舍入方法有两种:一种是“0舍1入”法,即如果右移时被丢掉数位的最高位为0则舍去,为1则将尾数的末位加“1”,另一种是“恒置1”,即只要数位被移掉,就在尾数的末位恒置“1”。

举个例子:

123.456的二进制表示:

123.456的二进制到23位时:111 1011.0111 0100 1011 1100 01...

后面还有依次为01...等低位,由于最高位的1会被隐藏,向后扩展一位如果不做舍入操作则结果为:

1.11 1011 0111 0100 1011 1100 0 * 26

但是经过舍入操作后,由于被舍掉的位的最高位是1,或者“恒置1”法,最后面的0都应该是1。因此最终就应该是:

1.11 1011 0111 0100 1011 1100 1 * 26

在这里需要说明,不管是恒置1,还是0舍1入法,其根本都是为了减小误差。

好了,尾数在这里就计算好了,他就是 01001100110011001100110 

再来看阶数,这里我们知道是2^2次方,那么指数就是2。同样IEEE标准又规定了,因为中间的 阶码在float中是占8位,而这个 阶码又是有符号的(意思就是说,可以有2^-2次方的形式)。

float 类型的 偏置量 Bias = 2k-1 -1 = 28-1 -1 = 127 ,但还要补上刚才因为左移作为小数部分的 2 位(也就是科学技术法的指数),因此偏置量为 127 + 2=129 ,就是 IEEE 浮点数表示标准:

V = (-1)s × M × 2E

E = e - Bias

中的 e ,此前计算 Bias=127 ,刚好验证了 E = 129 - 127 = 2 。

这里的阶码就是12910 ,二进制就是:1000 00012 。

因此,拼接起来后:

1000 0001 01001100110011001100110

| ←   8位 → | | ←------------- 23位 -------------→ |

一共就是31位了,这里还差一位,那就是符号位,我们定义的是5.2,正数。因此这里最高位是0,1表示负数。

而后结果就是:

  0 1000 0001 01001100110011001100110

1位 | ← 8位 → | | ←-------------- 23位 ------------→ |

到这里,我们内存里面的十六进制数产生了,分开来看:

0 100 0000 1 010 0110 0110 0110 0110 0110

    4       0        A        6       6        6        6        6

因此,我们看到的就是0x40A66666, 此就是5.2最终的整数形式。

网上有个例子:这个例子是计算小数点后60位的,因为1.25中的整数1的二进制就是1, 进位位为0,阶码为11位,还剩 64-1-11=52位,所以有效位为52位

<?php
$bin = "";
$int = ;
$base = ;
for ($i = ; $i <= ; $i++) {
$int = $int * ;
if ($int == $base) {
$bin.="";
break;
}
if ($int >$base) {
$bin.="";
$int = $int - $base;
} else {
$bin .= "";
}
} echo $bin;
echo "\n";
echo "现在的长度是".strlen($bin);
echo "\n"; echo"\n"; echo "52位长度的二进制\n";
$bin=substr($bin,,);
echo $bin."\n"; $f = ;
$l = strlen($bin);
for ($i = ; $i < $l; $i++) {
if ($bin[$i] > ) {
$f = $f + pow(, -($i + ));
}
}
echo "反计算后数值\n";
echo number_format($f, );
echo "\n"; echo "1.15本身的30位数据\n";
$f = 1.15;
echo number_format($f, );
echo "\n";

结果

0010011001100110011001100110011001100110011001100110011001100

现在的长度是61

52位长度的二进制

0010011001100110011001100110011001100110011001100110

反计算后数值

1.149999999999999911182158029987

1.15本身的30位数据

1.149999999999999911182158029987

经比较发现,61位的二进制和52的二进制 反向计算后的结果是一样的

redis中存储小数的更多相关文章

  1. 往redis中存储数据是利用pipeline方法

    在redis中保存数据时,保存和设置有效时间是分开写的话,如果中间出现的异常,这会导致数据永久有效,因此就可以采用pipeline方法. # 创建redis管道对象,可以一次执行多个语句 pipeli ...

  2. flask-session 在redis中存储session

    依赖: Flask Flask-Session redis import os from flask import Flask, session, request from flask_session ...

  3. Redis中存储对象区别

    1.最常用的是String结构,key和value都是字符串类型: 2.哈希:比较是用于对对象的操作: 3.List:按照插入数据顺序保存,value是可以重复的,底层是双向链表: 4.集合:是Str ...

  4. Redis中的value包含中文显示的问题?

    linux 系统 redis不识别中文  如何显示中文 在Redis中存储的value值是中文“马拉斯加”Shell下get获取后展示的结果为:\xc2\xed\xc0\xad\xcb\xb9\xbc ...

  5. redis 中文存储乱码问题

    在redis 中存储中文,读取会出现乱码(其实不是乱码,只是不是我们存的中文显示) redis> set test "我们" OK redis> get test &q ...

  6. Redis数据存储结构之String

    前言: 在Redis使用中,我们最常使用的操作是set key value,或 get key value .这里面包含了redis最基本的数据类型:String,字符串类型是redis中最基本的类型 ...

  7. Infinispan 8 中新的 Redis 缓存存储实现

    转载于:http://www.itxuexiwang.com/a/shujukujishu/redis/2016/0216/147.html nfinispan 8 包含了一个新的在 Redis k/ ...

  8. flink04 -----1 kafkaSource 2. kafkaSource的偏移量的存储位置 3 将kafka中的数据写入redis中去 4 将kafka中的数据写入mysql中去

    1. kafkaSource 见官方文档 2. kafkaSource的偏移量的存储位置 默认存在kafka的特殊topic中,但也可以设置参数让其不存在kafka的特殊topic中   3   将k ...

  9. 讨论两种Redis中Token的存储方式

    摘要:本文讨论一个问题:存储token时,token与对应用户id谁来作为key? 问题起源问题起源于要给公司的后台管理系统添加权限管理,选用的是开源框架shiro,而原本系统上是采用token做了登 ...

随机推荐

  1. 606. Construct String from Binary Tree 从二叉树中构建字符串

    [抄题]: You need to construct a string consists of parenthesis and integers from a binary tree with th ...

  2. 系统性能信息模块之psutil模块

    一.psutil模块介绍 官方网址:https://pypi.org/ psutil模块安装:https://github.com/giampaolo/psutil/blob/master/INSTA ...

  3. Red Hat Cluster Suite 组件 fencing FAQ

    说明 Red Hat Cluster实现HA的关键组件之一是fencing.没有设置fencing,虽然看上去也能够运行Cluster,但是一旦遇到故障切换就会出现异 常,所以深入理解fencing原 ...

  4. SQL 数据库 学习 006 如何设置一个用户名和密码

    我的电脑系统: Windows 10 64位 使用的SQL Server软件: SQL Server 2014 Express 先启动 SQL Server 2014 Management Studi ...

  5. SQL Pretty Printer不错的sql格式化工具

    之前使用过sql server 2000的查询设计器和Toad for oracle 都有格式化Sql语句的功能,感觉很方便,尤其对于我这种有着轻微强迫症的人来说.当最近把SQL Server Man ...

  6. Python代码规范利器Flake8

    写代码其实是需要规范的,团队中更是如此:不然 Google 也不会发布各种编码规范,耳熟能详的有Google C++ 风格指南,Google Python 风格指南,等等. 这些规范有用吗?有用也没用 ...

  7. hdu 4068 I-number

    #include<stdio.h> #include<string.h> ]; int al; int mysum() //求各位和 { ; al=strlen(a); ; i ...

  8. 在VS2010中使用Git【图文】(转)

    出处:http://www.cnblogs.com/oec2003/archive/2012/11/13/2768860.html 在之前的一片博客<Windows 下使用Git管理Github ...

  9. javascript总结28 :匿名函数

    1 匿名函数 //匿名函数. // (function (){ // console.log(1); // }) 2  匿名函数作用 //1.直接调用 (function (){ console.lo ...

  10. SQLServer学习-- SQLServer

    SQL Server 是Microsoft 公司推出的关系型数据库管理系统.具有使用方便可伸缩性好与相关软件集成程度高等优点,可跨越从运行Microsoft Windows 98 的膝上型电脑到运行M ...