// rational.cpp (c) 2005 adolfo@di-mare.com /** \file rational.cpp \brief Implementaciones para la clase \c "rational" \author Adolfo Di Mare \date 2005 - Why English names??? ==> http://www.di-mare.com/adolfo/binder/c01.htm#sc04 */ #include "rational.h" #include #include // isdigit() OPEN_namespace(ADH) // ADH_port.h ==> namespace ADH { USING_namespace(ADH); // ADH_port.h ==> using namespace ADH; /** Verifica la invariante de la clase \c rational. \par Rep Modelo de la clase: \code +---+ | 3 | <== m_num == numerador del número racional +---+ |134| <== m_den == denominador del número racional +---+ \endcode - http://www.di-mare.com/adolfo/binder/c03.htm#k1-Rep \remark Libera al programador de implementar el método \c Ok() - http://www.di-mare.com/adolfo/binder/c04.htm#sc11 */ bool check_ok( const rational& r ) { if ( &r != 0 ) { // Ok } else { /// - Invariante: ningún objeto puede estar almacenado en la posición nula. return false; } if ( r.m_den > 0 ) { // Ok } else { /// - Invariante: el denominador debe ser un número positivo. return false; } if (r.m_num == 0) { if ( r.m_den == 1 ) { /// - Invariante: el cero debe representarse con denominador igual a "1". return true; } else { return false; } } if ( mcd(r.m_num, r.m_den) == 1 ) { // Ok } else { /// - Invariante: el numerador y el denominador deben ser primos relativos. return false; } return true; } /** Verifica la invariante de la clase \c rational. \remark Esta implementación nos se le mete al Rep (casi siempre no es posible implementar una función como ésta). - http://www.di-mare.com/adolfo/binder/c03.htm#k1-Rep \remark Libera al programador de implementar el método \c Ok() - http://www.di-mare.com/adolfo/binder/c04.htm#sc11 */ bool check_ok_no_Rep( const rational& r ) { if ( &r != 0 ) { // Ok } else { /// - Invariante: ningún objeto puede estar almacenado en la posición nula. return false; } if ( r.den() > 0 ) { // Ok } else { /// - Invariante: el denominador debe ser un número positivo. return false; } if (r.num() == 0) { if ( r.den() == 1 ) { /// - Invariante: el cero debe representarse con denominador igual a "1". return true; } else { return false; } } if ( mcd(r.num(), r.den()) == 1 ) { // Ok } else { /// - Invariante: el numerador y el denominador deben ser primos relativos. return false; } return true; } // check_ok_no_Rep() /** Calcula el Máximo Común Divisor de los números \c "x" y \c "y". - mcd(x,y) >= 1 siempre. - MCD <==> GCD: Greatest Common Divisor . \pre (y != 0) \remark Se usa el algoritmo de Euclides para hacer el cálculo. \par Ejemplo: \code 2*3*5 == mcd( 2*2*2*2 * 3*3 * 5*5, 2*3*5 ) 30 == mcd( -3600, -30 ) \endcode */ long mcd(long x, long y) { long g = (x < 0 ? -x : x); // trabaja con valores positivos long r = (y < 0 ? -y : y); // "r" es el resto long temp; do { temp = r; r = g % r; g = temp; } while (0 != r); return g; } // mcd() /** Simplifica el numerador y el denomidador. - Transforma el número rational de manera que el numerador y el denominador sean primos relativos, asegurando además que el denominador es siempre positivo. - Si (m_num==0) ==> (m_den==1). - Simplifica la fracción para que \c m_num y \c m_den sean números primos relativos ie, mcd(m_num,m_den) == 1. - Asegura que \c m_den sea un número positivo. - Restaura la invariante de la clase \c rational. */ void rational::Simplify() { if (m_num == 0) { m_den = 1; } long divisor = mcd(m_num, m_den); if (divisor > 1) { // ==> (divisor != 0) m_num /= divisor; m_den /= divisor; } if (m_den < 0) { m_num = -m_num; m_den = -m_den; } } // rational::Simplify() /// Le suma a \c "*this" el valor de \c "otro". rational& rational::operator += (const rational& otro) { m_num = m_num * otro.m_den + m_den * otro.m_num; m_den *= otro.m_den; Simplify(); return *this; } // operator += /// Le resta a \c "*this" el valor de \c "otro". rational& rational::operator -= (const rational& otro) { long oldm_den = m_den; long oldm_num = m_num; long d = otro.m_den; long n = otro.m_num; m_den *= d; m_num = oldm_num * d - oldm_den * n; Simplify(); return *this; } // operator -= /// Multiplica \c "*a" por \c "b". rational & operator *= ( rational & a, const rational & b ) { a.set( a.num() * b.num(), a.den() * b.den() ); return a; } // operator *= /// Establece el valor de \c "*this" a partir de la hilera \c "nStr". /// \pre \c "nStr" debe estar escrita en el formato "[num/den]". rational& rational::fromString (const char* nStr) { char ch; // valor obtenido, caracter por caracter, de "nStr" bool es_positivo = true; // manejo de los signos + y - // se brinca todo hasta el primer dígito do { ch = *nStr; nStr++; if (ch == '-') { // cambia de signo es_positivo = !es_positivo; } } while (!isdigit(ch)); // se traga el numerador long num = 0; while (isdigit(ch)) { // convierte a decimal: izq --> der num = 10 * num + (ch-'0'); ch = *nStr; nStr++; } // se brinca los blancos después del numerador while (isspace(ch)) { ch = *nStr; nStr++; } long den; if (ch ==']') { // es un número entero den = 1; } else { do { // se brinca todo hasta el denominador ch = *nStr; nStr++; if (ch == '-') { es_positivo = !es_positivo; } } while (!isdigit(ch)); // se traga el denominador den = 0; while (isdigit(ch)) { den = 10 * den + (ch-'0'); ch = *nStr; nStr++; } // Ya no importa si aparece o no el ']' del final del número } // le cambia el signo, si hace falta if (! es_positivo) { num = -num; } set( num, den ); return *this; } /** Graba el valor de \c "r" en el flujo \c "COUT". - Graba el valor en el formato [num/den]. - En particular, este es el operador que se invoca cuando se usa, por ejemplo, este tipo de instrucción: \code cout << r << q; \endcode */ std::ostream& operator<< (std::ostream &COUT, const rational& r) { if ( r.m_den == 1 ) { // no hay parte fraccional return COUT << "[" << r.m_num << "]" ; } else { return COUT << "[" << r.m_num << "/" << r.m_den << "]" ; } } // operator << /** Lee del flujo de texto \c "CIN" el valor de \c "r". \pre El número rational debe haber sido escrito usando el formato "[r/den]", aunque es permisible usar algunos blancos. - Se termina de leer el valor sólo cuando encuentra \c "]". - [ -+-+-+-+- 4 / -- -+ -- 32 ] se lee como [1/8] */ std::istream& operator >> (std::istream &CIN, rational& r) { char ch; // valor leido, letra por letra, de "CIN" bool es_positivo = true; // manejo de los signos + y - // se brinca todo hasta el primer dígito do { CIN >> ch; if (ch == '-') { // cambia de signo es_positivo = !es_positivo; } } while (!isdigit(ch)); // se traga el numerador r.m_num = 0; while (isdigit(ch)) { // convierte a decimal: izq --> der r.m_num = 10 * r.m_num + (ch-'0'); CIN >> ch; } // se brinca los blancos después del numerador while (isspace(ch)) { CIN >> ch; } if (ch ==']') { // es un número entero r.m_den = 1; } else { do { // se brinca todo hasta el denominador CIN >> ch; if (ch == '-') { es_positivo = !es_positivo; } } while (!isdigit(ch)); // se traga el denominador r.m_den = 0; while (isdigit(ch)) { r.m_den = 10 * r.m_den + (ch-'0'); CIN >> ch; } // El programa se duerme si en el flujo de entrada // NO aparece el caracter delimitador final "]", // pues la lectura termina hasta encontrar el "]". while (ch != ']') { CIN >> ch; } } // corrección: Andrés Arias // le cambia el signo, si hace falta if (! es_positivo) { r.m_num = -r.m_num; } r.Simplify(); return CIN; /* no detecta errores... [1/0] lo lee y no se queja [ !#!#!$#@! 3/ aaaa 4 jajaja ] lo lee como 3/4 ... pero no se supone que el usuario cometa errores... */ } // operator >> /// \c "x+y". /// - Calcula y retorna la suma \c "x+y". rational operator + (const rational &x, const rational &y) { long res_num, res_den; res_den = x.m_den * y.m_den; res_num = x.m_num * y.m_den + x.m_den * y.m_num; return rational(res_num, res_den); } // operator + () /// \c "x-y". /// - Calcula y retorna la resta \c "x-y". rational operator - (const rational &x, const rational &y) { long res_num, res_den; res_den = x.m_den * y.m_den; res_num = x.m_num * y.m_den - x.m_den * y.m_num; return rational(res_num, res_den); } // operator - () /// \c "x*y". /// - Calcula y retorna la multiplicación \c "x*y". rational operator * (const rational &x, const rational &y) { long res_num, res_den; res_num = x.m_num * y.m_num; res_den = x.m_den * y.m_den; return rational(res_num, res_den); } // operator * () /// \c "x/y". /// - Calcula y retorna la división \c "x/y". /// \pre y != 0 rational operator / (const rational &x, const rational &y) { long res_num, res_den; if (0 != y.m_num) { res_num = x.m_num * y.m_den; res_den = x.m_den * y.m_num; } return rational(res_num, res_den); } // operator / () CLOSE_namespace(ADH) // ADH_port.h ==> } // namespace ADH // EOF: rational.cpp