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);