ReadValueToken correctly parses numbers
Changes the implementation of numbers parsing from std::atoi/f to std::strtod/l. These support setting the optional end-of-string pointer which are used to determine if a parsing was successful or not. This has the nice side effect of *greatly* simplifying the logic, at the expense of some C-style details. Tests added to verify that the different edge cases are handled properly.
This commit is contained in:
parent
63bfb550ff
commit
8e4e9b15d8
@ -87,46 +87,19 @@ namespace Opm {
|
||||
|
||||
std::copy( view.begin(), view.end(), buffer.begin() );
|
||||
|
||||
/*
|
||||
* isdigit can be a macro and is unreliable with std::algorithm. By
|
||||
* wrapping it in a lambda (which will be inlined anyway) this problem
|
||||
* goes away.
|
||||
*/
|
||||
const auto is_digit = []( char ch ) { return std::isdigit( ch ); };
|
||||
char* end;
|
||||
auto value = std::strtol( buffer.data(), &end, 10 );
|
||||
|
||||
if( !std::isdigit( view[ 0 ] ) && view[ 0 ] != '+' && view[ 0 ] != '-' )
|
||||
throw std::invalid_argument( "Ints must start with +/-/digit" );
|
||||
|
||||
if( !std::all_of( view.begin() + 1, view.end(), is_digit ) )
|
||||
if( std::distance( buffer.data(), end ) != int( view.size() ) )
|
||||
throw std::invalid_argument( "Expected integer, got '" + view + "'" );
|
||||
|
||||
/*
|
||||
* Unlike float, we can't atoi() and check if non-zero, simply because
|
||||
* atoi will parse '3.2' as '3', i.e. it will read to the first
|
||||
* non-digit character and stop.
|
||||
*/
|
||||
return std::atoi( buffer.data() );
|
||||
return value;
|
||||
}
|
||||
|
||||
static inline bool fortran_float( char ch ) {
|
||||
return ch == 'd' || ch == 'D' || ch == 'E';
|
||||
}
|
||||
|
||||
static inline bool slow_check_is_zero( const string_view& view ) {
|
||||
/* zero doubles can be expressed many ways. A simple, but slow check to
|
||||
* verify that a double parsed as zero actually is zero
|
||||
*/
|
||||
if( view[ 0 ] != '0' && view[ 0 ] != '.' ) return false;
|
||||
|
||||
const auto zero = []( char ch ) { return ch == '0'; };
|
||||
|
||||
auto itr = std::find_if_not( view.begin(), view.end(), zero );
|
||||
|
||||
if( itr == view.end() ) return true;
|
||||
if( *itr != '.' ) return false;
|
||||
return std::find_if_not( itr++, view.end(), zero ) != view.end();
|
||||
}
|
||||
|
||||
template<>
|
||||
double readValueToken< double >( string_view view ) {
|
||||
if( view.empty() ) throw std::invalid_argument( "Empty input string" );
|
||||
@ -140,11 +113,10 @@ namespace Opm {
|
||||
|
||||
std::copy( view.begin(), view.end(), buffer.begin() );
|
||||
|
||||
/* fast path - with any non-zero non-fortran-exponent float this will
|
||||
* be sufficient. We only do the extra work when absolutely needed
|
||||
*/
|
||||
auto value = std::atof( buffer.data() );
|
||||
if( value != 0.0 ) return value;
|
||||
char* end;
|
||||
auto value = std::strtod( buffer.data(), &end );
|
||||
if( std::distance( buffer.data(), end ) == int( view.size() ) )
|
||||
return value;
|
||||
|
||||
// Eclipse supports Fortran syntax for specifying exponents of floating point
|
||||
// numbers ('D' and 'E', e.g., 1.234d5) while C++ only supports the 'e' (e.g.,
|
||||
@ -155,10 +127,9 @@ namespace Opm {
|
||||
if( fortran != buffer.end() )
|
||||
*fortran = 'e';
|
||||
|
||||
value = std::atof( buffer.data() );
|
||||
if( value != 0.0 ) return value;
|
||||
|
||||
if( slow_check_is_zero( view ) ) return 0.0;
|
||||
value = std::strtod( buffer.data(), &end );
|
||||
if( std::distance( buffer.data(), end ) == int( view.size() ) )
|
||||
return value;
|
||||
|
||||
throw std::invalid_argument( "Expected double, got: '" + view + "'" );
|
||||
}
|
||||
|
@ -100,8 +100,21 @@ BOOST_AUTO_TEST_CASE( ContainsStar_WithStar_ReturnsTrue ) {
|
||||
|
||||
BOOST_AUTO_TEST_CASE( readValueToken_basic_validity_tests ) {
|
||||
BOOST_CHECK_THROW( Opm::readValueToken<int>( std::string( "3.3" ) ), std::invalid_argument );
|
||||
BOOST_CHECK_EQUAL( 3, Opm::readValueToken<int>( std::string( "3" ) ) );
|
||||
BOOST_CHECK_EQUAL( 3, Opm::readValueToken<int>( std::string( "+3" ) ) );
|
||||
BOOST_CHECK_EQUAL( -3, Opm::readValueToken<int>( std::string( "-3" ) ) );
|
||||
BOOST_CHECK_THROW( Opm::readValueToken<double>( std::string( "truls" ) ), std::invalid_argument );
|
||||
BOOST_CHECK_EQUAL( "3.3", Opm::readValueToken<std::string>( std::string( "3.3" ) ) );
|
||||
BOOST_CHECK_EQUAL( 0, Opm::readValueToken<double>( std::string( "0" ) ) );
|
||||
BOOST_CHECK_EQUAL( 0, Opm::readValueToken<double>( std::string( "0.0" ) ) );
|
||||
BOOST_CHECK_EQUAL( 0, Opm::readValueToken<double>( std::string( "+0.0" ) ) );
|
||||
BOOST_CHECK_EQUAL( 0, Opm::readValueToken<double>( std::string( "-0.0" ) ) );
|
||||
BOOST_CHECK_EQUAL( 0, Opm::readValueToken<double>( std::string( ".0" ) ) );
|
||||
BOOST_CHECK_THROW( Opm::readValueToken<double>( std::string( "1.0.0" ) ), std::invalid_argument );
|
||||
BOOST_CHECK_THROW( Opm::readValueToken<double>( std::string( "1g0" ) ), std::invalid_argument );
|
||||
BOOST_CHECK_THROW( Opm::readValueToken<double>( std::string( "1.23h" ) ), std::invalid_argument );
|
||||
BOOST_CHECK_THROW( Opm::readValueToken<double>( std::string( "+1.23h" ) ), std::invalid_argument );
|
||||
BOOST_CHECK_THROW( Opm::readValueToken<double>( std::string( "-1.23h" ) ), std::invalid_argument );
|
||||
BOOST_CHECK_EQUAL( 3.3, Opm::readValueToken<double>( std::string( "3.3" ) ) );
|
||||
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>( std::string( "3.3e0" ) ), 1e-6 );
|
||||
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>( std::string( "3.3d0" ) ), 1e-6 );
|
||||
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>( std::string( "3.3E0" ) ), 1e-6 );
|
||||
|
Loading…
Reference in New Issue
Block a user