/* GNU Ocrad - Optical Character Recognition program
Copyright (C) 2003, 2004, 2005, 2006, 2007 Antonio Diaz Diaz.
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 3 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, see .
*/
#include
#include
#include
#include
#include
#include "rational.h"
namespace {
int gcd( int n, int m ) throw() // Greatest Common Divisor
{
if( n < 0 ) n = -n;
if( m < 0 ) m = -m;
while( true )
{
if( m ) n %= m; else return n;
if( n ) m %= n; else return m;
}
}
int lcm( int n, int m ) throw() // Least Common Multiple
{
if( !n || !m ) return 0;
n /= gcd( n, m ); n *= m; // lcm( n, m ) == ( n * m ) / gcd( n, m )
if( n < 0 ) n = -n;
return n;
}
} // end namespace
void Rational::normalize() throw()
{
if( num == 0 ) { den = 1; return; }
if( den == 0 )
{ den = 1; if( num > 0 ) num = INT_MAX; else num = INT_MIN; return; }
if( den < 0 ) { num = -num; den = -den; }
if( den != 1 )
{
const int tmp = gcd( num, den );
num /= tmp; den /= tmp;
}
}
Rational & Rational::operator+=( const Rational & r ) throw()
{
const int tmp = lcm( den, r.den );
num = ( ( tmp / den ) * num ) + ( ( tmp / r.den ) * r.num );
den = tmp;
return *this;
}
Rational & Rational::operator*=( const Rational & r ) throw()
{
const int tmp1 = gcd( num, r.den );
const int tmp2 = gcd( r.num, den );
num = ( num / tmp1 ) * ( r.num / tmp2 );
den = ( den / tmp2 ) * ( r.den / tmp1 );
normalize(); // overflow may break the invariant
return *this;
}
Rational & Rational::operator/=( const Rational & r ) throw()
{
if( num )
{
if( r.num == 0 ) den = 0;
else
{
const int tmp1 = gcd( num, r.num );
const int tmp2 = gcd( den, r.den );
num = ( num / tmp1 ) * ( r.den / tmp2 );
den = ( den / tmp2 ) * ( r.num / tmp1 );
}
}
normalize();
return *this;
}
bool Rational::operator<( const Rational & r ) const throw()
{
if( num >= 0 && r.num <= 0 ) return false;
if( ( num <= 0 && r.num > 0 ) || ( num < 0 && r.num >= 0 ) ) return true;
int tmp1 = num / den, tmp2 = r.num / r.den; // both values have same sign
if( tmp1 != tmp2 ) return ( tmp1 < tmp2 );
tmp1 = gcd( num, r.num ); // values differ by less than 1
tmp2 = gcd( r.den, den );
return ( ( num / tmp1 ) * ( r.den / tmp2 ) < ( den / tmp2 ) * ( r.num / tmp1 ) );
}
Rational Rational::inverse() const throw()
{
Rational tmp( den );
if( num == 0 ) { tmp.num = INT_MAX; tmp.den = 1; }
else if( num < 0 ) { tmp.num = -den; tmp.den = -num; }
else tmp.den = num;
return tmp;
}
int Rational::round() const throw()
{
int result = num / den, rest = std::abs( num ) % den;
if( rest > 0 && rest >= den - rest )
{ if( num >= 0 ) ++result; else --result; }
return result;
}
// Recognized formats: 123 123/456 123.456 .123 12% 12/3% 12.3% .12%
// Values may be preceded by an optional '+' or '-' sign.
// Returns the number of chars read, or 0 if error.
//
int Rational::parse( const char * ptr ) throw()
{
if( !ptr || !*ptr ) return 0;
int n = 0, d = 1, c = 0;
bool minus = false;
while( std::isspace( ptr[c] ) ) ++c;
if( ptr[c] == '+' ) ++c;
else if( ptr[c] == '-' ) { ++c; minus = true; }
if( !std::isdigit( ptr[c] ) && ptr[c] != '.' ) return 0;
while( std::isdigit( ptr[c] ) )
{
if( ( INT_MAX - (ptr[c] - '0') ) / 10 < n ) return 0;
n = (n * 10) + (ptr[c] - '0'); ++c;
}
if( ptr[c] == '.' )
{
++c; if( !std::isdigit( ptr[c] ) ) return 0;
while( std::isdigit( ptr[c] ) )
{
if( ( INT_MAX - (ptr[c] - '0') ) / 10 < n || INT_MAX / 10 < d ) return 0;
n = (n * 10) + (ptr[c] - '0'); d *= 10; ++c;
}
}
else if( ptr[c] == '/' )
{
++c; d = 0;
while( std::isdigit( ptr[c] ) )
{
if( ( INT_MAX - (ptr[c] - '0') ) / 10 < d ) return 0;
d = (d * 10) + (ptr[c] - '0'); ++c;
}
if( d == 0 ) return 0;
}
if( ptr[c] == '%' )
{
++c;
if( n % 100 == 0 ) n /= 100;
else if( n % 10 == 0 && INT_MAX / 10 >= d ) { n /= 10; d *= 10; }
else if( INT_MAX / 100 >= d ) d *= 100;
else return 0;
}
if( minus ) n = -n;
num = n; den = d; normalize();
return c;
}
// Returns the fraction "num/den" as a floating point with "prec" decimals.
// If 'prec' is negative, only the needed decimals are shown.
//
const std::string Rational::to_decimal( const int iwidth, int prec ) const throw()
{
std::string s;
if( den == 0 )
{
if( num == 0 ) s = "NAN"; else if( num > 0 ) s = "+INF"; else s = "-INF";
return s;
}
bool negative = false, trunc = false;
int ipart = num / den;
if( ipart < 0 ) { ipart = -ipart; negative = true; }
if( prec < 0 ) { prec = -prec; trunc = true; }
do { s += '0' + ( ipart % 10 ); ipart /= 10; } while( ipart > 0 );
if( negative ) s += '-';
if( iwidth > (int)s.size() ) s.append( iwidth - s.size(), ' ' );
std::reverse( s.begin(), s.end() );
long long rest = std::abs( num ) % den;
if( prec > 0 && ( rest > 0 || !trunc ) )
{
s += '.';
while( prec > 0 && ( rest > 0 || !trunc ) )
{ rest *= 10; s += '0' + ( rest / den ); rest %= den; --prec; }
}
return s;
}