GnuCash  5.6-133-gc519490283+
Data Structures | Public Member Functions
GncNumeric Class Reference

The primary numeric class for representing amounts and values. More...

#include <gnc-numeric.hpp>

Public Member Functions

 GncNumeric ()
 Default constructor provides the zero value.
 
 GncNumeric (int64_t num, int64_t denom)
 Integer constructor. More...
 
 GncNumeric (GncRational rr)
 GncRational constructor. More...
 
 GncNumeric (gnc_numeric in)
 gnc_numeric constructor, used for interfacing old code. More...
 
 GncNumeric (double d)
 Double constructor. More...
 
 GncNumeric (const std::string &str, bool autoround=false)
 String constructor. More...
 
 GncNumeric (const GncNumeric &rhs)=default
 
 GncNumeric (GncNumeric &&rhs)=default
 
GncNumericoperator= (const GncNumeric &rhs)=default
 
GncNumericoperator= (GncNumeric &&rhs)=default
 
 operator gnc_numeric () const noexcept
 gnc_numeric conversion. More...
 
 operator double () const noexcept
 double conversion. More...
 
int64_t num () const noexcept
 Accessor for numerator value.
 
int64_t denom () const noexcept
 Accessor for denominator value.
 
GncNumeric operator- () const noexcept
 
GncNumeric inv () const noexcept
 
GncNumeric abs () const noexcept
 
GncNumeric reduce () const noexcept
 Return an equivalent fraction with all common factors between the numerator and the denominator removed. More...
 
template<RoundType RT>
GncNumeric convert (int64_t new_denom) const
 Convert a GncNumeric to use a new denominator. More...
 
template<RoundType RT>
GncNumeric convert_sigfigs (unsigned int figs) const
 Convert with the specified sigfigs. More...
 
std::string to_string () const noexcept
 Return a string representation of the GncNumeric. More...
 
bool is_decimal () const noexcept
 
GncNumeric to_decimal (unsigned int max_places=17) const
 Convert the numeric to have a power-of-10 denominator if possible without rounding. More...
 
void operator+= (GncNumeric b)
 
void operator-= (GncNumeric b)
 
void operator*= (GncNumeric b)
 
void operator/= (GncNumeric b)
 
int cmp (GncNumeric b)
 
int cmp (int64_t b)
 

Detailed Description

The primary numeric class for representing amounts and values.

Calculations are generally performed in 128-bit (by converting to GncRational) and reducing the result. If the result would overflow a 64-bit representation then the GncNumeric(GncRational&) constructor will call GncRational::round_to_numeric() to get the value to fit. It will not raise an exception, so in the unlikely event that you need an error instead of rounding, use GncRational directly.

Errors: Errors are signalled by exceptions as follows:

Rounding Policy: GncNumeric provides a convert() member function that object amount and value setters (and only those functions!) should call to set a number which is represented in the commodity's SCU. Since SCUs are seldom 18 digits the convert may result in rounding, which will be performed in the method specified by the arguments passed to convert(). Overflows may result in internal rounding as described earlier.

Definition at line 59 of file gnc-numeric.hpp.

Constructor & Destructor Documentation

◆ GncNumeric() [1/5]

GncNumeric::GncNumeric ( int64_t  num,
int64_t  denom 
)
inline

Integer constructor.

Unfortunately specifying a default for denom causes ambiguity errors with the other single-argument constructors on older versions of gcc, so one must always specify both arguments.

Parameters
numThe Numerator
denomThe Denominator

Definition at line 76 of file gnc-numeric.hpp.

76  :
77  m_num(num), m_den(denom) {
78  if (denom == 0)
79  throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
80  }
int64_t num() const noexcept
Accessor for numerator value.
int64_t denom() const noexcept
Accessor for denominator value.

◆ GncNumeric() [2/5]

GncNumeric::GncNumeric ( GncRational  rr)

GncRational constructor.

This constructor will round rr's GncInt128s to fit into the int64_t members using RoundType::half-down.

Parameters
rrA GncRational.

Definition at line 70 of file gnc-numeric.cpp.

71 {
72  /* Can't use isValid here because we want to throw different exceptions. */
73  if (rr.num().isNan() || rr.denom().isNan())
74  throw std::underflow_error("Operation resulted in NaN.");
75  if (rr.num().isOverflow() || rr.denom().isOverflow())
76  throw std::overflow_error("Operation overflowed a 128-bit int.");
77  if (rr.num().isBig() || rr.denom().isBig())
78  {
79  GncRational reduced(rr.reduce());
80  rr = reduced.round_to_numeric(); // A no-op if it's already small.
81  }
82  m_num = static_cast<int64_t>(rr.num());
83  m_den = static_cast<int64_t>(rr.denom());
84 }
bool isBig() const noexcept
Definition: gnc-int128.cpp:253
GncInt128 denom() const noexcept
Denominator accessor.
GncRational reduce() const
Return an equivalent fraction with all common factors between the numerator and the denominator remov...
bool isNan() const noexcept
Definition: gnc-int128.cpp:265
Rational number class using GncInt128 for the numerator and denominator.
GncRational round_to_numeric() const
Round to fit an int64_t, finding the closest possible approximation.
GncInt128 num() const noexcept
Numerator accessor.
bool isOverflow() const noexcept
Definition: gnc-int128.cpp:259

◆ GncNumeric() [3/5]

GncNumeric::GncNumeric ( gnc_numeric  in)
inline

gnc_numeric constructor, used for interfacing old code.

This function should not be used outside of gnc-numeric.cpp.

Parameters
inA gnc_numeric.

Definition at line 96 of file gnc-numeric.hpp.

96  : m_num(in.num), m_den(in.denom)
97  {
98  if (in.denom == 0)
99  throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
100  /* gnc_numeric has a dumb convention that a negative denominator means
101  * to multiply the numerator by the denominator instead of dividing.
102  */
103  if (in.denom < 0)
104  {
105  m_num *= -in.denom;
106  m_den = 1;
107  }
108  }

◆ GncNumeric() [4/5]

GncNumeric::GncNumeric ( double  d)

Double constructor.

Parameters
dThe double to be converted. In order to fit in an int64_t, its absolute value must be < 1e18; if its absolute value is < 1e-18 it will be represented as 0, though for practical purposes nearly all commodities will round to zero at 1e-9 or larger.

Definition at line 86 of file gnc-numeric.cpp.

86  : m_num(0), m_den(1)
87 {
88  static uint64_t max_leg_value{INT64_C(1000000000000000000)};
89  if (std::isnan(d) || fabs(d) > max_leg_value)
90  {
91  std::ostringstream msg;
92  msg << "Unable to construct a GncNumeric from " << d << ".\n";
93  throw std::invalid_argument(msg.str());
94  }
95  constexpr auto max_num = static_cast<double>(INT64_MAX);
96  auto logval = log10(fabs(d));
97  int64_t den;
98  uint8_t den_digits;
99  if (logval > 0.0)
100  den_digits = (max_leg_digits + 1) - static_cast<int>(floor(logval));
101  else
102  den_digits = max_leg_digits;
103  den = powten(den_digits);
104  auto num_d = static_cast<double>(den) * d;
105  while (fabs(num_d) > max_num && den_digits > 1)
106  {
107  den = powten(--den_digits);
108  num_d = static_cast<double>(den) * d;
109  }
110  auto num = static_cast<int64_t>(floor(num_d));
111 
112  if (num == 0)
113  return;
114  GncNumeric q(num, den);
115  auto r = q.reduce();
116  m_num = r.num();
117  m_den = r.denom();
118 }
The primary numeric class for representing amounts and values.
Definition: gnc-numeric.hpp:59
int64_t num() const noexcept
Accessor for numerator value.

◆ GncNumeric() [5/5]

GncNumeric::GncNumeric ( const std::string &  str,
bool  autoround = false 
)

String constructor.

Accepts integer values in decimal and hexadecimal. Does not accept thousands separators. If the string contains a '/' it is taken to separate the numerator and denominator; if it contains either a '.' or a ',' it is taken as a decimal point and the integers on either side will be combined and a denominator will be the appropriate power of 10. If neither is present the number will be treated as an integer and m_den will be set to 1.

Whitespace around a '/' is ignored. A correctly-formatted number will be extracted from a larger string.

Numbers that cannot be represented with int64_ts will throw std::out_of_range unless a decimal point is found (see above). A 0 denominator will cause the constructor to throw std::underflow_error. An empty string or one which contains no recognizable number will result in std::invalid_argument.

Definition at line 244 of file gnc-numeric.cpp.

244  {
245  static const std::string maybe_sign ("(-?)");
246  static const std::string opt_signed_int("(-?[0-9]*)");
247  static const std::string unsigned_int("([0-9]+)");
248  static const std::string hex_frag("(0[xX][A-Fa-f0-9]+)");
249  static const std::string slash("[ \\t]*/[ \\t]*");
250  static const std::string whitespace("[ \\t]+");
251  /* The llvm standard C++ library refused to recognize the - in the
252  * opt_signed_int pattern with the default ECMAScript syntax so we use the
253  * awk syntax.
254  */
255  static const regex numeral(opt_signed_int);
256  static const regex hex(hex_frag);
257  static const regex numeral_rational(opt_signed_int + slash + unsigned_int);
258  static const regex integer_and_fraction(maybe_sign + unsigned_int + whitespace + unsigned_int + slash + unsigned_int);
259  static const regex hex_rational(hex_frag + slash + hex_frag);
260  static const regex hex_over_num(hex_frag + slash + unsigned_int);
261  static const regex num_over_hex(opt_signed_int + slash + hex_frag);
262  static const regex decimal(opt_signed_int + "[.,]" + unsigned_int);
263  static const regex scientific("(?:(-?[0-9]+[.,]?)|(-?[0-9]*)[.,]([0-9]+))[Ee](-?[0-9]+)");
264  static const regex has_hex_prefix(".*0[xX]$");
265  smatch m, x;
266  /* The order of testing the regexes is from the more restrictve to the less
267  * restrictive, as less-restrictive ones will match patterns that would also
268  * match the more-restrictive and so invoke the wrong construction.
269  */
270  if (str.empty())
271  throw std::invalid_argument(
272  "Can't construct a GncNumeric from an empty string.");
273  if (auto res = fast_numeral_rational (str.c_str()))
274  {
275  m_num = res->num;
276  m_den = res->denom;
277  return;
278  }
279  if (regex_search(str, m, hex_rational))
280  {
281  GncNumeric n(stoll(m[1].str(), nullptr, 16),
282  stoll(m[2].str(), nullptr, 16));
283 
284  m_num = n.num();
285  m_den = n.denom();
286  return;
287  }
288  if (regex_search(str, m, hex_over_num))
289  {
290  GncNumeric n(stoll(m[1].str(), nullptr, 16), stoll(m[2].str()));
291  m_num = n.num();
292  m_den = n.denom();
293  return;
294  }
295  if (regex_search(str, m, num_over_hex))
296  {
297  GncNumeric n(stoll(m[1].str()), stoll(m[2].str(), nullptr, 16));
298  m_num = n.num();
299  m_den = n.denom();
300  return;
301  }
302  if (regex_search(str, m, integer_and_fraction))
303  {
304  GncNumeric n(stoll(m[3].str()), stoll(m[4].str()));
305  n += stoll(m[2].str());
306  m_num = m[1].str().empty() ? n.num() : -n.num();
307  m_den = n.denom();
308  return;
309  }
310  if (regex_search(str, m, numeral_rational))
311  {
312  GncNumeric n(stoll(m[1].str()), stoll(m[2].str()));
313  m_num = n.num();
314  m_den = n.denom();
315  return;
316  }
317  if (regex_search(str, m, scientific) && ! regex_match(m.prefix().str(), x, has_hex_prefix))
318  {
319  auto [num, denom] =
320  reduce_number_pair(numeric_from_scientific_match(m),
321  str, autoround);
322  m_num = num;
323  m_den = denom;
324  return;
325  }
326  if (regex_search(str, m, decimal))
327  {
328  std::string integer{m[1].matched ? m[1].str() : ""};
329  std::string decimal{m[2].matched ? m[2].str() : ""};
330  auto [num, denom] =
331  reduce_number_pair(numeric_from_decimal_match(integer, decimal),
332  str, autoround);
333  m_num = num;
334  m_den = denom;
335  return;
336  }
337  if (regex_search(str, m, hex))
338  {
339  GncNumeric n(stoll(m[1].str(), nullptr, 16), INT64_C(1));
340  m_num = n.num();
341  m_den = n.denom();
342  return;
343  }
344  if (regex_search(str, m, numeral))
345  {
346  GncNumeric n(stoll(m[1].str()), INT64_C(1));
347  m_num = n.num();
348  m_den = n.denom();
349  return;
350  }
351  std::ostringstream errmsg;
352  errmsg << "String " << str << " contains no recognizable numeric value.";
353  throw std::invalid_argument(errmsg.str());
354 }
The primary numeric class for representing amounts and values.
Definition: gnc-numeric.hpp:59
int64_t num() const noexcept
Accessor for numerator value.
int64_t denom() const noexcept
Accessor for denominator value.

Member Function Documentation

◆ abs()

GncNumeric GncNumeric::abs ( ) const
noexcept
Returns
-this if this < 0 else this.

Definition at line 385 of file gnc-numeric.cpp.

386 {
387  if (m_num < 0)
388  return -*this;
389  return *this;
390 }

◆ convert()

template<RoundType RT>
GncNumeric GncNumeric::convert ( int64_t  new_denom) const
inline

Convert a GncNumeric to use a new denominator.

If rounding is necessary use the indicated template specification. For example, to use half-up rounding you'd call bar = foo.convert<RoundType::half_up>(1000). If you specify RoundType::never this will throw std::domain_error if rounding is required.

Parameters
new_denomThe new denominator to convert the fraction to.
Returns
A new GncNumeric having the requested denominator.

Definition at line 192 of file gnc-numeric.hpp.

193  {
194  auto params = prepare_conversion(new_denom);
195  if (new_denom == GNC_DENOM_AUTO)
196  new_denom = m_den;
197  if (params.rem == 0)
198  return GncNumeric(params.num, new_denom);
199  return GncNumeric(round(params.num, params.den,
200  params.rem, RT2T<RT>()), new_denom);
201  }
GncNumeric()
Default constructor provides the zero value.
Definition: gnc-numeric.hpp:65
#define GNC_DENOM_AUTO
Values that can be passed as the &#39;denom&#39; argument.
Definition: gnc-numeric.h:247

◆ convert_sigfigs()

template<RoundType RT>
GncNumeric GncNumeric::convert_sigfigs ( unsigned int  figs) const
inline

Convert with the specified sigfigs.

The resulting denominator depends on the value of the GncNumeric, such that the specified significant digits are retained in the numerator and the denominator is always a power of

  1. This is of rather dubious benefit in an accounting program, but it's used in several places so it needs to be implemented.
Parameters
figsThe number of digits to use for the numerator.
Returns
A GncNumeric with the specified number of digits in the numerator and the appropriate power-of-ten denominator.

Definition at line 215 of file gnc-numeric.hpp.

216  {
217  auto new_denom(sigfigs_denom(figs));
218  auto params = prepare_conversion(new_denom);
219  if (new_denom == 0) //It had better not, but just in case...
220  new_denom = 1;
221  if (params.rem == 0)
222  return GncNumeric(params.num, new_denom);
223  return GncNumeric(round(params.num, params.den,
224  params.rem, RT2T<RT>()), new_denom);
225  }
GncNumeric()
Default constructor provides the zero value.
Definition: gnc-numeric.hpp:65

◆ inv()

GncNumeric GncNumeric::inv ( ) const
noexcept
Returns
0 if this == 0 else 1/this.

Definition at line 375 of file gnc-numeric.cpp.

376 {
377  if (m_num == 0)
378  return *this;
379  if (m_num < 0)
380  return GncNumeric(-m_den, -m_num);
381  return GncNumeric(m_den, m_num);
382 }
GncNumeric()
Default constructor provides the zero value.
Definition: gnc-numeric.hpp:65

◆ is_decimal()

bool GncNumeric::is_decimal ( ) const
noexcept
Returns
true if the denominator is a power of ten, false otherwise.

Definition at line 449 of file gnc-numeric.cpp.

450 {
451  for (unsigned pwr = 0; pwr < max_leg_digits && m_den >= pten[pwr]; ++pwr)
452  {
453  if (m_den == pten[pwr])
454  return true;
455  if (m_den % pten[pwr])
456  return false;
457  }
458  return false;
459 }

◆ operator double()

GncNumeric::operator double ( ) const
noexcept

double conversion.

Use static_cast<double>(foo)

Definition at line 361 of file gnc-numeric.cpp.

362 {
363  return static_cast<double>(m_num) / static_cast<double>(m_den);
364 }

◆ operator gnc_numeric()

GncNumeric::operator gnc_numeric ( ) const
noexcept

gnc_numeric conversion.

Use static_cast<gnc_numeric>(foo)

Definition at line 356 of file gnc-numeric.cpp.

357 {
358  return {m_num, m_den};
359 }

◆ operator-()

GncNumeric GncNumeric::operator- ( ) const
noexcept
Returns
A GncNumeric with the opposite sign.

Definition at line 367 of file gnc-numeric.cpp.

368 {
369  GncNumeric b(*this);
370  b.m_num = - b.m_num;
371  return b;
372 }
The primary numeric class for representing amounts and values.
Definition: gnc-numeric.hpp:59

◆ reduce()

GncNumeric GncNumeric::reduce ( ) const
noexcept

Return an equivalent fraction with all common factors between the numerator and the denominator removed.

Returns
reduced GncNumeric

Definition at line 393 of file gnc-numeric.cpp.

394 {
395  return static_cast<GncNumeric>(GncRational(*this).reduce());
396 }
GncRational reduce() const
Return an equivalent fraction with all common factors between the numerator and the denominator remov...
The primary numeric class for representing amounts and values.
Definition: gnc-numeric.hpp:59
Rational number class using GncInt128 for the numerator and denominator.

◆ to_decimal()

GncNumeric GncNumeric::to_decimal ( unsigned int  max_places = 17) const

Convert the numeric to have a power-of-10 denominator if possible without rounding.

Throws a std::range_error on failure; the message will explain the details.

Parameters
max_placesexponent of the largest permissible denominator.
Returns
A GncNumeric value with the new denominator.

Definition at line 462 of file gnc-numeric.cpp.

463 {
464  if (max_places > max_leg_digits)
465  max_places = max_leg_digits;
466 
467  if (m_num == 0)
468  return GncNumeric();
469 
470  if (is_decimal())
471  {
472  if (m_num == 0 || m_den < powten(max_places))
473  return *this; // Nothing to do.
474  /* See if we can reduce m_num to fit in max_places */
475  auto excess = m_den / powten(max_places);
476  if (m_num % excess)
477  {
478  std::ostringstream msg;
479  msg << "GncNumeric " << *this
480  << " could not be represented in " << max_places
481  << " decimal places without rounding.\n";
482  throw std::range_error(msg.str());
483  }
484  return GncNumeric(m_num / excess, powten(max_places));
485  }
486  GncRational rr(*this);
487  rr = rr.convert<RoundType::never>(powten(max_places)); //May throw
488  /* rr might have gotten reduced a bit too much; if so, put it back: */
489  unsigned int pwr{1};
490  for (; pwr <= max_places && !(rr.denom() % powten(pwr)); ++pwr);
491  auto reduce_to = powten(pwr);
492  GncInt128 rr_num(rr.num()), rr_den(rr.denom());
493  if (rr_den % reduce_to)
494  {
495  auto factor(reduce_to / rr.denom());
496  rr_num *= factor;
497  rr_den *= factor;
498  }
499  while (!rr_num.isZero() && rr_num > 9 && rr_den > 9 && rr_num % 10 == 0)
500  {
501  rr_num /= 10;
502  rr_den /= 10;
503  }
504  try
505  {
506  /* Construct from the parts to avoid the GncRational constructor's
507  * automatic rounding.
508  */
509  return {static_cast<int64_t>(rr_num), static_cast<int64_t>(rr_den)};
510  }
511  catch (const std::invalid_argument& err)
512  {
513  std::ostringstream msg;
514  msg << "GncNumeric " << *this
515  << " could not be represented as a decimal without rounding.\n";
516  throw std::range_error(msg.str());
517  }
518  catch (const std::overflow_error& err)
519  {
520  std::ostringstream msg;
521  msg << "GncNumeric " << *this
522  << " overflows when attempting to convert it to decimal.\n";
523  throw std::range_error(msg.str());
524  }
525  catch (const std::underflow_error& err)
526  {
527  std::ostringstream msg;
528  msg << "GncNumeric " << *this
529  << " underflows when attempting to convert it to decimal.\n";
530  throw std::range_error(msg.str());
531  }
532 }
Rational number class using GncInt128 for the numerator and denominator.
GncNumeric()
Default constructor provides the zero value.
Definition: gnc-numeric.hpp:65
bool is_decimal() const noexcept

◆ to_string()

std::string GncNumeric::to_string ( ) const
noexcept

Return a string representation of the GncNumeric.

See operator<< for details.

Definition at line 441 of file gnc-numeric.cpp.

442 {
443  std::ostringstream out;
444  out << *this;
445  return out.str();
446 }

The documentation for this class was generated from the following files: