成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Redis 浮點數累計實現

數據庫 Redis
按照官方文檔的說法 INCRBYFLOAT 可以表示小數位 17 位。比如按照 jedis 的 api 來說,我們能夠使用的就是在 double 的精度范圍內,也就是 15-16位。這里我也看了 redis 的源碼,他在底層實現是通過 c 語言的 long double 類型來進行計算的。

Redis 浮點數累計主要是有兩個命令

  • INCRBYFLOAT 是 SET 指令的浮點數累計
  • HINCRBYFLOAT 是 HASH 類型的浮點數累計

在內部 HINCRBYFLOAT 和 INCRBYFLOAT 自增實現相同。所以我們分析 INCRBYFLOAT 即可。

基本使用

直接使用指令。

INCRBYFLOAT mykey 0.1
INCRBYFLOAT mykey 1.111
INCRBYFLOAT 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,NULL) != C_OK ||
        getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != 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 導致精度丟失有兩種情況:

  1. 累計的范圍值超過 INCRBYFLOAT 所能表示的最大精度范圍,在 double 范圍內。

INCRBYFLOAT 底層計算是通過long double 來計算的在 C語言中 long double占用128 位,其范圍為: 最小值: ±5.4×10^-4951 最大值: ±1.1×10^4932 能表示的有效數字在34~35位之間。

  1. 我們使用類似 jedis 的 api 提供的是 double 類型的參數,可能在調用之前,參數轉換的過程就發生了精度問題。比如
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 可以表示的有效數字多于 double 類型這意味著,對于同樣的浮點計算,long double 具有更少的舍入誤差。

具體來說,幾點原因造成 long double 沒有精度問題:

  1. long double 使用更多的bit位來表示浮點數。
  2. long double 使用四舍五入(rounding to nearest)而不是銀行家舍入(bankers' rounding),導致更少的誤差累加。
  3. 許多編譯器及 CPU 針對 long double 具有優化, 會生成精度更高的機器碼來執行 long double 計算。
  4. long double 內部采用更大的指數域, 能更準確地表示相同范圍內的數字。

綜上,long double 的更廣范圍和更高精度,讓它在相同的浮點計算中具有更少的舍入誤差。這也就解釋了為什么 long double 沒有明顯的精度問題,因為它天生就是為了提供更高精度而設計的。相比之下,double 使用的位數相對有限,即使采用折中舍入法,在一些場景下它的誤差也可能累加顯著。所以總的來說,long double 之所以沒有精度問題,主要還是源于其更大的范圍和更高的內在精度

問題總結

  1. Redis 浮點數累計操作 INCRBYFLOAT 不適合精度要求比較高的金額計算。
  2. Redis 浮點數累計操作 INCRBYFLOAT 也不能平替 BigDecimal 計算,如果一定需要存儲可以考慮通過 lua 腳本實現 CAS 進行修改,最終存儲為 String 類型的一個結果。
  3. Redis 的浮點數雖然做了比較好的優化,但是沒有從根本解決計算精度問題。

參考文檔

  • https://redis.io/commands/incrbyfloat/。
  • https://wiki.c2.com/?BankersRounding。
  • https://www.wikihow.com/Round-to-the-Nearest-Tenth。
  • https://learn.microsoft.com/zh-cn/cpp/c-language/type-long-double?view=msvc-170。
  • https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/strtold-strtold-l-wcstold-wcstold-l?view=msvc-170。
責任編輯:姜華 來源: 運維開發故事
相關推薦

2020-09-15 12:57:46

C 語言浮點數內存

2017-10-16 10:42:27

前端JavaScript浮點數

2024-05-31 08:38:35

Python浮點數屬性

2015-12-02 10:21:34

JavaScript浮點數精度調整

2018-08-24 10:16:23

內存浮點數存儲

2020-10-12 06:38:08

存儲定點數

2021-10-19 14:04:28

C++類型數字

2011-05-25 14:10:38

浮點數

2009-05-19 10:10:01

Oracle時間加減時間操作

2010-07-22 17:39:44

2010-01-15 15:21:35

C++

2021-11-15 09:32:06

浮點面試Java

2022-06-15 15:44:21

無損數據壓縮鴻蒙

2024-07-11 15:50:36

2025-04-01 07:50:00

Dinero.js前端開發

2024-08-23 08:43:08

2025-01-17 09:20:00

2025-03-03 04:20:00

2023-11-08 13:32:00

JavaScript浮點數計算

2025-03-14 10:34:22

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品在线视频 | 在线播放国产一区二区三区 | 日韩福利在线观看 | 一级免费看片 | 欧美一区二区成人 | 欧美一级免费看 | 国产成人免费网站 | 中文字幕的av | 精品三级 | 免费久久精品视频 | 成人午夜免费网站 | 中文字幕第二区 | 国产一区二区精品在线观看 | 美女一区二区在线观看 | 久久亚洲经典 | 精品久久久久久久久久久久 | 成人在线观看免费视频 | 日韩在线视频一区 | 久久精品中文字幕 | 国产成人啪免费观看软件 | 人人干在线 | 天天干免费视频 | 国产三级一区二区 | 亚洲一区自拍 | 成人av免费 | 狠狠爱免费视频 | 午夜精品久久久久久久久久久久 | 欧美一区二区三区在线免费观看 | 国产精品久久久久久久久久 | av一区二区三区四区 | 黄网站免费在线看 | 黄色欧美大片 | 国产一区二区三区在线 | 日韩av一区二区在线 | 欧美嘿咻 | 911精品美国片911久久久 | 91福利在线导航 | 拍戏被cao翻了h承欢 | 欧美在线a | 国产综合网站 | 国产95在线 |