diff --git a/libgnucash/engine/gnc-numeric.cpp b/libgnucash/engine/gnc-numeric.cpp index 4401592943..4297cab309 100644 --- a/libgnucash/engine/gnc-numeric.cpp +++ b/libgnucash/engine/gnc-numeric.cpp @@ -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(an); - return TRUE; + return GncNumeric (str); } catch (const std::exception& err) { PWARN("%s", err.what()); - return FALSE; + return gnc_numeric_error (GNC_ERROR_ARG); } } diff --git a/libgnucash/engine/gnc-numeric.h b/libgnucash/engine/gnc-numeric.h index 254d18ca9f..f08c0c5266 100644 --- a/libgnucash/engine/gnc-numeric.h +++ b/libgnucash/engine/gnc-numeric.h @@ -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. */ diff --git a/libgnucash/engine/test/gtest-gnc-numeric.cpp b/libgnucash/engine/test/gtest-gnc-numeric.cpp index 8194359d1c..ac6b1fe964 100644 --- a/libgnucash/engine/test/gtest-gnc-numeric.cpp +++ b/libgnucash/engine/test/gtest-gnc-numeric.cpp @@ -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);