When formatting doubles as strings, use 17 digits of precision to ensure they are read back in without loss (up from 12 digits).

Add a mode to the JSON parser to parse all real numbers into strings, so we can later parse them into doubles or another fixed/floating point format to preserve as much precision as needed
This commit is contained in:
Eric Frias 2015-03-31 09:47:54 -04:00
parent dde8ed9d7a
commit e5a5323642
4 changed files with 44 additions and 33 deletions

View file

@ -19,7 +19,8 @@ namespace fc
{ {
legacy_parser = 0, legacy_parser = 0,
strict_parser = 1, strict_parser = 1,
relaxed_parser = 2 relaxed_parser = 2,
legacy_parser_with_string_doubles = 3
}; };
static ostream& to_stream( ostream& out, const fc::string& ); static ostream& to_stream( ostream& out, const fc::string& );

View file

@ -581,7 +581,7 @@ namespace fc { namespace json_relaxed
continue; continue;
} }
if( skip_white_space(in) ) continue; if( skip_white_space(in) ) continue;
string key = stringFromStream<T, strict>( in ); string key = json_relaxed::stringFromStream<T, strict>( in );
skip_white_space(in); skip_white_space(in);
if( in.peek() != ':' ) if( in.peek() != ':' )
{ {
@ -589,7 +589,7 @@ namespace fc { namespace json_relaxed
("key", key) ); ("key", key) );
} }
in.get(); in.get();
auto val = variant_from_stream<T, strict>( in ); auto val = json_relaxed::variant_from_stream<T, strict>( in );
obj(std::move(key),std::move(val)); obj(std::move(key),std::move(val));
skip_white_space(in); skip_white_space(in);
@ -630,7 +630,7 @@ namespace fc { namespace json_relaxed
continue; continue;
} }
if( skip_white_space(in) ) continue; if( skip_white_space(in) ) continue;
ar.push_back( variant_from_stream<T, strict>(in) ); ar.push_back( json_relaxed::variant_from_stream<T, strict>(in) );
skip_white_space(in); skip_white_space(in);
} }
if( in.peek() != ']' ) if( in.peek() != ']' )
@ -647,7 +647,7 @@ namespace fc { namespace json_relaxed
variant numberFromStream( T& in ) variant numberFromStream( T& in )
{ try { { try {
fc::string token = tokenFromStream(in); fc::string token = tokenFromStream(in);
variant result = parseNumberOrStr<strict>( token ); variant result = json_relaxed::parseNumberOrStr<strict>( token );
if( strict && !(result.is_int64() || result.is_uint64() || result.is_double()) ) if( strict && !(result.is_int64() || result.is_uint64() || result.is_double()) )
FC_THROW_EXCEPTION( parse_error_exception, "expected: number" ); FC_THROW_EXCEPTION( parse_error_exception, "expected: number" );
return result; return result;
@ -700,11 +700,11 @@ namespace fc { namespace json_relaxed
in.get(); in.get();
continue; continue;
case '"': case '"':
return stringFromStream<T, strict>( in ); return json_relaxed::stringFromStream<T, strict>( in );
case '{': case '{':
return objectFromStream<T, strict>( in ); return json_relaxed::objectFromStream<T, strict>( in );
case '[': case '[':
return arrayFromStream<T, strict>( in ); return json_relaxed::arrayFromStream<T, strict>( in );
case '-': case '-':
case '+': case '+':
case '.': case '.':
@ -718,7 +718,7 @@ namespace fc { namespace json_relaxed
case '7': case '7':
case '8': case '8':
case '9': case '9':
return numberFromStream<T, strict>( in ); return json_relaxed::numberFromStream<T, strict>( in );
// null, true, false, or 'warning' / string // null, true, false, or 'warning' / string
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p':
@ -729,7 +729,7 @@ namespace fc { namespace json_relaxed
case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
case 'Y': case 'Z': case 'Y': case 'Z':
case '_': case '/': case '_': case '/':
return wordFromStream<T, strict>( in ); return json_relaxed::wordFromStream<T, strict>( in );
case 0x04: // ^D end of transmission case 0x04: // ^D end of transmission
case EOF: case EOF:
FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" );

View file

@ -15,16 +15,15 @@
namespace fc namespace fc
{ {
// forward declarations of provided functions // forward declarations of provided functions
template<typename T> variant variant_from_stream( T& in ); template<typename T, json::parse_type parser_type> variant variant_from_stream( T& in );
template<typename T> char parseEscape( T& in ); template<typename T> char parseEscape( T& in );
template<typename T> fc::string stringFromStream( T& in ); template<typename T> fc::string stringFromStream( T& in );
template<typename T> bool skip_white_space( T& in ); template<typename T> bool skip_white_space( T& in );
template<typename T> fc::string stringFromToken( T& in ); template<typename T> fc::string stringFromToken( T& in );
template<typename T> variant_object objectFromStream( T& in ); template<typename T, json::parse_type parser_type> variant_object objectFromStream( T& in );
template<typename T> variants arrayFromStream( T& in ); template<typename T, json::parse_type parser_type> variants arrayFromStream( T& in );
template<typename T> variant number_from_stream( T& in ); template<typename T, json::parse_type parser_type> variant number_from_stream( T& in );
template<typename T> variant token_from_stream( T& in ); template<typename T> variant token_from_stream( T& in );
template<typename T> variant variant_from_stream( T& in );
void escape_string( const string& str, ostream& os ); void escape_string( const string& str, ostream& os );
template<typename T> void to_stream( T& os, const variants& a ); template<typename T> void to_stream( T& os, const variants& a );
template<typename T> void to_stream( T& os, const variant_object& o ); template<typename T> void to_stream( T& os, const variant_object& o );
@ -168,7 +167,7 @@ namespace fc
("token", token.str() ) ); ("token", token.str() ) );
} }
template<typename T> template<typename T, json::parse_type parser_type>
variant_object objectFromStream( T& in ) variant_object objectFromStream( T& in )
{ {
mutable_variant_object obj; mutable_variant_object obj;
@ -197,7 +196,7 @@ namespace fc
("key", key) ); ("key", key) );
} }
in.get(); in.get();
auto val = variant_from_stream( in ); auto val = variant_from_stream<T, parser_type>( in );
obj(std::move(key),std::move(val)); obj(std::move(key),std::move(val));
skip_white_space(in); skip_white_space(in);
@ -219,7 +218,7 @@ namespace fc
} FC_RETHROW_EXCEPTIONS( warn, "Error parsing object" ); } FC_RETHROW_EXCEPTIONS( warn, "Error parsing object" );
} }
template<typename T> template<typename T, json::parse_type parser_type>
variants arrayFromStream( T& in ) variants arrayFromStream( T& in )
{ {
variants ar; variants ar;
@ -238,7 +237,7 @@ namespace fc
continue; continue;
} }
if( skip_white_space(in) ) continue; if( skip_white_space(in) ) continue;
ar.push_back( variant_from_stream(in) ); ar.push_back( variant_from_stream<T, parser_type>(in) );
skip_white_space(in); skip_white_space(in);
} }
if( in.peek() != ']' ) if( in.peek() != ']' )
@ -251,7 +250,7 @@ namespace fc
return ar; return ar;
} }
template<typename T> template<typename T, json::parse_type parser_type>
variant number_from_stream( T& in ) variant number_from_stream( T& in )
{ {
fc::stringstream ss; fc::stringstream ss;
@ -309,7 +308,7 @@ namespace fc
if (str == "-." || str == ".") // check the obviously wrong things we could have encountered if (str == "-." || str == ".") // check the obviously wrong things we could have encountered
FC_THROW_EXCEPTION(parse_error_exception, "Can't parse token \"${token}\" as a JSON numeric constant", ("token", str)); FC_THROW_EXCEPTION(parse_error_exception, "Can't parse token \"${token}\" as a JSON numeric constant", ("token", str));
if( dot ) if( dot )
return to_double(str); return parser_type == json::legacy_parser_with_string_doubles ? variant(str) : variant(to_double(str));
if( neg ) if( neg )
return to_int64(str); return to_int64(str);
return to_uint64(str); return to_uint64(str);
@ -386,7 +385,7 @@ namespace fc
} }
template<typename T> template<typename T, json::parse_type parser_type>
variant variant_from_stream( T& in ) variant variant_from_stream( T& in )
{ {
skip_white_space(in); skip_white_space(in);
@ -404,9 +403,9 @@ namespace fc
case '"': case '"':
return stringFromStream( in ); return stringFromStream( in );
case '{': case '{':
return objectFromStream( in ); return objectFromStream<T, parser_type>( in );
case '[': case '[':
return arrayFromStream( in ); return arrayFromStream<T, parser_type>( in );
case '-': case '-':
case '.': case '.':
case '0': case '0':
@ -419,7 +418,7 @@ namespace fc
case '7': case '7':
case '8': case '8':
case '9': case '9':
return number_from_stream( in ); return number_from_stream<T, parser_type>( in );
// null, true, false, or 'warning' / string // null, true, false, or 'warning' / string
case 'n': case 'n':
case 't': case 't':
@ -466,7 +465,9 @@ namespace fc
switch( ptype ) switch( ptype )
{ {
case legacy_parser: case legacy_parser:
return variant_from_stream( in ); return variant_from_stream<fc::stringstream, legacy_parser>( in );
case legacy_parser_with_string_doubles:
return variant_from_stream<fc::stringstream, legacy_parser_with_string_doubles>( in );
case strict_parser: case strict_parser:
return json_relaxed::variant_from_stream<fc::stringstream, true>( in ); return json_relaxed::variant_from_stream<fc::stringstream, true>( in );
case relaxed_parser: case relaxed_parser:
@ -760,7 +761,9 @@ namespace fc
switch( ptype ) switch( ptype )
{ {
case legacy_parser: case legacy_parser:
return variant_from_stream( bi ); return variant_from_stream<boost::filesystem::ifstream, legacy_parser>( bi );
case legacy_parser_with_string_doubles:
return variant_from_stream<boost::filesystem::ifstream, legacy_parser_with_string_doubles>( bi );
case strict_parser: case strict_parser:
return json_relaxed::variant_from_stream<boost::filesystem::ifstream, true>( bi ); return json_relaxed::variant_from_stream<boost::filesystem::ifstream, true>( bi );
case relaxed_parser: case relaxed_parser:
@ -774,7 +777,9 @@ namespace fc
switch( ptype ) switch( ptype )
{ {
case legacy_parser: case legacy_parser:
return variant_from_stream( in ); return variant_from_stream<fc::buffered_istream, legacy_parser>( in );
case legacy_parser_with_string_doubles:
return variant_from_stream<fc::buffered_istream, legacy_parser_with_string_doubles>( in );
case strict_parser: case strict_parser:
return json_relaxed::variant_from_stream<buffered_istream, true>( in ); return json_relaxed::variant_from_stream<buffered_istream, true>( in );
case relaxed_parser: case relaxed_parser:
@ -807,7 +812,10 @@ namespace fc
switch( ptype ) switch( ptype )
{ {
case legacy_parser: case legacy_parser:
variant_from_stream( in ); variant_from_stream<fc::stringstream, legacy_parser>( in );
break;
case legacy_parser_with_string_doubles:
variant_from_stream<fc::stringstream, legacy_parser_with_string_doubles>( in );
break; break;
case strict_parser: case strict_parser:
json_relaxed::variant_from_stream<fc::stringstream, true>( in ); json_relaxed::variant_from_stream<fc::stringstream, true>( in );

View file

@ -9,6 +9,7 @@
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <locale> #include <locale>
#include <limits>
/** /**
* Implemented with std::string for now. * Implemented with std::string for now.
@ -127,11 +128,12 @@ namespace fc {
FC_RETHROW_EXCEPTIONS( warn, "${i} => double", ("i",i) ) FC_RETHROW_EXCEPTIONS( warn, "${i} => double", ("i",i) )
} }
fc::string to_string( double d) fc::string to_string(double d)
{ {
// +2 is required to ensure that the double is rounded correctly when read back in. http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
std::stringstream ss; std::stringstream ss;
ss << std::setprecision(12) << std::fixed << d; ss << std::setprecision(std::numeric_limits<double>::digits10 + 2) << std::fixed << d;
return ss.str(); //boost::lexical_cast<std::string>(d); return ss.str();
} }
fc::string to_string( uint64_t d) fc::string to_string( uint64_t d)