最近有一个算法,关于求最近一段时间文本相似度的问题,用到了Reids的SortSet,测试后发现SortSet的Score是Double类型,遇到下面的两种情况都有可能存在精度问题。
  1、当整数的时候,整数位数最好不超过16位,如果超过16位,就有可能失真了,超过17位,Redis会选择使用科学计数法。
  2、当带了小数点,我们取整数部分的位数n,当小数点后前n+1位都0的时候, 如 1.001,11.0001 。
  此时如果程序处理不好将会导致严重的错误,并且影响运营时效。后面针对这两种情况进行举例说明。
  至于为什么不选择HSET,那是因为Redis虽然是单线程的,但是在多线程场景下会出现线程穿插的情况。
  也就是说多线程增量HSET域值的时候,会导致增量有误。   即使是用了管道,也有可能出现线程穿插的情况。至于如何测试Reids在多线程的情况下是否存线程穿插。

java测试代码

public static void main(String[] args) {
        Jedis fromJedis = new Jedis("127.0.0.1", 7003);
        Set<Tuple> set= fromJedis.zrangeWithScores("test", 0, -1);
        for(Tuple tuple : set){
            System.out.println(tuple.getElement());
            System.out.println(tuple.getScore());
            System.out.println(new BigDecimal(tuple.getScore()).toPlainString());
        }
}

不超过16位整数

redis db0用满 redis dbsize不准确_16位

17位整数

redis db0用满 redis dbsize不准确_16位_02


  使用文章开头的java测试代码,输出结果如下:

a
1.2345678901234568E16
12345678901234568

18位整数

redis db0用满 redis dbsize不准确_SortSet_03


  使用文章开头的java测试代码,输出结果如下:

a
1.2345678901234568E17
123456789012345680

取整数部分的位数n,当小数点后前n+1位不都为0的时候

redis db0用满 redis dbsize不准确_redis db0用满_04

取整数部分的位数n,当小数点后前n+1位都为0的时候

redis db0用满 redis dbsize不准确_SortSet_05


  使用文章开头的java测试代码,输出结果如下(这个规律希望大家多测测,只是本人猜测,不一定满足):

a
1.001
1.0000009999999999177333620536956004798412322998046875

  规律大概如此,也有可能不尽全对。既然已经知道规律,至于如何解决,我自己心里有数了,相信大家也知道解决方案了。
  再说一个小规律,小数点前后位数和是17位的时候,不会出现科学计数。或许大家能用上,反正我刚好用上了。
  用到Dobule的时候,还是要迫切注意精度问题和科学计数法。

java中Double分析

遍历输出1-27位长度的整数转Double

public static void main(String[] args) {
        String num[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9"};
        String str = "";
        for(int i = 0; i < 3; i++){
            for(String s : num){
                str += s;
                System.out.println("整数:" + str + "  , double:  " + Double.parseDouble(str));

            }
        }
}

输出结果如下:

redis db0用满 redis dbsize不准确_redis db0用满_06

System.out.println(String.format("%.0f", Double.parseDouble("123456789123456789")));
System.out.println(new BigDecimal(Double.parseDouble("123456789123456789")).toPlainString());

System.out.println(String.format("%.0f", Double.parseDouble("1234567891234567891")));
System.out.println(new BigDecimal(Double.parseDouble("1234567891234567891")).toPlainString());

System.out.println(String.format("%.0f", Double.parseDouble("12345678912345678912")));
System.out.println(new BigDecimal(Double.parseDouble("12345678912345678912")).toPlainString());

输出结果:

redis db0用满 redis dbsize不准确_16位_07


  不管使用String.format还是使用BigDecimal转换Double,当double的位数大于17位的时候都会导致精度的损失。

  java double类型小数点前整数大于7位,会使用科学技数。

  java double类型小数点前后位数和大于15位的时候,也会使用科学计数。

  java double类型但只要小于18位便不会出现精度损失问题。

  出现科学计数的时候,可以是用BigDecimal.valueOf()方法转成普通的表示方法,但是该方法在double位数大于17位的时候,同样会损失精度。请看下面的例子;

System.out.println(BigDecimal.valueOf(Double.valueOf("123456789.20160823")).toString());
        System.out.println(BigDecimal.valueOf(Double.valueOf("1234567891.20160823")).toString());

输出结果:

123456789.20160823
1234567891.2016082

第二条打印结果,小数点最后一位被舍弃了。