GnuCash  4.12-527-g05ffd3d4eb
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 66 of file gnc-numeric.cpp.

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

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

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

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

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

◆ 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:246

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

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

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

◆ operator double()

GncNumeric::operator double ( ) const
noexcept

double conversion.

Use static_cast<double>(foo)

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

232 {
233  return static_cast<double>(m_num) / static_cast<double>(m_den);
234 }

◆ operator gnc_numeric()

GncNumeric::operator gnc_numeric ( ) const
noexcept

gnc_numeric conversion.

Use static_cast<gnc_numeric>(foo)

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

227 {
228  return {m_num, m_den};
229 }

◆ operator-()

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

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

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

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

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

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

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