GnuCash c935c2f+
Loading...
Searching...
No Matches
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.
 
 GncNumeric (GncRational rr)
 GncRational constructor.
 
 GncNumeric (gnc_numeric in)
 gnc_numeric constructor, used for interfacing old code.
 
 GncNumeric (double d)
 Double constructor.
 
 GncNumeric (const std::string &str, bool autoround=false)
 String constructor.
 
 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.
 
 operator double () const noexcept
 double conversion.
 
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.
 
template<RoundType RT>
GncNumeric convert (int64_t new_denom) const
 Convert a GncNumeric to use a new denominator.
 
template<RoundType RT>
GncNumeric convert_sigfigs (unsigned int figs) const
 Convert with the specified sigfigs.
 
std::string to_string () const noexcept
 Return a string representation of the GncNumeric.
 
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.
 
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 60 of file gnc-numeric.hpp.

Constructor & Destructor Documentation

◆ GncNumeric() [1/6]

GncNumeric::GncNumeric ( )
inline

Default constructor provides the zero value.

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

66: m_num (0), m_den(1) {}

◆ GncNumeric() [2/6]

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 77 of file gnc-numeric.hpp.

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

◆ GncNumeric() [3/6]

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

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

◆ GncNumeric() [4/6]

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 97 of file gnc-numeric.hpp.

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

◆ GncNumeric() [5/6]

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

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

◆ GncNumeric() [6/6]

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

247 {
248 static const std::string begin("^[^-.0-9]*");
249 static const std::string end("[^0-9]*$");
250 static const std::string begin_group("(?:");
251 static const std::string end_group(")");
252 static const std::string or_op("|");
253 static const std::string maybe_sign ("(-?)");
254 static const std::string opt_signed_int("(-?[0-9]*)");
255 static const std::string opt_signed_separated_int("(-?[0-9]{1,3})");
256 static const std::string unsigned_int("([0-9]+)");
257 static const std::string eu_separated_int("(?:[[:space:]'.]([0-9]{3}))?");
258 static const std::string en_separated_int("(?:\\,([0-9]{3}))?");
259 static const std::string eu_decimal_part("(?:\\,([0-9]+))?");
260 static const std::string en_decimal_part("(?:\\.([0-9]+))?");
261 static const std::string hex_frag("(0[xX][A-Fa-f0-9]+)");
262 static const std::string slash("[ \\t]*/[ \\t]*");
263 static const std::string whitespace("[ \\t]+");
264 static const std::string eu_sep_decimal(begin_group + opt_signed_separated_int + eu_separated_int + eu_separated_int + eu_separated_int + eu_separated_int + eu_decimal_part + end_group);
265 static const std::string en_sep_decimal(begin_group + opt_signed_separated_int + en_separated_int + en_separated_int + en_separated_int + en_separated_int + en_decimal_part + end_group);
266 /* The llvm standard C++ library refused to recognize the - in the
267 * opt_signed_int pattern with the default ECMAScript syntax so we use the
268 * awk syntax.
269 */
270 static const regex numeral(begin + opt_signed_int + end);
271 static const regex hex(begin + hex_frag + end);
272 static const regex numeral_rational(begin + opt_signed_int + slash + unsigned_int + end);
273 static const regex integer_and_fraction(begin + maybe_sign + unsigned_int + whitespace + unsigned_int + slash + unsigned_int + end);
274 static const regex hex_rational(begin + hex_frag + slash + hex_frag + end);
275 static const regex hex_over_num(begin + hex_frag + slash + unsigned_int + end);
276 static const regex num_over_hex(begin + opt_signed_int + slash + hex_frag + end);
277 static const regex decimal(begin + opt_signed_int + "[.,]" + unsigned_int + end);
278 static const u32regex sep_decimal =
279 boost::make_u32regex(begin + begin_group + eu_sep_decimal + or_op + en_sep_decimal + end_group + end);
280 static const regex scientific("(?:(-?[0-9]+[.,]?)|(-?[0-9]*)[.,]([0-9]+))[Ee](-?[0-9]+)");
281 static const regex has_hex_prefix(".*0[xX]$");
282 smatch m, x;
283 /* The order of testing the regexes is from the more restrictve to the less
284 * restrictive, as less-restrictive ones will match patterns that would also
285 * match the more-restrictive and so invoke the wrong construction.
286 */
287 if (str.empty())
288 throw std::invalid_argument(
289 "Can't construct a GncNumeric from an empty string.");
290 if (auto res = fast_numeral_rational (str.c_str()))
291 {
292 m_num = res->num;
293 m_den = res->denom;
294 return;
295 }
296 if (regex_search(str, m, hex_rational))
297 {
298 GncNumeric n(stoll(m[1].str(), nullptr, 16),
299 stoll(m[2].str(), nullptr, 16));
300
301 m_num = n.num();
302 m_den = n.denom();
303 return;
304 }
305 if (regex_search(str, m, hex_over_num))
306 {
307 GncNumeric n(stoll(m[1].str(), nullptr, 16), stoll(m[2].str()));
308 m_num = n.num();
309 m_den = n.denom();
310 return;
311 }
312 if (regex_search(str, m, num_over_hex))
313 {
314 GncNumeric n(stoll(m[1].str()), stoll(m[2].str(), nullptr, 16));
315 m_num = n.num();
316 m_den = n.denom();
317 return;
318 }
319 if (regex_search(str, m, integer_and_fraction))
320 {
321 GncNumeric n(stoll(m[3].str()), stoll(m[4].str()));
322 n += stoll(m[2].str());
323 m_num = m[1].str().empty() ? n.num() : -n.num();
324 m_den = n.denom();
325 return;
326 }
327 if (regex_search(str, m, numeral_rational))
328 {
329 GncNumeric n(stoll(m[1].str()), stoll(m[2].str()));
330 m_num = n.num();
331 m_den = n.denom();
332 return;
333 }
334 if (regex_search(str, m, scientific) && ! regex_match(m.prefix().str(), x, has_hex_prefix))
335 {
336 auto [num, denom] =
337 reduce_number_pair(numeric_from_scientific_match(m),
338 str, autoround);
339 m_num = num;
340 m_den = denom;
341 return;
342 }
343 if (regex_search(str, m, decimal))
344 {
345 std::string integer{m[1].matched ? m[1].str() : ""};
346 std::string decimal{m[2].matched ? m[2].str() : ""};
347 auto [num, denom] = reduce_number_pair(numeric_from_decimal_match(integer, decimal), str, autoround);
348 m_num = num;
349 m_den = denom;
350 return;
351 }
352 if (u32regex_search(str, m, sep_decimal))
353 {
354 /* There's a bit of magic here because of the complexity of
355 * the regex. It supports two formats, one for locales that
356 * use space, apostrophe, or dot for thousands separator and
357 * comma for decimal separator and the other for locales that
358 * use comma for thousands and dot for decimal. For each
359 * format there are 5 captures for thousands-groups (allowing
360 * up to 10^16 - 1) and one for decimal, hence the loops from
361 * 1 - 5 and 7 - 11 with the decimal being either capture 6 or
362 * capture 12.
363 */
364 std::string integer(""), decimal("");
365 for (auto i{1}; i < 6; ++i)
366 if (m[i].matched)
367 integer += m[i].str();
368 if (m[6].matched)
369 decimal += m[6].str();
370 if (integer.empty() && decimal.empty())
371 {
372 for (auto i{7}; i <12; ++i)
373 if (m[i].matched)
374 integer += m[i].str();
375 if (m[12].matched)
376 decimal += m[12].str();
377 }
378 auto [num, denom] =
379 reduce_number_pair(numeric_from_decimal_match(integer, decimal),
380 str, autoround);
381 m_num = num;
382 m_den = denom;
383 return;
384 }
385 if (regex_search(str, m, hex))
386 {
387 GncNumeric n(stoll(m[1].str(), nullptr, 16), INT64_C(1));
388 m_num = n.num();
389 m_den = n.denom();
390 return;
391 }
392 if (regex_search(str, m, numeral))
393 {
394 GncNumeric n(stoll(m[1].str()), INT64_C(1));
395 m_num = n.num();
396 m_den = n.denom();
397 return;
398 }
399 std::ostringstream errmsg;
400 errmsg << "String " << str << " contains no recognizable numeric value.";
401 throw std::invalid_argument(errmsg.str());
402}

Member Function Documentation

◆ abs()

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

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

434{
435 if (m_num < 0)
436 return -*this;
437 return *this;
438}

◆ 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 193 of file gnc-numeric.hpp.

194 {
195 auto params = prepare_conversion(new_denom);
196 if (new_denom == GNC_DENOM_AUTO)
197 new_denom = m_den;
198 if (params.rem == 0)
199 return GncNumeric(params.num, new_denom);
200 return GncNumeric(round(params.num, params.den,
201 params.rem, RT2T<RT>()), new_denom);
202 }
GncNumeric()
Default constructor provides the zero value.
#define GNC_DENOM_AUTO
Values that can be passed as the 'denom' argument.

◆ 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 216 of file gnc-numeric.hpp.

217 {
218 auto new_denom(sigfigs_denom(figs));
219 auto params = prepare_conversion(new_denom);
220 if (new_denom == 0) //It had better not, but just in case...
221 new_denom = 1;
222 if (params.rem == 0)
223 return GncNumeric(params.num, new_denom);
224 return GncNumeric(round(params.num, params.den,
225 params.rem, RT2T<RT>()), new_denom);
226 }

◆ denom()

int64_t GncNumeric::denom ( ) const
inlinenoexcept

Accessor for denominator value.

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

162{ return m_den; }

◆ inv()

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

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

424{
425 if (m_num == 0)
426 return *this;
427 if (m_num < 0)
428 return GncNumeric(-m_den, -m_num);
429 return GncNumeric(m_den, m_num);
430}

◆ is_decimal()

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

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

498{
499 for (unsigned pwr = 0; pwr < max_leg_digits && m_den >= pten[pwr]; ++pwr)
500 {
501 if (m_den == pten[pwr])
502 return true;
503 if (m_den % pten[pwr])
504 return false;
505 }
506 return false;
507}

◆ num()

int64_t GncNumeric::num ( ) const
inlinenoexcept

Accessor for numerator value.

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

158{ return m_num; }

◆ operator double()

GncNumeric::operator double ( ) const
noexcept

double conversion.

Use static_cast<double>(foo)

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

410{
411 return static_cast<double>(m_num) / static_cast<double>(m_den);
412}

◆ operator gnc_numeric()

GncNumeric::operator gnc_numeric ( ) const
noexcept

gnc_numeric conversion.

Use static_cast<gnc_numeric>(foo)

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

405{
406 return {m_num, m_den};
407}

◆ operator-()

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

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

416{
417 GncNumeric b(*this);
418 b.m_num = - b.m_num;
419 return b;
420}

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

442{
443 return static_cast<GncNumeric>(GncRational(*this).reduce());
444}

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

511{
512 if (max_places > max_leg_digits)
513 max_places = max_leg_digits;
514
515 if (m_num == 0)
516 return GncNumeric();
517
518 if (is_decimal())
519 {
520 if (m_num == 0 || m_den < powten(max_places))
521 return *this; // Nothing to do.
522 /* See if we can reduce m_num to fit in max_places */
523 auto excess = m_den / powten(max_places);
524 if (m_num % excess)
525 {
526 std::ostringstream msg;
527 msg << "GncNumeric " << *this
528 << " could not be represented in " << max_places
529 << " decimal places without rounding.\n";
530 throw std::range_error(msg.str());
531 }
532 return GncNumeric(m_num / excess, powten(max_places));
533 }
534 GncRational rr(*this);
535 rr = rr.convert<RoundType::never>(powten(max_places)); //May throw
536 /* rr might have gotten reduced a bit too much; if so, put it back: */
537 unsigned int pwr{1};
538 for (; pwr <= max_places && !(rr.denom() % powten(pwr)); ++pwr);
539 auto reduce_to = powten(pwr);
540 GncInt128 rr_num(rr.num()), rr_den(rr.denom());
541 if (rr_den % reduce_to)
542 {
543 auto factor(reduce_to / rr.denom());
544 rr_num *= factor;
545 rr_den *= factor;
546 }
547 while (!rr_num.isZero() && rr_num > 9 && rr_den > 9 && rr_num % 10 == 0)
548 {
549 rr_num /= 10;
550 rr_den /= 10;
551 }
552 try
553 {
554 /* Construct from the parts to avoid the GncRational constructor's
555 * automatic rounding.
556 */
557 return {static_cast<int64_t>(rr_num), static_cast<int64_t>(rr_den)};
558 }
559 catch (const std::invalid_argument& err)
560 {
561 std::ostringstream msg;
562 msg << "GncNumeric " << *this
563 << " could not be represented as a decimal without rounding.\n";
564 throw std::range_error(msg.str());
565 }
566 catch (const std::overflow_error& err)
567 {
568 std::ostringstream msg;
569 msg << "GncNumeric " << *this
570 << " overflows when attempting to convert it to decimal.\n";
571 throw std::range_error(msg.str());
572 }
573 catch (const std::underflow_error& err)
574 {
575 std::ostringstream msg;
576 msg << "GncNumeric " << *this
577 << " underflows when attempting to convert it to decimal.\n";
578 throw std::range_error(msg.str());
579 }
580}
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 489 of file gnc-numeric.cpp.

490{
491 std::ostringstream out;
492 out << *this;
493 return out.str();
494}

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