Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ParseFloat and replace stod with it #1227

Merged
merged 6 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 56 additions & 60 deletions src/commands/redis_cmd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,23 @@

namespace Redis {

const char *kCursorPrefix = "_";

const char *errInvalidSyntax = "syntax error";
const char *errInvalidExpireTime = "invalid expire time";
const char *errWrongNumOfArguments = "wrong number of arguments";
const char *errValueNotInteger = "value is not an integer or out of range";
const char *errAdministorPermissionRequired = "administor permission required to perform the command";
const char *errValueMustBePositive = "value is out of range, must be positive";
const char *errNoSuchKey = "no such key";
const char *errUnbalancedStreamList =
constexpr const char *kCursorPrefix = "_";

constexpr const char *errInvalidSyntax = "syntax error";
constexpr const char *errInvalidExpireTime = "invalid expire time";
constexpr const char *errWrongNumOfArguments = "wrong number of arguments";
constexpr const char *errValueNotInteger = "value is not an integer or out of range";
constexpr const char *errAdministorPermissionRequired = "administor permission required to perform the command";
constexpr const char *errValueMustBePositive = "value is out of range, must be positive";
constexpr const char *errNoSuchKey = "no such key";
constexpr const char *errUnbalancedStreamList =
"Unbalanced XREAD list of streams: for each stream key an ID or '$' must be specified.";
const char *errTimeoutIsNegative = "timeout is negative";
const char *errLimitOptionNotAllowed = "syntax error, LIMIT cannot be used without the special ~ option";
const char *errZSetLTGTNX = "GT, LT, and/or NX options at the same time are not compatible";
const char *errScoreIsNotValidFloat = "score is not a valid float";
const char *errValueIsNotFloat = "value is not a valid float";
const char *errNoMatchingScript = "NOSCRIPT No matching script. Please use EVAL";
constexpr const char *errTimeoutIsNegative = "timeout is negative";
constexpr const char *errLimitOptionNotAllowed = "syntax error, LIMIT cannot be used without the special ~ option";
constexpr const char *errZSetLTGTNX = "GT, LT, and/or NX options at the same time are not compatible";
constexpr const char *errScoreIsNotValidFloat = "score is not a valid float";
constexpr const char *errValueIsNotFloat = "value is not a valid float";
constexpr const char *errNoMatchingScript = "NOSCRIPT No matching script. Please use EVAL";

enum class AuthResult {
OK,
Expand Down Expand Up @@ -812,11 +812,11 @@ class CommandIncrBy : public Commander {
class CommandIncrByFloat : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
try {
increment_ = std::stod(args[2]);
} catch (std::exception &e) {
return {Status::RedisParseErr, errValueNotInteger};
auto increment = ParseFloat(args[2]);
if (!increment) {
return {Status::RedisParseErr, errValueIsNotFloat};
}
increment_ = *increment;

return Commander::Parse(args);
}
Expand Down Expand Up @@ -1469,11 +1469,11 @@ class CommandHIncrBy : public Commander {
class CommandHIncrByFloat : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
try {
increment_ = std::stod(args[3]);
} catch (std::exception &e) {
return {Status::RedisParseErr, errValueNotInteger};
auto increment = ParseFloat(args[3]);
if (!increment) {
return {Status::RedisParseErr, errValueIsNotFloat};
}
increment_ = *increment;
return Commander::Parse(args);
}

Expand Down Expand Up @@ -2525,17 +2525,16 @@ class CommandZAdd : public Commander {
}
}

try {
for (size_t i = index; i < args.size(); i += 2) {
double score = std::stod(args[i]);
if (std::isnan(score)) {
return {Status::RedisParseErr, errScoreIsNotValidFloat};
}

member_scores_.emplace_back(MemberScore{args[i + 1], score});
for (size_t i = index; i < args.size(); i += 2) {
auto score = ParseFloat(args[i]);
if (!score) {
return {Status::RedisParseErr, errValueIsNotFloat};
}
} catch (const std::exception &e) {
return {Status::RedisParseErr, errValueIsNotFloat};
if (std::isnan(*score)) {
return {Status::RedisParseErr, errScoreIsNotValidFloat};
}

member_scores_.emplace_back(MemberScore{args[i + 1], *score});
}

return Commander::Parse(args);
Expand Down Expand Up @@ -2647,11 +2646,11 @@ class CommandZCard : public Commander {
class CommandZIncrBy : public Commander {
public:
Status Parse(const std::vector<std::string> &args) override {
try {
incr_ = std::stod(args[2]);
} catch (const std::exception &e) {
return {Status::RedisParseErr, "value is not an double or out of range"};
auto increment = ParseFloat(args[2]);
if (!increment) {
return {Status::RedisParseErr, errValueIsNotFloat};
}
incr_ = *increment;
return Commander::Parse(args);
}

Expand Down Expand Up @@ -3140,14 +3139,12 @@ class CommandZUnionStore : public Commander {
} else if (Util::ToLower(args[i]) == "weights" && i + numkeys_ < args.size()) {
size_t k = 0;
while (k < numkeys_) {
try {
keys_weights_[k].weight = std::stod(args[i + k + 1]);
} catch (const std::exception &e) {
return {Status::RedisParseErr, "weight is not an double or out of range"};
}
if (std::isnan(keys_weights_[k].weight)) {
return {Status::RedisParseErr, "weight is not an double or out of range"};
auto weight = ParseFloat(args[i + k + 1]);
if (!weight || std::isnan(*weight)) {
return {Status::RedisParseErr, "weight is not a double or out of range"};
}
keys_weights_[k].weight = *weight;

k++;
}
i += numkeys_ + 1;
Expand Down Expand Up @@ -3212,12 +3209,13 @@ class CommandGeoBase : public Commander {

Status ParseLongLat(const std::string &longitude_para, const std::string &latitude_para, double *longitude,
double *latitude) {
try {
*longitude = std::stod(longitude_para);
*latitude = std::stod(latitude_para);
} catch (const std::exception &e) {
auto long_stat = ParseFloat(longitude_para);
auto lat_stat = ParseFloat(latitude_para);
if (!long_stat || !lat_stat) {
return {Status::RedisParseErr, errValueIsNotFloat};
}
*longitude = *long_stat;
*latitude = *lat_stat;

if (*longitude < GEO_LONG_MIN || *longitude > GEO_LONG_MAX || *latitude < GEO_LAT_MIN || *latitude > GEO_LAT_MAX) {
return {Status::RedisParseErr, "invalid longitude,latitude pair " + longitude_para + "," + latitude_para};
Expand Down Expand Up @@ -3385,11 +3383,11 @@ class CommandGeoRadius : public CommandGeoBase {
auto s = ParseLongLat(args[2], args[3], &longitude_, &latitude_);
if (!s.IsOK()) return s;

try {
radius_ = std::stod(args[4]);
} catch (const std::exception &e) {
auto radius = ParseFloat(args[4]);
if (!radius) {
return {Status::RedisParseErr, errValueIsNotFloat};
}
radius_ = *radius;

s = ParseDistanceUnit(args[5]);
if (!s.IsOK()) return s;
Expand Down Expand Up @@ -3516,11 +3514,11 @@ class CommandGeoRadiusByMember : public CommandGeoRadius {
CommandGeoRadiusByMember() : CommandGeoRadius() {}

Status Parse(const std::vector<std::string> &args) override {
try {
radius_ = std::stod(args[3]);
} catch (const std::exception &e) {
auto radius = ParseFloat(args[3]);
if (!radius) {
return {Status::RedisParseErr, errValueIsNotFloat};
}
radius_ = *radius;

auto s = ParseDistanceUnit(args[4]);
if (!s.IsOK()) return s;
Expand Down Expand Up @@ -4545,14 +4543,12 @@ class CommandDebug : public Commander {
Status Parse(const std::vector<std::string> &args) override {
subcommand_ = Util::ToLower(args[1]);
if ((subcommand_ == "sleep") && args.size() == 3) {
double second = 0.0;
try {
second = std::stod(args[2]);
} catch (const std::exception &e) {
auto second = ParseFloat(args[2]);
if (!second) {
return {Status::RedisParseErr, "invalid debug sleep time"};
}

microsecond_ = static_cast<uint64_t>(second * 1000 * 1000);
microsecond_ = static_cast<uint64_t>(*second * 1000 * 1000);
return Status::OK();
}
return {Status::RedisInvalidCmd, "Syntax error, DEBUG SLEEP <seconds>"};
Expand Down
29 changes: 29 additions & 0 deletions src/common/parse_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@

#pragma once

#include <charconv>
#include <cstdlib>
#include <limits>
#include <string>
#include <system_error>
#include <tuple>

#include "status.h"
Expand Down Expand Up @@ -144,3 +146,30 @@ StatusOr<T> ParseInt(const std::string &v, NumericRange<T> range, int base = 0)

// available units: K, M, G, T, P
StatusOr<std::uint64_t> ParseSizeAndUnit(const std::string &v);

// TryParseFloat parses a string to a floating-point number,
// it returns the first unmatched character position instead of an error status
template <typename T = double> // float or double
StatusOr<ParseResultAndPos<T>> TryParseFloat(std::string_view str, std::chars_format fmt = std::chars_format::general) {
T result = 0;

auto stat = std::from_chars(str.begin(), str.end(), result, fmt);

if (stat.ec != std::errc{}) {
return {Status::NotOK, std::make_error_code(stat.ec).message()};
}

return {result, stat.ptr};
}

// ParseFloat parses a string to a floating-point number
template <typename T = double> // float or double
StatusOr<T> ParseFloat(std::string_view str, std::chars_format fmt = std::chars_format::general) {
auto [result, pos] = GET_OR_RET(TryParseFloat<T>(str, fmt));

if (pos != str.end()) {
return {Status::NotOK, "encounter non-number characters"};
}

return result;
}
18 changes: 18 additions & 0 deletions tests/cppunit/parse_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,21 @@ TEST(ParseUtil, ParseSizeAndUnit) {
ASSERT_FALSE(ParseSizeAndUnit("16384p"));
ASSERT_EQ(ParseSizeAndUnit("16388p").Msg(), "arithmetic overflow");
}

TEST(ParseUtil, ParseFloat) {
std::string_view v = "1.23";
ASSERT_EQ(*TryParseFloat(v), ParseResultAndPos<double>(1.23, v.end()));

v = "25345.346e65hello";
ASSERT_EQ(*TryParseFloat(v), ParseResultAndPos<double>(25345.346e65, v.end() - 5));

ASSERT_EQ(TryParseFloat("eeeeeeee").Msg(), "Invalid argument");

v = " 1e8 ";
ASSERT_EQ(*TryParseFloat(v), ParseResultAndPos<double>(1e8, v.end() - 3));

ASSERT_EQ(*ParseFloat("1.23"), 1.23);
ASSERT_EQ(*ParseFloat("1.23e2"), 1.23e2);
ASSERT_FALSE(ParseFloat("1.2 "));
ASSERT_FALSE(ParseFloat("1.2hello"));
}