[gnc-numeric.cpp] parse integer + fraction; e.g."10 1/4" == 10.25

This commit is contained in:
Christopher Lam
2023-08-29 08:29:59 +08:00
parent 9802c80996
commit c45b9736ab
3 changed files with 50 additions and 18 deletions

View File

@@ -208,21 +208,24 @@ numeric_from_scientific_match(smatch &m)
}
GncNumeric::GncNumeric(const std::string &str, bool autoround) {
static const std::string numer_frag("(-?[0-9]*)");
static const std::string denom_frag("([0-9]+)");
static const std::string maybe_sign ("(-?)");
static const std::string opt_signed_int("(-?[0-9]*)");
static const std::string unsigned_int("([0-9]+)");
static const std::string hex_frag("(0[xX][A-Fa-f0-9]+)");
static const std::string slash("[ \\t]*/[ \\t]*");
static const std::string whitespace("[ \\t]+");
/* The llvm standard C++ library refused to recognize the - in the
* numer_frag pattern with the default ECMAScript syntax so we use the
* opt_signed_int pattern with the default ECMAScript syntax so we use the
* awk syntax.
*/
static const regex numeral(numer_frag);
static const regex numeral(opt_signed_int);
static const regex hex(hex_frag);
static const regex numeral_rational(numer_frag + slash + denom_frag);
static const regex numeral_rational(opt_signed_int + slash + unsigned_int);
static const regex integer_and_fraction(maybe_sign + unsigned_int + whitespace + unsigned_int + slash + unsigned_int);
static const regex hex_rational(hex_frag + slash + hex_frag);
static const regex hex_over_num(hex_frag + slash + denom_frag);
static const regex num_over_hex(numer_frag + slash + hex_frag);
static const regex decimal(numer_frag + "[.,]" + denom_frag);
static const regex hex_over_num(hex_frag + slash + unsigned_int);
static const regex num_over_hex(opt_signed_int + slash + hex_frag);
static const regex decimal(opt_signed_int + "[.,]" + unsigned_int);
static const regex scientific("(?:(-?[0-9]+[.,]?)|(-?[0-9]*)[.,]([0-9]+))[Ee](-?[0-9]+)");
static const regex has_hex_prefix(".*0[xX]$");
smatch m, x;
@@ -256,6 +259,14 @@ GncNumeric::GncNumeric(const std::string &str, bool autoround) {
m_den = n.denom();
return;
}
if (regex_search(str, m, integer_and_fraction))
{
GncNumeric n(stoll(m[3].str()), stoll(m[4].str()));
n += stoll(m[2].str());
m_num = m[1].str().empty() ? n.num() : -n.num();
m_den = n.denom();
return;
}
if (regex_search(str, m, numeral_rational))
{
GncNumeric n(stoll(m[1].str()), stoll(m[2].str()));
@@ -1303,19 +1314,19 @@ gnc_num_dbg_to_string(gnc_numeric n)
return p;
}
gboolean
string_to_gnc_numeric(const gchar* str, gnc_numeric *n)
gnc_numeric
gnc_numeric_from_string (const gchar* str)
{
if (!str)
return gnc_numeric_error (GNC_ERROR_ARG);
try
{
GncNumeric an(str);
*n = static_cast<gnc_numeric>(an);
return TRUE;
return GncNumeric (str);
}
catch (const std::exception& err)
{
PWARN("%s", err.what());
return FALSE;
return gnc_numeric_error (GNC_ERROR_ARG);
}
}

View File

@@ -296,10 +296,9 @@ gnc_numeric double_to_gnc_numeric(double n, gint64 denom,
gint how);
/** Read a gnc_numeric from str, skipping any leading whitespace.
* Return TRUE on success and store the resulting value in "n".
* Return NULL on error. */
gboolean string_to_gnc_numeric(const gchar* str, gnc_numeric *n);
* Returns the resulting gnc_numeric.
* Return GNC_ERROR_ARG on error. */
gnc_numeric gnc_numeric_from_string (const gchar* str);
/** Create a gnc_numeric object that signals the error condition
* noted by error_code, rather than a number.
*/

View File

@@ -180,6 +180,28 @@ TEST(gncnumeric_constructors, test_string_constructor)
GncNumeric neg_embedded("The number is -123456/456");
EXPECT_EQ(-123456, neg_embedded.num());
EXPECT_EQ(456, neg_embedded.denom());
ASSERT_NO_THROW(GncNumeric integer_fraction("The number is 1234 567/890"));
GncNumeric integer_fraction("The number is 1234 567/890");
EXPECT_EQ(1098827, integer_fraction.num());
EXPECT_EQ(890, integer_fraction.denom());
ASSERT_NO_THROW(GncNumeric neg_integer_fraction("The number is -1234 567/890"));
GncNumeric neg_integer_fraction("The number is -1234 567/890");
EXPECT_EQ(-1098827, neg_integer_fraction.num());
EXPECT_EQ(890, neg_integer_fraction.denom());
GncNumeric integer_large_fraction("The number is 1234 4567/890");
EXPECT_EQ(1102827, integer_large_fraction.num());
EXPECT_EQ(890, integer_large_fraction.denom());
GncNumeric neg_integer_large_fraction("The number is -1234 4567/890");
EXPECT_EQ(-1102827, neg_integer_large_fraction.num());
EXPECT_EQ(890, neg_integer_large_fraction.denom());
ASSERT_NO_THROW(GncNumeric zero_integer_fraction("The number is 0 567/890"));
GncNumeric zero_integer_fraction("The number is 0 567/890");
EXPECT_EQ(567, zero_integer_fraction.num());
EXPECT_EQ(890, zero_integer_fraction.denom());
ASSERT_NO_THROW(GncNumeric neg_zero_integer_fraction("The number is -0 567/890"));
GncNumeric neg_zero_integer_fraction("The number is -0 567/890");
EXPECT_EQ(-567, neg_zero_integer_fraction.num());
EXPECT_EQ(890, neg_zero_integer_fraction.denom());
EXPECT_THROW(GncNumeric throw_zero_denom("123/0"), std::invalid_argument);
EXPECT_THROW(GncNumeric overflow("12345678987654321.123456"),
std::overflow_error);