/**
* Clase que modeliza números racionales.
* InvarianteClaseRacional: el denominador de un objeto bien formado
* siempre es distinto de cero
*/
public class Racional implements Cloneable {
private long numerador,denominador;
/**
* Crea un nuevo número racional.
* @param i numerador del número racional
* @param j denominador del número racional
* @throws RacionalException cuando j es 0.
*
* Postc: referencia a un nuevo objeto Racional bien formado o excepción.
*/
public Racional(long i, long j) throws RacionalException{
if (j == 0)
throw new RacionalException("Número racional imposible");
this.numerador = i;
this.denominador = j;
}
/**
* Crea un nuevo número racional con denominador 1.
* @param i numerador
* @throws RacionalException
*
* Postc: un nuevo objeto Racional bien formado.
*/
public Racional (long i){
this(i,1);
}
/**
* Suma el número racional actual con el parámetro recibido.
* @param r el segundo sumando a sumar
* @return devuelve un nuevo número racional bien formado que es
* suma del número racional actual y el parámetro pasado siempre
* que no haya desbordamiento.
*
* Prec: No hay desbordamiento en ninguna operación aritmética utilizada
*/
public Racional suma (Racional r){
assert(!Racional.habraOverflowAlMultiplicar(this.numerador, r.denominador)
&& !Racional.habraOverflowAlMultiplicar(this.denominador, r.numerador)
&& !Racional.habraOverflowAlMultiplicar(this.denominador, r.denominador)
&& !Racional.habraOverflowAlSumar(this.numerador*r.denominador, this.denominador*r.numerador));
return new Racional(this.numerador*r.denominador + this.denominador*r.numerador,
this.denominador*r.denominador);
}
/**
* Diferencia entre el número racional actual y el parámetro recibido, en
* ese orden.
* @param r número racional sustraendo en la resta.
* @return devuelve un nuevo número racional bien formado que es
* la diferencia entre el número racional actual y el parámetro pasado en ese
* orden siempre que no haya desbordamiento.
*
* Prec: No hay desbordamiento en ninguna operación aritmética utilizada
*/
public Racional diferencia (Racional r){
assert(!Racional.habraOverflowAlMultiplicar(this.numerador, r.denominador)
&& !Racional.habraOverflowAlMultiplicar(this.denominador, r.numerador)
&& !Racional.habraOverflowAlMultiplicar(this.denominador, r.denominador)
&& !Racional.habraOverflowAlSumar(this.numerador*r.denominador, -this.denominador*r.numerador));
return new Racional(this.numerador*r.denominador - this.denominador*r.numerador,
this.denominador*r.denominador);
}
/**
* Producto entre el número racional actual y el parámetro recibido.
* @param r número racional a multiplicar con el actual.
* @return devuelve un nuevo número racional bien formado que es
* el producto entre el número racional actual y el parámetro pasado,
* siempre que no haya desbordamiento.
*
* Prec: Math.max(Math.abs(this.numerador*r.numerador),
* Math.abs(this.denominador*r.denominador)) < Long.MAX_VALUE
*/
public Racional multiplicacion (Racional r){
assert(!Racional.habraOverflowAlMultiplicar(this.numerador, r.numerador)
&& !Racional.habraOverflowAlMultiplicar(this.denominador, r.denominador));
return new Racional(this.numerador*r.numerador,
this.denominador*r.denominador);
}
/**
* División entre el número racional actual y el parámetro recibido.
* @param r número racional divisor.
* @return devuelve un nuevo número racional bien formado que es
* el cociente entre el número racional actual (dividendo) y el parámetro pasado.
*
* Prec: No hay desbordamiento en las operaciones aritméticas usadas.
*
* Postc: Un nuevo racional resultado de la división, si no hay desbordamiento,
* o excepción
*/
public Racional division (Racional r) throws RacionalException {
assert(!Racional.habraOverflowAlMultiplicar(this.numerador, r.denominador)
&& !Racional.habraOverflowAlMultiplicar(this.denominador, r.numerador));
try {
long i = this.denominador*r.numerador;
return new Racional(this.numerador*r.denominador,i);
} catch (Exception e) {
throw new RacionalException("División por cero");
}
}
/**
* Devuelve una cadena representando el número racional en forma
* irreducible.
*/
@Override
public String toString(){
this.simplifica();
if (this.denominador==1)
return ""+this.numerador;
return ""+this.numerador + "/" + this.denominador;
}
/**
* Devuelve true si el número racional es igual al objeto pasado,
* que debe ser un racional y equivalente a él y false en otro caso.
*/
@Override
public boolean equals (Object o){
if (o instanceof Racional){
Racional aux =(Racional)o;
this.simplifica();
aux.simplifica();
return this.numerador==aux.numerador && this.denominador==aux.denominador;
}
return false;
}
@Override
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
/**
* Devuelve verdadero si el número racional actual es menor que r
* @param r un número racional a comparar.
* @return verdadero si this < r como números racionales.
*
* Prec: la versión irreducible de this y r deben verificar que
* no hay desbordamiento en las operaciones aritméticas utilizadas
*/
public boolean esMenorQue (Racional r){
this.simplifica();
r.simplifica();
assert(!Racional.habraOverflowAlMultiplicar(this.numerador, r.denominador)
&& !Racional.habraOverflowAlMultiplicar(this.denominador, r.numerador));
return this.numerador*r.denominador < this.denominador*r.numerador;
}
/**
* Devuelve verdadero si el número racional actual es mayor que r
* @param r un número racional a comparar.
* @return verdadero si this > r como números racionales.
*
* Prec: la versión irreducible de this y r deben verificar que no hay
* desbordamiento en las operaciones aritméticas utilizadas
*/
public boolean esMayorQue (Racional r){
this.simplifica();
r.simplifica();
assert(!Racional.habraOverflowAlMultiplicar(this.numerador, r.denominador)
&& !Racional.habraOverflowAlMultiplicar(this.denominador, r.numerador));
return this.numerador*r.denominador > this.denominador*r.numerador;
}
/**
* Calcula el máximo común divisor de dos enteros long.
* El máximo común divisor es el mayor entero que divide de forma exacta
* a los dos números. Siempre será un número positivo y 1 si los dos enteros
* son primos relativos.
* @param a Un long
* @param b Un long
* @return El máximo común divisor de a y b.
*
* Prec: a != 0 || b != 0
*/
private long mcd (long a, long b){
assert(a!=0 || b !=0);
long aa=Math.abs(a);
long bb=Math.abs(b);
if (aa < bb){ long aux = aa; aa = bb; bb =aux;}
while (bb != 0){
long r = aa % bb;
aa = bb;
bb = r;
}
return aa;
}
/**
* Simplifica un número racional de forma que cumple:
* this.denominador > 0 && mcd(this.numerador,this.denominador)==1
*/
private void simplifica(){
if (Math.signum(numerador) == Math.signum(denominador)){
if (Math.signum(denominador) < 0){
this.numerador = -this.numerador;
this.denominador = -this.denominador;
}
}
else
if (Math.signum(denominador) < 0) {
this.denominador = -this.denominador;
this.numerador = -this.numerador;
}
// Postc: el signo está en el numerador solamente
long mcd = mcd(Math.abs(this.numerador), this.denominador);
this.numerador = this.numerador / mcd;
this.denominador = this.denominador / mcd;
// Ya está en forma irreducible
assert(this.denominador > 0 && mcd(this.numerador,this.denominador)==1);
}
/** Detecta si hay desbordamiento al multiplicar dos números long.
*
* @param a
* @param b
* @return true si hay desbordamiento al calcular a*b, false en otro caso.
*/
private static boolean habraOverflowAlMultiplicar(long a, long b) {
// Casos por el signo de a y b
if (a == 0 || b == 0) {
return false;
} else if (a > 0 && b > 0) {
return a > Long.MAX_VALUE / b;
} else if (b < 0 && a < 0) {
return a < Long.MAX_VALUE / b;
} else {
if (b > 0) { // Observar que Long.MIN_VALUE / -1 provoca overflow.
// De ahí la necesidad de estas dos casos separados.
return a < Long.MIN_VALUE / b;
} else { // a > 0
return b < Long.MIN_VALUE / a;
}
}
}
/** Detecta si hay desbordamiento al sumar dos números long.
*
* @param a
* @param b
* @return true si hay desbordamiento al calcular a+b, false en otro caso.
*/
private static boolean habraOverflowAlSumar(long a, long b) {
if (a > 0 && b > 0) {
return a > Long.MAX_VALUE - b;
} else if (a < 0 && b < 0) {
return a < Long.MIN_VALUE - b;
}
return false;
}
}