mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Restructure the src directory
It is split into - /libgnucash (for the non-gui bits) - /gnucash (for the gui) - /common (misc source files used by both) - /bindings (currently only holds python bindings) This is the first step in restructuring the code. It will need much more fine tuning later on.
This commit is contained in:
319
libgnucash/engine/gnc-rational.cpp
Normal file
319
libgnucash/engine/gnc-rational.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
/********************************************************************
|
||||
* gnc-rational.hpp - A rational number library *
|
||||
* Copyright 2014 John Ralls <jralls@ceridwen.us> *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
||||
* *
|
||||
*******************************************************************/
|
||||
|
||||
#include <sstream>
|
||||
#include "gnc-rational.hpp"
|
||||
#include "gnc-numeric.hpp"
|
||||
|
||||
|
||||
GncRational::GncRational(GncNumeric n) noexcept :
|
||||
m_num(n.num()), m_den(n.denom())
|
||||
{
|
||||
if (m_den.isNeg())
|
||||
{
|
||||
m_num *= -m_den;
|
||||
m_den = 1;
|
||||
}
|
||||
}
|
||||
|
||||
GncRational::GncRational (gnc_numeric n) noexcept :
|
||||
m_num (n.num), m_den (n.denom)
|
||||
{
|
||||
if (m_den.isNeg())
|
||||
{
|
||||
m_num *= -m_den;
|
||||
m_den = 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
GncRational::valid() const noexcept
|
||||
{
|
||||
if (m_num.valid() && m_den.valid())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
GncRational::is_big() const noexcept
|
||||
{
|
||||
if (m_num.isBig() || m_den.isBig())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
GncRational::operator gnc_numeric () const noexcept
|
||||
{
|
||||
if (!valid())
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
GncRational
|
||||
GncRational::operator-() const noexcept
|
||||
{
|
||||
return GncRational(-m_num, m_den);
|
||||
}
|
||||
|
||||
GncRational
|
||||
GncRational::inv () const noexcept
|
||||
{
|
||||
if (m_num == 0)
|
||||
return *this;
|
||||
if (m_num < 0)
|
||||
return GncRational(-m_den, -m_num);
|
||||
return GncRational(m_den, m_num);
|
||||
}
|
||||
|
||||
GncRational
|
||||
GncRational::abs() const noexcept
|
||||
{
|
||||
if (m_num < 0)
|
||||
return -*this;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
GncRational::operator+=(GncRational b)
|
||||
{
|
||||
GncRational new_val = *this + b;
|
||||
*this = std::move(new_val);
|
||||
}
|
||||
|
||||
void
|
||||
GncRational::operator-=(GncRational b)
|
||||
{
|
||||
GncRational new_val = *this - b;
|
||||
*this = std::move(new_val);
|
||||
}
|
||||
|
||||
void
|
||||
GncRational::operator*=(GncRational b)
|
||||
{
|
||||
GncRational new_val = *this * b;
|
||||
*this = std::move(new_val);
|
||||
}
|
||||
|
||||
void
|
||||
GncRational::operator/=(GncRational b)
|
||||
{
|
||||
GncRational new_val = *this / b;
|
||||
*this = std::move(new_val);
|
||||
}
|
||||
|
||||
int
|
||||
GncRational::cmp(GncRational b)
|
||||
{
|
||||
if (m_den == b.denom())
|
||||
{
|
||||
auto b_num = b.num();
|
||||
return m_num < b_num ? -1 : b_num < m_num ? 1 : 0;
|
||||
}
|
||||
auto gcd = m_den.gcd(b.denom());
|
||||
GncInt128 a_num(m_num * b.denom() / gcd), b_num(b.num() * m_den / gcd);
|
||||
return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
|
||||
}
|
||||
|
||||
GncRational::round_param
|
||||
GncRational::prepare_conversion (GncInt128 new_denom) const
|
||||
{
|
||||
if (new_denom == m_den || new_denom == GNC_DENOM_AUTO)
|
||||
return {m_num, m_den, 0};
|
||||
GncRational conversion(new_denom, m_den);
|
||||
auto red_conv = conversion.reduce();
|
||||
GncInt128 old_num(m_num);
|
||||
auto new_num = old_num * red_conv.num();
|
||||
if (new_num.isOverflow())
|
||||
throw std::overflow_error("Conversion overflow");
|
||||
auto rem = new_num % red_conv.denom();
|
||||
new_num /= red_conv.denom();
|
||||
return {new_num, red_conv.denom(), rem};
|
||||
}
|
||||
|
||||
GncInt128
|
||||
GncRational::sigfigs_denom(unsigned figs) const noexcept
|
||||
{
|
||||
if (m_num == 0)
|
||||
return 1;
|
||||
|
||||
auto num_abs = m_num.abs();
|
||||
bool not_frac = num_abs > m_den;
|
||||
int64_t val{ not_frac ? num_abs / m_den : m_den / num_abs };
|
||||
unsigned digits{};
|
||||
while (val >= 10)
|
||||
{
|
||||
++digits;
|
||||
val /= 10;
|
||||
}
|
||||
return not_frac ? powten(figs - digits - 1) : powten(figs + digits);
|
||||
}
|
||||
|
||||
GncRational
|
||||
GncRational::reduce() const
|
||||
{
|
||||
auto gcd = m_den.gcd(m_num);
|
||||
if (gcd.isNan() || gcd.isOverflow())
|
||||
throw std::overflow_error("Reduce failed, calculation of gcd overflowed.");
|
||||
return GncRational(m_num / gcd, m_den / gcd);
|
||||
}
|
||||
|
||||
GncRational
|
||||
GncRational::round_to_numeric() const
|
||||
{
|
||||
unsigned int ll_bits = GncInt128::legbits;
|
||||
if (m_num.isZero())
|
||||
return GncRational(); //Default constructor makes 0/1
|
||||
if (!(m_num.isBig() || m_den.isBig()))
|
||||
return *this;
|
||||
if (m_num.abs() > m_den)
|
||||
{
|
||||
auto quot(m_num / m_den);
|
||||
if (quot.isBig())
|
||||
{
|
||||
std::ostringstream msg;
|
||||
msg << " Cannot be represented as a "
|
||||
<< "GncNumeric. Its integer value is too large.\n";
|
||||
throw std::overflow_error(msg.str());
|
||||
}
|
||||
GncRational new_v;
|
||||
while (new_v.num().isZero())
|
||||
{
|
||||
try
|
||||
{
|
||||
new_v = convert<RoundType::half_down>(m_den / (m_num.abs() >> ll_bits));
|
||||
if (new_v.is_big())
|
||||
{
|
||||
--ll_bits;
|
||||
new_v = GncRational();
|
||||
}
|
||||
}
|
||||
catch(const std::overflow_error& err)
|
||||
{
|
||||
--ll_bits;
|
||||
}
|
||||
}
|
||||
return new_v;
|
||||
}
|
||||
auto quot(m_den / m_num);
|
||||
if (quot.isBig())
|
||||
return GncRational(); //Smaller than can be represented as a GncNumeric
|
||||
GncRational new_v;
|
||||
while (new_v.num().isZero())
|
||||
{
|
||||
auto divisor = m_den >> ll_bits;
|
||||
if (m_num.isBig())
|
||||
{
|
||||
GncInt128 oldnum(m_num), num, rem;
|
||||
oldnum.div(divisor, num, rem);
|
||||
auto den = m_den / divisor;
|
||||
num += rem * 2 >= den ? 1 : 0;
|
||||
if (num.isBig() || den.isBig())
|
||||
{
|
||||
--ll_bits;
|
||||
continue;
|
||||
}
|
||||
GncRational new_rational(num, den);
|
||||
return new_rational;
|
||||
}
|
||||
new_v = convert<RoundType::half_down>(m_den / divisor);
|
||||
if (new_v.is_big())
|
||||
{
|
||||
--ll_bits;
|
||||
new_v = GncRational();
|
||||
}
|
||||
}
|
||||
return new_v;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator+(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator+ called with out-of-range operand.");
|
||||
GncInt128 lcm = a.denom().lcm(b.denom());
|
||||
GncInt128 num(a.num() * lcm / a.denom() + b.num() * lcm / b.denom());
|
||||
if (!(lcm.valid() && num.valid()))
|
||||
throw std::overflow_error("Operator+ overflowed.");
|
||||
GncRational retval(num, lcm);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator-(GncRational a, GncRational b)
|
||||
{
|
||||
GncRational retval = a + (-b);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator*(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator* called with out-of-range operand.");
|
||||
GncInt128 num (a.num() * b.num()), den(a.denom() * b.denom());
|
||||
if (!(num.valid() && den.valid()))
|
||||
throw std::overflow_error("Operator* overflowed.");
|
||||
GncRational retval(num, den);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator/(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator/ called with out-of-range operand.");
|
||||
auto a_num = a.num(), b_num = b.num(), a_den = a.denom(), b_den = b.denom();
|
||||
if (b_num == 0)
|
||||
throw std::underflow_error("Divide by 0.");
|
||||
if (b_num.isNeg())
|
||||
{
|
||||
a_num = -a_num;
|
||||
b_num = -b_num;
|
||||
}
|
||||
|
||||
/* q = (a_num * b_den)/(b_num * a_den). If a_den == b_den they cancel out
|
||||
* and it's just a_num/b_num.
|
||||
*/
|
||||
if (a_den == b_den)
|
||||
return GncRational(a_num, b_num);
|
||||
|
||||
/* Protect against possibly preventable overflow: */
|
||||
if (a_num.isBig() || a_den.isBig() ||
|
||||
b_num.isBig() || b_den.isBig())
|
||||
{
|
||||
GncInt128 gcd = b_den.gcd(a_den);
|
||||
b_den /= gcd;
|
||||
a_den /= gcd;
|
||||
}
|
||||
|
||||
GncInt128 num(a_num * b_den), den(a_den * b_num);
|
||||
if (!(num.valid() && den.valid()))
|
||||
throw std::overflow_error("Operator/ overflowed.");
|
||||
return GncRational(num, den);
|
||||
}
|
||||
Reference in New Issue
Block a user