Skip to content

Commit

Permalink
Rename GncNumeric to GncRational and move to its own files.
Browse files Browse the repository at this point in the history
  • Loading branch information
jralls committed Dec 4, 2014
1 parent 79938ca commit 8e429f7
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 276 deletions.
1 change: 1 addition & 0 deletions po/POTFILES.in
Expand Up @@ -430,6 +430,7 @@ src/import-export/qif-imp/gnc-plugin-qif-import.c
[type: gettext/gsettings]src/import-export/qif-imp/gschemas/org.gnucash.dialogs.import.qif.gschema.xml.in.in
src/libqof/qof/gnc-date.cpp
src/libqof/qof/gnc-numeric.cpp
src/libqof/qof/gnc-rational.cpp
src/libqof/qof/guid.cpp
src/libqof/qof/kvp_frame.cpp
src/libqof/qof/kvp-util.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/libqof/qof/Makefile.am
Expand Up @@ -25,6 +25,7 @@ AM_CPPFLAGS = \
libgnc_qof_la_SOURCES = \
gnc-date.cpp \
gnc-numeric.cpp \
gnc-rational.cpp \
guid.cpp \
kvp-util.cpp \
kvp_frame.cpp \
Expand All @@ -51,6 +52,7 @@ qofinclude_HEADERS = \
gnc-date-p.h \
gnc-date.h \
gnc-numeric.h \
gnc-rational.hpp \
guid.h \
kvp-util-p.h \
kvp-util.h \
Expand Down
280 changes: 4 additions & 276 deletions src/libqof/qof/gnc-numeric.cpp
Expand Up @@ -39,14 +39,16 @@ extern "C"

#include "gnc-numeric.h"
#include "qofint128.hpp"
#include "gnc-rational.hpp"

using GncNumeric = GncRational;

/* static short module = MOD_ENGINE; */
static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000, 10000000000,
100000000000, 1000000000000, 10000000000000,
100000000000000, 10000000000000000,
100000000000000000, 1000000000000000000};
#define POWTEN_OVERFLOW -5
static const int POWTEN_OVERFLOW {-5};

static inline gint64
powten (int exp)
Expand All @@ -55,281 +57,7 @@ powten (int exp)
return POWTEN_OVERFLOW;
return exp < 0 ? -pten[-exp] : pten[exp];
}
struct GncNumeric; //Forward declaration

struct GncDenom
{
GncDenom (GncNumeric& a, GncNumeric& b, int64_t spec, uint how) noexcept;
void reduce (const GncNumeric& a) noexcept;
QofInt128 get () const noexcept { return m_value; }

enum class RoundType : int
{
floor = GNC_HOW_RND_FLOOR,
ceiling = GNC_HOW_RND_CEIL,
truncate = GNC_HOW_RND_TRUNC,
promote = GNC_HOW_RND_PROMOTE,
half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
half_up = GNC_HOW_RND_ROUND_HALF_UP,
bankers = GNC_HOW_RND_ROUND,
never = GNC_HOW_RND_NEVER,
};
enum class DenomType : int
{
exact = GNC_HOW_DENOM_EXACT,
reduce = GNC_HOW_DENOM_REDUCE,
lcd = GNC_HOW_DENOM_LCD,
fixed = GNC_HOW_DENOM_FIXED,
sigfigs = GNC_HOW_DENOM_SIGFIG,
};

QofInt128 m_value;
RoundType m_round;
DenomType m_type;
bool m_auto;
uint m_sigfigs;
GNCNumericErrorCode m_error;
};

struct GncNumeric
{
GncNumeric (gnc_numeric n) noexcept;
GncNumeric (QofInt128 num, QofInt128 den) noexcept;
operator gnc_numeric() const noexcept;
void round (GncDenom& denom) noexcept;

QofInt128 m_num;
QofInt128 m_den;
GNCNumericErrorCode m_error;
};

GncNumeric::GncNumeric (gnc_numeric n) noexcept :
m_num (n.num), m_den (n.denom), m_error {GNC_ERROR_OK}
{
if (m_den.isNeg())
{
m_num *= -m_den;
m_den = 1;
}
}

GncNumeric::GncNumeric (QofInt128 num, QofInt128 den) noexcept :
m_num (num), m_den (den), m_error {}
{
}

GncNumeric::operator gnc_numeric () const noexcept
{
if (m_num.isOverflow() || m_num.isNan() ||
m_den.isOverflow() || m_den.isNan())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
if (m_error != GNC_ERROR_OK)
return gnc_numeric_error (m_error);
try
{
return {static_cast<int64_t>(m_num), static_cast<int64_t>(m_den)};
}
catch (std::overflow_error)
{
return gnc_numeric_error (GNC_ERROR_OVERFLOW);
}
}

void
GncNumeric::round (GncDenom& denom) noexcept
{
denom.reduce (*this);
if (m_error == GNC_ERROR_OK && denom.m_error != GNC_ERROR_OK)
{
m_error = denom.m_error;
return;
}
QofInt128 new_den = denom.get();
if (new_den == 0) new_den = m_den;
if (!(m_num.isBig() || new_den.isBig() ))
{
if (m_den == new_den)
return;

if (m_num.isZero())
{
m_den = new_den;
return;
}
}
QofInt128 new_num {}, remainder {};
if (new_den.isNeg())
m_num.div(-new_den * m_den, new_num, remainder);
else
(m_num * new_den).div(m_den, new_num, remainder);

if (remainder.isZero() && !(new_num.isBig() || new_den.isBig()))
{
m_num = new_num;
m_den = new_den;
return;
}

if (new_num.isBig() || new_den.isBig())
{
if (!denom.m_auto)
{
m_error = GNC_ERROR_OVERFLOW;
return;
}

/* First, try to reduce it */
QofInt128 gcd = new_num.gcd(new_den);
new_num /= gcd;
new_den /= gcd;
remainder /= gcd;

/* if that didn't work, shift both num and den down until neither is "big", then
* fall through to rounding.
*/
while (new_num && new_num.isBig() && new_den && new_den.isBig())
{
new_num >>= 1;
new_den >>= 1;
remainder >>= 1;
}
}

/* If we got here, then we can't exactly represent the rational with
* new_denom. We must either round or punt.
*/
switch (denom.m_round)
{
case GncDenom::RoundType::never:
m_error = GNC_ERROR_REMAINDER;
return;
case GncDenom::RoundType::floor:
if (new_num.isNeg()) ++new_num;
break;
case GncDenom::RoundType::ceiling:
if (! new_num.isNeg()) ++new_num;
break;
case GncDenom::RoundType::truncate:
break;
case GncDenom::RoundType::promote:
new_num += new_num.isNeg() ? -1 : 1;
break;
case GncDenom::RoundType::half_down:
if (new_den.isNeg())
{
if (remainder * 2 > m_den * new_den)
new_num += new_num.isNeg() ? -1 : 1;
}
else if (remainder * 2 > m_den)
new_num += new_num.isNeg() ? -1 : 1;
break;
case GncDenom::RoundType::half_up:
if (new_den.isNeg())
{
if (remainder * 2 >= m_den * new_den)
new_num += new_num.isNeg() ? -1 : 1;
}
else if (remainder * 2 >= m_den)
new_num += new_num.isNeg() ? -1 : 1;
break;
case GncDenom::RoundType::bankers:
if (new_den.isNeg())
{
if (remainder * 2 > m_den * -new_den ||
(remainder * 2 == m_den * -new_den && new_num % 2))
new_num += new_num.isNeg() ? -1 : 1;
}
else
{
if (remainder * 2 > m_den ||
(remainder * 2 == m_den && new_num % 2))
new_num += new_num.isNeg() ? -1 : 1;
}
break;
}
m_num = new_num;
m_den = new_den;
return;
}

GncDenom::GncDenom (GncNumeric& a, GncNumeric& b,
int64_t spec, uint how) noexcept :
m_value (spec),
m_round (static_cast<GncDenom::RoundType>(how & GNC_NUMERIC_RND_MASK)),
m_type (static_cast<GncDenom::DenomType>(how & GNC_NUMERIC_DENOM_MASK)),
m_auto (spec == GNC_DENOM_AUTO),
m_sigfigs ((how & GNC_NUMERIC_SIGFIGS_MASK) >> 8),
m_error (GNC_ERROR_OK)

{

if (!m_auto)
return;
switch (m_type)
{
case DenomType::fixed:
if (a.m_den == b.m_den)
{
m_value = a.m_den;
}
else if (b.m_num == 0)
{
m_value = a.m_den;
b.m_den = a.m_den;
}
else if (a.m_num == 0)
{
m_value = b.m_den;
a.m_den = b.m_den;
}
else
{
m_error = GNC_ERROR_DENOM_DIFF;
}
m_auto = false;
break;

case DenomType::lcd:
m_value = a.m_den.lcm(b.m_den);
m_auto = false;
break;
default:
break;

}
}

void
GncDenom::reduce (const GncNumeric& a) noexcept
{
if (!m_auto)
return;
switch (m_type)
{
default:
break;
case DenomType::reduce:
m_value = a.m_den / a.m_num.gcd(a.m_den);
break;

case DenomType::sigfigs:
QofInt128 val {};
if (a.m_num.abs() > a.m_den)
val = a.m_num.abs() / a.m_den;
else
val = a.m_den / a.m_num.abs();
uint digits {};
while (val >= 10)
{
++digits;
val /= 10;
}
m_value = (a.m_num.abs() > a.m_den ? powten (m_sigfigs - digits - 1) :
powten (m_sigfigs + digits));
m_auto = false;
break;
}
}

/* =============================================================== */
/* This function is small, simple, and used everywhere below,
Expand Down

0 comments on commit 8e429f7

Please sign in to comment.