GnuCash  5.0
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 64 of file gnc-numeric.cpp.

65 {
66  /* Can't use isValid here because we want to throw different exceptions. */
67  if (rr.num().isNan() || rr.denom().isNan())
68  throw std::underflow_error("Operation resulted in NaN.");
69  if (rr.num().isOverflow() || rr.denom().isOverflow())
70  throw std::overflow_error("Operation overflowed a 128-bit int.");
71  if (rr.num().isBig() || rr.denom().isBig())
72  {
73  GncRational reduced(rr.reduce());
74  rr = reduced.round_to_numeric(); // A no-op if it's already small.
75  }
76  m_num = static_cast<int64_t>(rr.num());
77  m_den = static_cast<int64_t>(rr.denom());
78 }
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 80 of file gnc-numeric.cpp.

80  : m_num(0), m_den(1)
81 {
82  static uint64_t max_leg_value{INT64_C(1000000000000000000)};
83  if (std::isnan(d) || fabs(d) > max_leg_value)
84  {
85  std::ostringstream msg;
86  msg << "Unable to construct a GncNumeric from " << d << ".\n";
87  throw std::invalid_argument(msg.str());
88  }
89  constexpr auto max_num = static_cast<double>(INT64_MAX);
90  auto logval = log10(fabs(d));
91  int64_t den;
92  uint8_t den_digits;
93  if (logval > 0.0)
94  den_digits = (max_leg_digits + 1) - static_cast<int>(floor(logval) + 1.0);
95  else
96  den_digits = max_leg_digits;
97  den = powten(den_digits);
98  auto num_d = static_cast<double>(den) * d;
99  while (fabs(num_d) > max_num && den_digits > 1)
100  {
101  den = powten(--den_digits);
102  num_d = static_cast<double>(den) * d;
103  }
104  auto num = static_cast<int64_t>(floor(num_d));
105 
106  if (num == 0)
107  return;
108  GncNumeric q(num, den);
109  auto r = q.reduce();
110  m_num = r.num();
111  m_den = r.denom();
112 }
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 117 of file gnc-numeric.cpp.

118 {
119  static const std::string numer_frag("(-?[0-9]*)");
120  static const std::string denom_frag("([0-9]+)");
121  static const std::string hex_frag("(0x[a-f0-9]+)");
122  static const std::string slash( "[ \\t]*/[ \\t]*");
123  /* The llvm standard C++ library refused to recognize the - in the
124  * numer_frag pattern with the default ECMAScript syntax so we use the
125  * awk syntax.
126  */
127  static const regex numeral(numer_frag);
128  static const regex hex(hex_frag);
129  static const regex numeral_rational(numer_frag + slash + denom_frag);
130  static const regex hex_rational(hex_frag + slash + hex_frag);
131  static const regex hex_over_num(hex_frag + slash + denom_frag);
132  static const regex num_over_hex(numer_frag + slash + hex_frag);
133  static const regex decimal(numer_frag + "[.,]" + denom_frag);
134  smatch m;
135 /* The order of testing the regexes is from the more restrictve to the less
136  * restrictive, as less-restrictive ones will match patterns that would also
137  * match the more-restrictive and so invoke the wrong construction.
138  */
139  if (str.empty())
140  throw std::invalid_argument("Can't construct a GncNumeric from an empty string.");
141  if (regex_search(str, m, hex_rational))
142  {
143  GncNumeric n(stoll(m[1].str(), nullptr, 16),
144  stoll(m[2].str(), nullptr, 16));
145  m_num = n.num();
146  m_den = n.denom();
147  return;
148  }
149  if (regex_search(str, m, hex_over_num))
150  {
151  GncNumeric n(stoll(m[1].str(), nullptr, 16),
152  stoll(m[2].str()));
153  m_num = n.num();
154  m_den = n.denom();
155  return;
156  }
157  if (regex_search(str, m, num_over_hex))
158  {
159  GncNumeric n(stoll(m[1].str()),
160  stoll(m[2].str(), nullptr, 16));
161  m_num = n.num();
162  m_den = n.denom();
163  return;
164  }
165  if (regex_search(str, m, numeral_rational))
166  {
167  GncNumeric n(stoll(m[1].str()), stoll(m[2].str()));
168  m_num = n.num();
169  m_den = n.denom();
170  return;
171  }
172  if (regex_search(str, m, decimal))
173  {
174  auto neg = (m[1].length() && m[1].str()[0] == '-');
175  GncInt128 high((neg && m[1].length() > 1) || (!neg && m[1].length()) ?
176  stoll(m[1].str()) : 0);
177  GncInt128 low(stoll(m[2].str()));
178  int64_t d = powten(m[2].str().length());
179  GncInt128 n = high * d + (neg ? -low : low);
180 
181  if (!autoround && n.isBig())
182  {
183  std::ostringstream errmsg;
184  errmsg << "Decimal string " << m[1].str() << "." << m[2].str()
185  << "can't be represented in a GncNumeric without rounding.";
186  throw std::overflow_error(errmsg.str());
187  }
188  while (n.isBig() && d > 0)
189  {
190  n >>= 1;
191  d >>= 1;
192  }
193  if (n.isBig()) //Shouldn't happen, of course
194  {
195  std::ostringstream errmsg;
196  errmsg << "Decimal string " << m[1].str() << "." << m[2].str()
197  << " can't be represented in a GncNumeric, even after reducing denom to " << d;
198  throw std::overflow_error(errmsg.str());
199  }
200  GncNumeric gncn(static_cast<int64_t>(n), d);
201  m_num = gncn.num();
202  m_den = gncn.denom();
203  return;
204  }
205  if (regex_search(str, m, hex))
206  {
207  GncNumeric n(stoll(m[1].str(), nullptr, 16),INT64_C(1));
208  m_num = n.num();
209  m_den = n.denom();
210  return;
211  }
212  if (regex_search(str, m, numeral))
213  {
214  GncNumeric n(stoll(m[1].str()), INT64_C(1));
215  m_num = n.num();
216  m_den = n.denom();
217  return;
218  }
219  std::ostringstream errmsg;
220  errmsg << "String " << str << " contains no recognizable numeric value.";
221  throw std::invalid_argument(errmsg.str());
222 }
bool isBig() const noexcept
Definition: gnc-int128.cpp:253
The primary numeric class for representing amounts and values.
Definition: gnc-numeric.hpp:59

Member Function Documentation

◆ abs()

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

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

254 {
255  if (m_num < 0)
256  return -*this;
257  return *this;
258 }

◆ 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 243 of file gnc-numeric.cpp.

244 {
245  if (m_num == 0)
246  return *this;
247  if (m_num < 0)
248  return GncNumeric(-m_den, -m_num);
249  return GncNumeric(m_den, m_num);
250 }
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 317 of file gnc-numeric.cpp.

318 {
319  for (unsigned pwr = 0; pwr < max_leg_digits && m_den >= pten[pwr]; ++pwr)
320  {
321  if (m_den == pten[pwr])
322  return true;
323  if (m_den % pten[pwr])
324  return false;
325  }
326  return false;
327 }

◆ operator double()

GncNumeric::operator double ( ) const
noexcept

double conversion.

Use static_cast<double>(foo)

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

230 {
231  return static_cast<double>(m_num) / static_cast<double>(m_den);
232 }

◆ operator gnc_numeric()

GncNumeric::operator gnc_numeric ( ) const
noexcept

gnc_numeric conversion.

Use static_cast<gnc_numeric>(foo)

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

225 {
226  return {m_num, m_den};
227 }

◆ operator-()

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

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

236 {
237  GncNumeric b(*this);
238  b.m_num = - b.m_num;
239  return b;
240 }
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 261 of file gnc-numeric.cpp.

262 {
263  return static_cast<GncNumeric>(GncRational(*this).reduce());
264 }
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 330 of file gnc-numeric.cpp.

331 {
332  if (max_places > max_leg_digits)
333  max_places = max_leg_digits;
334 
335  if (m_num == 0)
336  return GncNumeric();
337 
338  if (is_decimal())
339  {
340  if (m_num == 0 || m_den < powten(max_places))
341  return *this; // Nothing to do.
342  /* See if we can reduce m_num to fit in max_places */
343  auto excess = m_den / powten(max_places);
344  if (m_num % excess)
345  {
346  std::ostringstream msg;
347  msg << "GncNumeric " << *this
348  << " could not be represented in " << max_places
349  << " decimal places without rounding.\n";
350  throw std::range_error(msg.str());
351  }
352  return GncNumeric(m_num / excess, powten(max_places));
353  }
354  GncRational rr(*this);
355  rr = rr.convert<RoundType::never>(powten(max_places)); //May throw
356  /* rr might have gotten reduced a bit too much; if so, put it back: */
357  unsigned int pwr{1};
358  for (; pwr <= max_places && !(rr.denom() % powten(pwr)); ++pwr);
359  auto reduce_to = powten(pwr);
360  GncInt128 rr_num(rr.num()), rr_den(rr.denom());
361  if (rr_den % reduce_to)
362  {
363  auto factor(reduce_to / rr.denom());
364  rr_num *= factor;
365  rr_den *= factor;
366  }
367  while (!rr_num.isZero() && rr_num > 9 && rr_den > 9 && rr_num % 10 == 0)
368  {
369  rr_num /= 10;
370  rr_den /= 10;
371  }
372  try
373  {
374  /* Construct from the parts to avoid the GncRational constructor's
375  * automatic rounding.
376  */
377  return {static_cast<int64_t>(rr_num), static_cast<int64_t>(rr_den)};
378  }
379  catch (const std::invalid_argument& err)
380  {
381  std::ostringstream msg;
382  msg << "GncNumeric " << *this
383  << " could not be represented as a decimal without rounding.\n";
384  throw std::range_error(msg.str());
385  }
386  catch (const std::overflow_error& err)
387  {
388  std::ostringstream msg;
389  msg << "GncNumeric " << *this
390  << " overflows when attempting to convert it to decimal.\n";
391  throw std::range_error(msg.str());
392  }
393  catch (const std::underflow_error& err)
394  {
395  std::ostringstream msg;
396  msg << "GncNumeric " << *this
397  << " underflows when attempting to convert it to decimal.\n";
398  throw std::range_error(msg.str());
399  }
400 }
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 309 of file gnc-numeric.cpp.

310 {
311  std::ostringstream out;
312  out << *this;
313  return out.str();
314 }

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