namespace example { struct lcm_generator { lcm_generator() { } template T as() const; }; template <> inline unsigned lcm_generator::as() const { return 160000U; } template <> inline int64_t lcm_generator::as() const { return INT64(160000); } template <> inline double lcm_generator::as() const { return 160000.0; } int64_t round(double); lcm_generator const lcm; } // ***************************************************************************** // Design Notes: Adding 0.5 to _value before calling std::floor(), which // rounds down, causes rounding to the nearest whole number. This only works // for positive values, however. // ----------------------------------------------------------------------------- inline int64_t example::round(double const _value) { return static_cast(std::floor(_value + 0.5)); } namespace example { namespace custom { template T extract(char const * & _input, char const * _description, std::string const & _input); int64_t parse(std::string const & _value); int64_t parse_fraction(char const *& _end, int64_t _result, char const * _denominator, bool _is_mixed_number, std::string const & _value); void raise_extract_failed(char const * _description, std::string const & _value) GNU_ATTRIBUTE(noreturn); void raise_zero_denominator(std::string const &) GNU_ATTRIBUTE(noreturn); } } // ***************************************************************************** // Design Notes: Non-dependent exception throwing code appears in // raise_extract_failed(). // ----------------------------------------------------------------------------- template T example::custom::extract(char const * & _input, char const * const _description, std::string const & _value) { T result; if (!core::to_number(result, _input, &_input, 10)) { raise_extract_failed(_description, _value); } return result; } // ***************************************************************************** // Design Notes: This function parses several number formats: floating // point, whole number, fraction, and mixed number. All formats begin with a // number, so the input is first parsed and converted to int64_t, base 10. If // a decimal point appears immediately after the input consumed for that // conversion, then reparse from the beginning as floating point rather than // jump through hoops to compute the fractional part. If there is a space // after the initial number, then the initial number is a whole number that may // be followed by a fraction, meaning the input was a mixed number. If there // is a slash after the initial number, then the initial number is the // numerator of a fraction. // // When converting a whole number, it is important to avoid floating point // representational and radix problems. The int64_t conversion is done with // base 10, thus ignoring any leading zeroes. Conversion to double would work // without specifying the base, but then it is difficult to know whether to // look for a fraction, for a mixed number, and the integer value may not be // representable in a double. // // Negative values, except when the input is a whole number, must be processed // as positive until after the fractional part has been parsed and computed to // simplify accounting for rounding errors in round() and to account correctly // for magnitude. // // When computing a fraction, the product of lcm and numerator is divided by // denominator in order to reduce rounding and representational errors that // would result from first computing the quotient of numerator and denominator. // ----------------------------------------------------------------------------- int64_t example::custom::parse(std::string const & _value) { assert(std::string::npos != _value.find_first_not_of("-+0123456789eE ./")); if (_value.empty()) { return 0; } char const * const start(_value.c_str()); char const * end(start); int64_t result; (void)core::to_number(result, end, &end, 10); bool const is_floating_point('.' == *end); if (is_floating_point) { end = start; double value(extract(end, "input", _value) * lcm.as()); bool const is_negative(0 > result); if (is_negative) { value = -value; } result = round(value); if (is_negative) { result = -result; } } else { result *= lcm.as(); char const * const last(start + _value.length()); if (end != last) { size_t const slash(_value.find_first_of('/', end - start)); bool const is_fraction(std::string::npos != slash); if (is_fraction) { char const * const denominator(start + slash + 1); bool const is_mixed_number( std::string::npos != _value.find_last_of(' ', slash)); bool const is_non_negative(0 <= result); if (is_non_negative) { result = parse_fraction(end, result, denominator, is_mixed_number, _value); } else { result = -parse_fraction(end, -result, denominator, is_mixed_number, _value); } } } } assert(end == (start + _value.length()) || std::string::npos == _value.find_first_not_of( " \t\r\n", end - start, 4)); return result; } // ***************************************************************************** // Design Notes: // ----------------------------------------------------------------------------- int64_t example::custom::parse_fraction(char const *& _end, int64_t const _result, char const * const _denominator, bool const _is_mixed_number, std::string const & _value) { double value; if (_is_mixed_number) { unsigned const numerator(extract(_end, "numerator", _value)); _end = _denominator; unsigned const denominator( extract(_end, "denominator", _value)); double fraction(numerator * lcm.as() / denominator); value = _result + fraction; } else { _end = _denominator; unsigned const denominator( extract(_end, "denominator", _value)); value = _result / denominator; } int64_t const result(round(value)); return result; } namespace example { namespace xpressive { struct direct_impl { typedef int64_t result_type; template int64_t operator ()(Value const & _value) const { int64_t result(core::numeric_cast(_value)); result *= lcm.as(); return result; } }; // exception type indicating denominator was zero struct zero_denominator { }; struct to_price_impl { typedef int64_t result_type; template int64_t operator ()(Value const & _value) const { int64_t result(0); if (_value) { double const value(core::numeric_cast(_value)); result = round(lcm.as() * value); } return result; } template int64_t operator ()(Value const & _numerator, Value const & _denominator) const { unsigned const numerator(core::numeric_cast(_numerator)); unsigned const denominator( core::numeric_cast(_denominator)); if (0 == denominator) { throw zero_denominator(); } double const fraction(lcm.as() * numerator / denominator); return round(fraction); } template int64_t operator ()(Value const & _whole, double const _fraction) const { int const whole(core::numeric_cast(_whole)); double const value(lcm.as() * whole + _fraction); return round(value); } }; ThreadLocal match_s; } } namespace example { namespace xpressive { namespace prices { using namespace boost::xpressive; function::type const direct = {{}}; function::type const to_price = {{}}; placeholder is_negative_; placeholder price_; const BOOST_PROTO_AUTO(zero, as_xpr('0')); const BOOST_PROTO_AUTO(sign, as_xpr('-') [ is_negative_ = true ] | as_xpr('+')); // ignore const BOOST_PROTO_AUTO(fraction, ( *zero >> (s1= +_d) >> *blank >> '/' >> *blank >> *zero >> (s2= +_d) ) [ price_ = to_price(s1, s2) ]); const BOOST_PROTO_AUTO(real, ( *_d >> '.' >> *_d >> !((set= 'E', 'e', 'G', 'g') >> +_d) ) [ price_ = to_price(_) ]); const BOOST_PROTO_AUTO(whole_number, ( *zero >> (s1= +_d) >> +blank >> fraction ) [ price_ = to_price(s1, price_) ]); const BOOST_PROTO_AUTO(integer, ( *zero >> (s1= +_d) ) [ price_ = direct(s1) ]); const sregex price = *blank >> !sign >> (real | whole_number | fraction | integer) >> *space; } } } // ***************************************************************************** // Design Notes: // ----------------------------------------------------------------------------- int64_t example::xpressive::parse(const std::string & _value) { typedef std::string::const_iterator iterator; using namespace prices; int64_t result; iterator begin(_value.begin()); iterator end(_value.end()); bool is_negative(false); boost::xpressive::smatch & match(match_s.getValue()); match.let(price_ = result); match.let(is_negative_ = is_negative); bool matched; try { matched = boost::xpressive::regex_match(begin, end, match, price); } catch (zero_denominator) { raise_zero_denominator(_value); } if (!matched) { raise_parse_failure(_value); } if (is_negative) { result = -result; } return result; }