This is an automated email from the ASF dual-hosted git repository.

jihuayu pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git


The following commit(s) were added to refs/heads/unstable by this push:
     new 4aede367b fix(string,hash): use compact float format in IncrByFloat to 
match Redis (#3427)
4aede367b is described below

commit 4aede367b72416a591623c2f04f176ab3e74c40a
Author: Songqing Zhang <[email protected]>
AuthorDate: Wed Apr 8 20:39:43 2026 +0800

    fix(string,hash): use compact float format in IncrByFloat to match Redis 
(#3427)
    
    Previously, String::IncrByFloat and Hash::IncrByFloat used
    std::to_string(double) to persist float values, which produces C-style
    %f format with 6 fixed decimal places (e.g., "10.500000"). Redis uses a
        compact %g-like format that strips trailing zeros (e.g., "10.5").
    
        This caused GET/HGET after INCRBYFLOAT/HINCRBYFLOAT to return values
    like "10.500000" instead of "10.5", breaking Redis protocol
    compatibility.
    
    Replace std::to_string with util::Float2String (which uses fmt {:.17g})
        in both String::IncrByFloat and Hash::IncrByFloat. The INCRBYFLOAT
    command reply already used Float2String correctly; this fix aligns the
        stored representation with both the reply format and Redis behavior.
    
    Co-authored-by: 纪华裕 <[email protected]>
---
 src/types/redis_hash.cc            |  3 ++-
 src/types/redis_string.cc          |  2 +-
 tests/cppunit/types/hash_test.cc   | 24 ++++++++++++++++++++++++
 tests/cppunit/types/string_test.cc | 33 +++++++++++++++++++++++++++++++++
 4 files changed, 60 insertions(+), 2 deletions(-)

diff --git a/src/types/redis_hash.cc b/src/types/redis_hash.cc
index cbb5fa0ef..26cdc43ab 100644
--- a/src/types/redis_hash.cc
+++ b/src/types/redis_hash.cc
@@ -28,6 +28,7 @@
 #include <random>
 #include <utility>
 
+#include "common/string_util.h"
 #include "db_util.h"
 #include "parse_util.h"
 #include "sample_helper.h"
@@ -144,7 +145,7 @@ rocksdb::Status Hash::IncrByFloat(engine::Context &ctx, 
const Slice &user_key, c
   WriteBatchLogData log_data(kRedisHash);
   s = batch->PutLogData(log_data.Encode());
   if (!s.ok()) return s;
-  s = batch->Put(sub_key, std::to_string(*new_value));
+  s = batch->Put(sub_key, util::Float2String(*new_value));
   if (!s.ok()) return s;
   if (!exists) {
     metadata.size += 1;
diff --git a/src/types/redis_string.cc b/src/types/redis_string.cc
index e2d431303..e056d3413 100644
--- a/src/types/redis_string.cc
+++ b/src/types/redis_string.cc
@@ -430,7 +430,7 @@ rocksdb::Status String::IncrByFloat(engine::Context &ctx, 
const std::string &use
   *new_value = n;
 
   raw_value = raw_value.substr(0, offset);
-  raw_value.append(std::to_string(n));
+  raw_value.append(util::Float2String(n));
   return updateRawValue(ctx, ns_key, raw_value);
 }
 
diff --git a/tests/cppunit/types/hash_test.cc b/tests/cppunit/types/hash_test.cc
index d6fa542f1..f4cca4c34 100644
--- a/tests/cppunit/types/hash_test.cc
+++ b/tests/cppunit/types/hash_test.cc
@@ -229,6 +229,30 @@ TEST_F(RedisHashTest, HIncrByFloat) {
   auto s = hash_->Del(*ctx_, key_);
 }
 
+TEST_F(RedisHashTest, HIncrByFloatStoredFormat) {
+  double value = 0.0;
+  Slice field("hash-incrbyfloat-format-field");
+
+  // Stored value should use compact format without trailing zeros
+  auto s = hash_->IncrByFloat(*ctx_, key_, field, 10.5, &value);
+  EXPECT_TRUE(s.ok());
+  EXPECT_DOUBLE_EQ(10.5, value);
+  std::string bytes;
+  s = hash_->Get(*ctx_, key_, field, &bytes);
+  EXPECT_TRUE(s.ok());
+  EXPECT_EQ("10.5", bytes);
+
+  // Subsequent IncrByFloat should parse the stored compact format correctly
+  s = hash_->IncrByFloat(*ctx_, key_, field, 1.5, &value);
+  EXPECT_TRUE(s.ok());
+  EXPECT_DOUBLE_EQ(12.0, value);
+  s = hash_->Get(*ctx_, key_, field, &bytes);
+  EXPECT_TRUE(s.ok());
+  EXPECT_EQ("12", bytes);
+
+  s = hash_->Del(*ctx_, key_);
+}
+
 TEST_F(RedisHashTest, HRangeByLex) {
   uint64_t ret = 0;
   std::vector<FieldValue> fvs;
diff --git a/tests/cppunit/types/string_test.cc 
b/tests/cppunit/types/string_test.cc
index f30ef1622..d7f79eab0 100644
--- a/tests/cppunit/types/string_test.cc
+++ b/tests/cppunit/types/string_test.cc
@@ -108,6 +108,39 @@ TEST_F(RedisStringTest, IncrByFloat) {
   s = string_->Del(*ctx_, key_);
 }
 
+TEST_F(RedisStringTest, IncrByFloatStoredFormat) {
+  double f = 0.0;
+  std::string value;
+
+  // Stored value should use compact format without trailing zeros
+  auto s = string_->IncrByFloat(*ctx_, key_, 10.5, &f);
+  EXPECT_TRUE(s.ok());
+  EXPECT_DOUBLE_EQ(10.5, f);
+  s = string_->Get(*ctx_, key_, &value);
+  EXPECT_TRUE(s.ok());
+  EXPECT_EQ("10.5", value);
+
+  s = string_->Del(*ctx_, key_);
+  EXPECT_TRUE(s.ok());
+
+  // Integer-valued floats should not have decimal point
+  s = string_->IncrByFloat(*ctx_, key_, 3.0, &f);
+  EXPECT_TRUE(s.ok());
+  s = string_->Get(*ctx_, key_, &value);
+  EXPECT_TRUE(s.ok());
+  EXPECT_EQ("3", value);
+
+  // Subsequent IncrByFloat should parse the stored compact format correctly
+  s = string_->IncrByFloat(*ctx_, key_, 1.5, &f);
+  EXPECT_TRUE(s.ok());
+  EXPECT_DOUBLE_EQ(4.5, f);
+  s = string_->Get(*ctx_, key_, &value);
+  EXPECT_TRUE(s.ok());
+  EXPECT_EQ("4.5", value);
+
+  s = string_->Del(*ctx_, key_);
+}
+
 TEST_F(RedisStringTest, IncrBy) {
   int64_t ret = 0;
   string_->IncrBy(*ctx_, key_, 1, &ret);

Reply via email to