Templetize and improve beast string-to-integer conversions:

* Properly handle numbers at the edge of precision
* Improve and expand unit test coverage
This commit is contained in:
Nik Bougalis
2014-09-16 22:49:02 -07:00
parent 579b1e6f79
commit 30eb927ad4
2 changed files with 215 additions and 56 deletions

View File

@@ -74,7 +74,6 @@ public:
try try
{ {
lexicalCastThrow<int>("\xef\xbc\x91\xef\xbc\x90"); // utf-8 encoded lexicalCastThrow<int>("\xef\xbc\x91\xef\xbc\x90"); // utf-8 encoded
fail("Should throw");
} }
catch(BadLexicalCast const&) catch(BadLexicalCast const&)
{ {
@@ -82,6 +81,175 @@ public:
} }
} }
template <class T>
void tryBadConvert (std::string const& s)
{
T out;
expect (!lexicalCastChecked (out, s), s);
}
void testConversionOverflows()
{
testcase ("conversion overflows");
tryBadConvert <std::uint64_t> ("99999999999999999999");
tryBadConvert <std::uint32_t> ("4294967300");
tryBadConvert <std::uint16_t> ("75821");
}
void testConversionUnderflows ()
{
testcase ("conversion underflows");
tryBadConvert <std::uint32_t> ("-1");
tryBadConvert <std::int64_t> ("-99999999999999999999");
tryBadConvert <std::int32_t> ("-4294967300");
tryBadConvert <std::int16_t> ("-75821");
}
template <class T>
bool tryEdgeCase (std::string const& s)
{
T ret;
bool const result = lexicalCastChecked (ret, s);
if (!result)
return false;
return s == std::to_string (ret);
}
void testEdgeCases ()
{
testcase ("conversion edge cases");
expect(tryEdgeCase <std::uint64_t> ("18446744073709551614"));
expect(tryEdgeCase <std::uint64_t> ("18446744073709551615"));
expect(!tryEdgeCase <std::uint64_t> ("18446744073709551616"));
expect(tryEdgeCase <std::int64_t> ("9223372036854775806"));
expect(tryEdgeCase <std::int64_t> ("9223372036854775807"));
expect(!tryEdgeCase <std::int64_t> ("9223372036854775808"));
expect(tryEdgeCase <std::int64_t> ("-9223372036854775807"));
expect(tryEdgeCase <std::int64_t> ("-9223372036854775808"));
expect(!tryEdgeCase <std::int64_t> ("-9223372036854775809"));
expect(tryEdgeCase <std::uint32_t> ("4294967294"));
expect(tryEdgeCase <std::uint32_t> ("4294967295"));
expect(!tryEdgeCase <std::uint32_t> ("4294967296"));
expect(tryEdgeCase <std::int32_t> ("2147483646"));
expect(tryEdgeCase <std::int32_t> ("2147483647"));
expect(!tryEdgeCase <std::int32_t> ("2147483648"));
expect(tryEdgeCase <std::int32_t> ("-2147483647"));
expect(tryEdgeCase <std::int32_t> ("-2147483648"));
expect(!tryEdgeCase <std::int32_t> ("-2147483649"));
expect(tryEdgeCase <std::uint16_t> ("65534"));
expect(tryEdgeCase <std::uint16_t> ("65535"));
expect(!tryEdgeCase <std::uint16_t> ("65536"));
expect(tryEdgeCase <std::int16_t> ("32766"));
expect(tryEdgeCase <std::int16_t> ("32767"));
expect(!tryEdgeCase <std::int16_t> ("32768"));
expect(tryEdgeCase <std::int16_t> ("-32767"));
expect(tryEdgeCase <std::int16_t> ("-32768"));
expect(!tryEdgeCase <std::int16_t> ("-32769"));
}
template <class T>
void testThrowConvert(std::string const& s, bool success)
{
bool result = !success;
T out;
try
{
out = lexicalCastThrow <T> (s);
result = true;
}
catch(BadLexicalCast const&)
{
result = false;
}
expect (result == success, s);
}
void testThrowingConversions ()
{
testcase ("throwing conversion");
testThrowConvert <std::uint64_t> ("99999999999999999999", false);
testThrowConvert <std::uint64_t> ("9223372036854775806", true);
testThrowConvert <std::uint32_t> ("4294967290", true);
testThrowConvert <std::uint32_t> ("42949672900", false);
testThrowConvert <std::uint32_t> ("429496729000", false);
testThrowConvert <std::uint32_t> ("4294967290000", false);
testThrowConvert <std::int32_t> ("5294967295", false);
testThrowConvert <std::int32_t> ("-2147483644", true);
testThrowConvert <std::int16_t> ("66666", false);
testThrowConvert <std::int16_t> ("-5711", true);
}
void testZero ()
{
testcase ("zero conversion");
{
std::int32_t out;
expect (lexicalCastChecked (out, "-0"), "0");
expect (lexicalCastChecked (out, "0"), "0");
expect (lexicalCastChecked (out, "+0"), "0");
}
{
std::uint32_t out;
expect (!lexicalCastChecked (out, "-0"), "0");
expect (lexicalCastChecked (out, "0"), "0");
expect (lexicalCastChecked (out, "+0"), "0");
}
}
void testEntireRange ()
{
testcase ("entire range");
std::int32_t i = std::numeric_limits<std::int16_t>::min();
std::string const empty("");
while (i <= std::numeric_limits<std::int16_t>::max())
{
std::int16_t j = static_cast<std::int16_t>(i);
auto actual = std::to_string (j);
auto result = lexicalCast (j, empty);
expect (result == actual, actual + " (string to integer)");
if (result == actual)
{
auto number = lexicalCast <std::int16_t> (result);
if (number != j)
expect (false, actual + " (integer to string)");
}
i++;
}
}
void run() void run()
{ {
std::int64_t const seedValue = 50; std::int64_t const seedValue = 50;
@@ -98,6 +266,12 @@ public:
testIntegers <std::uint64_t> (r); testIntegers <std::uint64_t> (r);
testPathologies(); testPathologies();
testConversionOverflows ();
testConversionUnderflows ();
testThrowingConversions ();
testZero ();
testEdgeCases ();
testEntireRange ();
} }
}; };

View File

@@ -29,6 +29,8 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include <iostream>
namespace beast { namespace beast {
namespace detail { namespace detail {
@@ -47,14 +49,16 @@ parse_integral (Int& num, FwdIt first, FwdIt last, Accumulator accumulator)
if (first == last) if (first == last)
return false; return false;
while (first != last) while (first != last)
{ {
auto const c = *first++; auto const c = *first++;
if (c < '0' || c > '9') if (c < '0' || c > '9')
return false; return false;
if (!accumulator(Int(c - '0'))) if (!accumulator(num, Int(c - '0')))
return false; return false;
} }
return true; return true;
} }
@@ -62,14 +66,19 @@ template <class Int, class FwdIt>
bool bool
parse_negative_integral (Int& num, FwdIt first, FwdIt last) parse_negative_integral (Int& num, FwdIt first, FwdIt last)
{ {
Int const limit = std::numeric_limits <Int>::min(); Int limit_value = std::numeric_limits <Int>::min() / 10;
Int limit_digit = std::numeric_limits <Int>::min() % 10;
if (limit_digit < 0)
limit_digit = -limit_digit;
return parse_integral<Int> (num, first, last, return parse_integral<Int> (num, first, last,
[&num,&limit](Int n) [limit_value, limit_digit](Int& value, Int digit)
{ {
if ((limit - (10 * num)) > -n) assert ((digit >= 0) && (digit <= 9));
if (value < limit_value || (value == limit_value && digit > limit_digit))
return false; return false;
num = 10 * num - n; value = (value * 10) - digit;
return true; return true;
}); });
} }
@@ -78,14 +87,16 @@ template <class Int, class FwdIt>
bool bool
parse_positive_integral (Int& num, FwdIt first, FwdIt last) parse_positive_integral (Int& num, FwdIt first, FwdIt last)
{ {
Int const limit = std::numeric_limits <Int>::max(); Int limit_value = std::numeric_limits <Int>::max() / 10;
Int limit_digit = std::numeric_limits <Int>::max() % 10;
return parse_integral<Int> (num, first, last, return parse_integral<Int> (num, first, last,
[&num,&limit](Int n) [limit_value, limit_digit](Int& value, Int digit)
{ {
if (n > (limit - (10 * num))) assert ((digit >= 0) && (digit <= 9));
if (value > limit_value || (value == limit_value && digit > limit_digit))
return false; return false;
num = 10 * num + n; value = (value * 10) + digit;
return true; return true;
}); });
} }
@@ -129,17 +140,22 @@ struct LexicalCast;
template <class In> template <class In>
struct LexicalCast <std::string, In> struct LexicalCast <std::string, In>
{ {
bool operator() (std::string& out, short in) { out = std::to_string(in); return true; } template <class Arithmetic = In>
bool operator() (std::string& out, int in) { out = std::to_string(in); return true; } std::enable_if_t <std::is_arithmetic <Arithmetic>::value, bool>
bool operator() (std::string& out, long in) { out = std::to_string(in); return true; } operator () (std::string& out, Arithmetic in)
bool operator() (std::string& out, long long in) { out = std::to_string(in); return true; } {
bool operator() (std::string& out, unsigned short in) { out = std::to_string(in); return true; } out = std::to_string (in);
bool operator() (std::string& out, unsigned int in) { out = std::to_string(in); return true; } return true;
bool operator() (std::string& out, unsigned long in) { out = std::to_string(in); return true; } }
bool operator() (std::string& out, unsigned long long in) { out = std::to_string(in); return true; }
bool operator() (std::string& out, float in) { out = std::to_string(in); return true; } template <class Enumeration = In>
bool operator() (std::string& out, double in) { out = std::to_string(in); return true; } std::enable_if_t <std::is_enum <Enumeration>::value, bool>
bool operator() (std::string& out, long double in) { out = std::to_string(in); return true; } operator () (std::string& out, Enumeration in)
{
out = std::to_string (
static_cast <std::underlying_type_t <Enumeration>> (in));
return true;
}
}; };
// Parse std::string to number // Parse std::string to number
@@ -150,43 +166,20 @@ struct LexicalCast <Out, std::string>
"beast::LexicalCast can only be used with integral types"); "beast::LexicalCast can only be used with integral types");
template <class Integral = Out> template <class Integral = Out>
typename std::enable_if <std::is_unsigned <Integral>::value, bool>::type std::enable_if_t <std::is_unsigned <Integral>::value, bool>
operator () (Integral& out, std::string const& in) const operator () (Integral& out, std::string const& in) const
{ {
return parseUnsigned (out, std::begin(in), std::end(in)); return parseUnsigned (out, std::begin(in), std::end(in));
} }
template <class Integral = Out> template <class Integral = Out>
typename std::enable_if <std::is_signed <Integral>::value, bool>::type std::enable_if_t <std::is_signed <Integral>::value, bool>
operator () (Integral& out, std::string const& in) const operator () (Integral& out, std::string const& in) const
{ {
return parseSigned (out, std::begin(in), std::end(in)); return parseSigned (out, std::begin(in), std::end(in));
} }
}; };
#if 0
template <class Out>
bool
LexicalCast <Out, std::string>::operator() (bool& out, std::string const& in) const
{
// boost::lexical_cast is very strict, it
// throws on anything but "1" or "0"
//
if (in == "1")
{
out = true;
return true;
}
else if (in == "0")
{
out = false;
return true;
}
return false;
}
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Conversion from null terminated char const* // Conversion from null terminated char const*
@@ -206,15 +199,7 @@ struct LexicalCast <Out, char*>
{ {
bool operator() (Out& out, char* in) const bool operator() (Out& out, char* in) const
{ {
Out result; return LexicalCast <Out, std::string>()(out, in);
if (LexicalCast <Out, char const*> () (result, in))
{
out = result;
return true;
}
return false;
} }
}; };