INCRBYFLOAT mykey 0.1INCRBYFLOAT mykey 1.111INCRBYFLOAT mykey 1.111111
利用 lua 脚本的办法,由于 redis 可以通过 lua 脚本来担保操作的原子性,以是当我们同时操作多个 key 的时候一样平常利用 lua 脚本的办法。
eval \公众return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])\公众 1 mykey1 \"大众1.11\公众 eval \"大众return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])\"大众 1 mykey1 \"大众1.11111\公众 eval \"大众return redis.call('INCRBYFLOAT', KEYS[1], ARGV[1])\公众 1 mykey1 \"大众1.11111\"大众
INCRBYFLOAT 可表示范围
按照官方文档的说法 INCRBYFLOAT 可以表示小数位 17 位。比如按照 jedis 的 api 来说,我们能够利用的便是在 double 的精度范围内,也便是 15-16位。这里我也看了 redis 的源码,他在底层实现是通过 c 措辞的 long double 类型来进行打算的。
void incrbyfloatCommand(client c) { long double incr, value; robj o, new; o = lookupKeyWrite(c->db,c->argv[1]); if (checkType(c,o,OBJ_STRING)) return; if (getLongDoubleFromObjectOrReply(c,o,&value,) != C_OK || getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,) != C_OK) return; value += incr; if (isnan(value) || isinf(value)) { addReplyError(c,\公众increment would produce NaN or Infinity\"大众); return; } new = createStringObjectFromLongDouble(value,1); if (o) dbReplaceValue(c->db,c->argv[1],new); else dbAdd(c->db,c->argv[1],new); signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_STRING,\"大众incrbyfloat\"大众,c->argv[1],c->db->id); server.dirty++; addReplyBulk(c,new); / Always replicate INCRBYFLOAT as a SET command with the final value in order to make sure that differences in float precision or formatting will not create differences in replicas or after an AOF restart. / rewriteClientCommandArgument(c,0,shared.set); rewriteClientCommandArgument(c,2,new); rewriteClientCommandArgument(c,3,shared.keepttl);}
源码地址:https://github.com/redis/redis/blob/unstable/src/t_string.c long double 是 c 措辞的长双精度浮点型,在 x86 的 64 位操作系统上占常日占用 16 字节(128 位),相较于 8 字节的 double 类型具有更大的范围和更高的精度。(这部分来源于 chatgpt) 由于 redis 采取的 long double 类型来做浮点数打算, 以是 redis 就可以担保到小数点后 17 位的精度。 整数位也可以表示 17 位 redis 的浮点数打算常日情形下会丢失精度吗? 常日情形下是不会的,但是不能担保一定不会。

测试代码如下:
public class RedisIncrByFloatTest {public static void main(String[] args) { Jedis jedis = new Jedis(\"大众127.0.0.1\"大众, 6379); BigDecimal decimalIncr = java.math.BigDecimal.ZERO; String key = \公众IncrFloat:Digit100\公众;//测试精度 test_accuracy(jedis, decimalIncr, key);//测试正浮点数最大值 test_max_positive_float(jedis, decimalIncr, key); jedis.disconnect(); jedis.close(); }private static void test_max_positive_float(Jedis jedis, BigDecimal decimalIncr, String key) { jedis.del(key); String value = \公众99999999999999999.00000000000000003\"大众; List<String> evalKeys = Collections.singletonList(key); List<String> evalArgs = Collections.singletonList(value); String luaStr = \公众redis.call('INCRBYFLOAT', KEYS[1], ARGV[1]) return redis.call('GET', KEYS[1])\公众; Object result = jedis.eval(luaStr, evalKeys, evalArgs); decimalIncr = decimalIncr.add(new BigDecimal(value)); BigDecimal redisIncr = new BigDecimal(String.valueOf(result)); value = \公众0.99999999999999996\公众; evalKeys = Collections.singletonList(key); evalArgs = Collections.singletonList(value); luaStr = \"大众redis.call('INCRBYFLOAT', KEYS[1], ARGV[1]) return redis.call('GET', KEYS[1])\公众; result = jedis.eval(luaStr, evalKeys, evalArgs); decimalIncr = decimalIncr.add(new BigDecimal(value)); redisIncr = new BigDecimal(String.valueOf(result));boolean eq = comparteNumber(redisIncr, decimalIncr);if (eq) { System.out.println(\公众累计结果精确, 整数位: \"大众 + 17 + \"大众位, 结果期望值: decimalIncr \公众 + decimalIncr.toPlainString() + \"大众, 目标值(redis):\"大众 + redisIncr.toPlainString()); } else { System.out.println(\公众累计结果禁绝确, 整数位: \"大众 + 17 + \"大众位, 期望值: decimalIncr \"大众 + decimalIncr.toPlainString() + \公众, 目标值(redis):\"大众 + redisIncr.toPlainString()); } }private static void test_accuracy(Jedis jedis, BigDecimal decimalIncr, String key) { jedis.del(key);for (int i = 16; i < 30; i++) { String value = createValue(i);final List<String> evalKeys = Collections.singletonList(key);final List<String> evalArgs = Collections.singletonList(value); String luaStr = \公众redis.call('INCRBYFLOAT', KEYS[1], ARGV[1]) return redis.call('GET', KEYS[1])\"大众; Object result = jedis.eval(luaStr, evalKeys, evalArgs); decimalIncr = decimalIncr.add(new BigDecimal(value)); BigDecimal redisIncr = new BigDecimal(String.valueOf(result));boolean eq = comparteNumber(redisIncr, decimalIncr);if (eq) { System.out.println(\"大众累计结果精确, 整数位: \"大众 + i + \公众位, 结果期望值: decimalIncr \公众 + decimalIncr.toPlainString() + \"大众, 目标值(redis):\公众 + redisIncr.toPlainString()); } else { System.out.println(\"大众累计结果禁绝确, 整数位: \"大众 + i + \公众位, 期望值: decimalIncr \"大众 + decimalIncr.toPlainString() + \公众, 目标值(redis):\公众 + redisIncr.toPlainString());break; } } }private static String createValue(int i) { String result = \"大众9\"大众 + \"大众0\公众.repeat(Math.max(0, i - 1));return result + \"大众.00000000000000003\"大众; }private static boolean comparteNumber(BigDecimal redisIncr, BigDecimal decimalIncr) {return decimalIncr.compareTo(redisIncr) == 0; }}
输出结果:
累计结果精确, 整数位: 16位, 结果期望值: decimalIncr 9000000000000000.00000000000000003, 目标值(redis):9000000000000000.00000000000000003累计结果精确, 整数位: 17位, 结果期望值: decimalIncr 99000000000000000.00000000000000006, 目标值(redis):99000000000000000.00000000000000006累计结果禁绝确, 整数位: 18位, 期望值: decimalIncr 999000000000000000.00000000000000009, 目标值(redis):999000000000000000累计结果精确, 整数位: 17位, 结果期望值: decimalIncr 99999999999999999.99999999999999999, 目标值(redis):99999999999999999.99999999999999999
INCRBYFLOAT 导致精度丢失
INCRBYFLOAT 导致精度丢失有两种情形:
INCRBYFLOAT 底层打算是通过long double 来打算的在 C措辞中 long double占用128 位,其范围为: 最小值: ±5.4×10^-4951 最大值: ±1.1×10^4932 能表示的有效数字在34~35位之间。
StringRedisTemplate template = new StringRedisTemplate(); template.opsForValue().increment(\公众v1\公众, 1.3D);
在 RedisTemplate 的这个 increment 接管的参数类型便是一个 double 以是会发生精度问题
C 措辞长双精度类型由于 redis 底层采取的是long double 打算,以是这个问题转化为长双精度(long double)为什么没有精度问题? 这是由于 long double 具有更大的范围和更高的精度。long double 的范围和精度高于 double 类型:
详细来说,几点缘故原由造成 long double 没有精度问题:
综上,long double 的更广范围和更高精度,让它在相同的浮点打算中具有更少的舍入偏差。这也就阐明了为什么 long double 没有明显的精度问题,由于它天生便是为了供应更高精度而设计的。比较之下,double 利用的位数相对有限,纵然采取折中舍入法,在一些场景下它的偏差也可能累加显著。以是总的来说,long double 之以是没有精度问题,紧张还是源于其更大的范围和更高的内在精度。
问题总结末了,求关注。如果你还想看更多优质原创文章,欢迎关注我们的公众号「运维开拓故事」。
如果我的文章对你有所帮助,还请帮忙一下,你的支持会勉励我输出更高质量的文章,非常感谢!
你还可以把我的"大众年夜众号设为「星标」,这样当公众年夜众号文章更新时,你会在第一韶光收到推送,避免错过我的文章更新。