Estoy tratando de trabajar con fracciones en Java.
Quiero implementar funciones aritméticas. Para esto, primero necesitaré una forma de normalizar las funciones. Sé que no puedo sumar 1/6 y 1/2 hasta que tenga un denominador común. Tendré que agregar 1/6 y 3/6. Un enfoque ingenuo me haría agregar 2/12 y 6/12 y luego reducir. ¿Cómo puedo lograr un denominador común con la menor penalización por desempeño? ¿Qué algoritmo es mejor para esto?
Versión 8 (gracias a hstoerr ):
Las mejoras incluyen:
- el método equals () ahora es consistente con el método compareTo ()
final class Fraction extends Number {
private int numerator;
private int denominator;
public Fraction(int numerator, int denominator) {
if(denominator == 0) {
throw new IllegalArgumentException("denominator is zero");
}
if(denominator < 0) {
numerator *= -1;
denominator *= -1;
}
this.numerator = numerator;
this.denominator = denominator;
}
public Fraction(int numerator) {
this.numerator = numerator;
this.denominator = 1;
}
public int getNumerator() {
return this.numerator;
}
public int getDenominator() {
return this.denominator;
}
public byte byteValue() {
return (byte) this.doubleValue();
}
public double doubleValue() {
return ((double) numerator)/((double) denominator);
}
public float floatValue() {
return (float) this.doubleValue();
}
public int intValue() {
return (int) this.doubleValue();
}
public long longValue() {
return (long) this.doubleValue();
}
public short shortValue() {
return (short) this.doubleValue();
}
public boolean equals(Fraction frac) {
return this.compareTo(frac) == 0;
}
public int compareTo(Fraction frac) {
long t = this.getNumerator() * frac.getDenominator();
long f = frac.getNumerator() * this.getDenominator();
int result = 0;
if(t>f) {
result = 1;
}
else if(f>t) {
result = -1;
}
return result;
}
}
He eliminado todas las versiones anteriores. Mi agradecimiento a:
Respuestas:
Da la casualidad de que escribí una clase de BigFraction no hace mucho, para los problemas del Proyecto Euler . Mantiene un numerador y denominador BigInteger, por lo que nunca se desbordará. Pero será un poco lento para muchas operaciones que sabes que nunca se desbordarán ... de todos modos, úsala si quieres. Me muero por mostrar esto de alguna manera. :)
Editar : la última y mejor versión de este código, incluidas las pruebas unitarias, ahora está alojada en GitHub y también está disponible a través de Maven Central . Dejo mi código original aquí para que esta respuesta no sea solo un enlace ...
fuente
BigInteger
para almacenar valores arbitrariamente precisos. Si no es asílong
, entonces , que tiene una implementación más sencilla;Number
;Comparable<T>
;equals()
yhashCode()
;String
;toString()
; ySerializable
.De hecho, pruébate esto para ver el tamaño. Se ejecuta pero puede tener algunos problemas:
La salida es:
fuente
Apache Commons Math ha tenido una clase de fracciones durante bastante tiempo. La mayoría de las veces la respuesta a "¡Ojalá Java tuviera algo como X en la biblioteca central!" se puede encontrar bajo el paraguas de la biblioteca Apache Commons .
fuente
¡Por favor, conviértalo en un tipo inmutable! El valor de una fracción no cambia; la mitad no se convierte en un tercio, por ejemplo. En lugar de setDenominator, podría tener withDenominator que devuelve un nuevo fracción que tiene el mismo numerador pero el denominador especificado.
La vida es mucho más fácil con tipos inmutables.
Ignorar los valores iguales y el código hash también sería sensato, por lo que se puede usar en mapas y conjuntos. Los puntos de Outlaw Programmer sobre los operadores aritméticos y el formato de cadenas también son buenos.
Como guía general, eche un vistazo a BigInteger y BigDecimal. No hacen lo mismo, pero son lo suficientemente similares como para darte buenas ideas.
fuente
Bueno, por un lado, me desharía de los setters y haría que las fracciones fueran inmutables.
Probablemente también desee métodos para sumar, restar, etc., y tal vez alguna forma de obtener la representación en varios formatos de cadena.
EDITAR: Probablemente marcaría los campos como 'finales' para señalar mi intención, pero supongo que no es gran cosa ...
fuente
fuente
No es estrictamente necesario. (De hecho, si desea manejar la igualdad correctamente, no confíe en el doble para funcionar correctamente). Si b * d es positivo, a / b <c / d si ad <bc. Si hay números enteros negativos involucrados, eso puede manejarse apropiadamente ...
Podría reescribir como:
El uso de
long
aquí es para asegurarse de que no haya un desbordamiento si multiplica dos grandesint
s . manejar Si puede garantizar que el denominador siempre es no negativo (si es negativo, simplemente niegue tanto el numerador como el denominador), entonces puede deshacerse de tener que verificar si b * d es positivo y guardar algunos pasos. No estoy seguro de qué comportamiento estás buscando con denominador cero.No estoy seguro de cómo se compara el rendimiento con el uso de dobles para comparar. (es decir, si le importa tanto el rendimiento) Aquí hay un método de prueba que solía verificar. (Parece funcionar correctamente).
(PD, podría considerar la reestructuración para implementar
Comparable
oComparator
para su clase).fuente
Una mejora muy pequeña podría ser potencialmente guardar el valor doble que está calculando para que solo lo calcule en el primer acceso. Esta no será una gran victoria a menos que acceda mucho a este número, pero tampoco es demasiado difícil.
Un punto adicional podría ser la comprobación de errores que hace en el denominador ... cambia automáticamente de 0 a 1. No estoy seguro de si esto es correcto para su aplicación en particular, pero en general, si alguien está tratando de dividir por 0, algo está muy mal . Dejaría que esto arroje una excepción (una excepción especializada si cree que es necesaria) en lugar de cambiar el valor de una manera aparentemente arbitraria que el usuario no conoce.
En contraste con algunos otros comentarios, sobre la adición de métodos para sumar restar, etc ... ya que no mencionó que los necesita, supongo que no lo hace. Y a menos que esté construyendo una biblioteca que realmente va a ser utilizada en muchos lugares o por otras personas, vaya con YAGNI (no la necesitará, por lo que no debería estar allí).
fuente
Hay varias formas de mejorar este o cualquier tipo de valor:
Básicamente, eche un vistazo a la API para otras clases de valor como Double , Integer y haga lo que hacen :)
fuente
Si multiplicas el numerador y el denominador de una fracción por el denominador de la otra y viceversa, terminas con dos fracciones (que siguen siendo los mismos valores) con el mismo denominador y puedes comparar los numeradores directamente. Por lo tanto, no necesitaría calcular el valor doble:
fuente
cómo mejoraría ese código:
fuente
Ya tiene una función compareTo ... Implementaría la interfaz Comparable.
Sin embargo, puede que no importe realmente para lo que vayas a hacer con él.
fuente
Si te sientes aventurero, echa un vistazo a JScience . Tiene una
Rational
clase que representa fracciones.fuente
Yo diría que lanza una ArithmeticException para dividir por cero, ya que eso es realmente lo que está sucediendo:
En lugar de "Dividir por cero", es posible que desee que el mensaje diga "Dividir por cero: el denominador de la fracción es cero".
fuente
Una vez que haya creado un objeto de fracción, ¿por qué querría permitir que otros objetos establezcan el numerador o el denominador? Creo que estos deberían ser de solo lectura. Hace que el objeto sea inmutable ...
Además ... establecer el denominador en cero debería generar una excepción de argumento no válido (no sé qué es en Java)
fuente
Timothy Budd tiene una excelente implementación de una clase racional en sus "Estructuras de datos en C ++". Lenguaje diferente, por supuesto, pero se adapta muy bien a Java.
Recomendaría más constructores. Un constructor predeterminado tendría numerador 0, denominador 1. Un solo constructor arg asumiría un denominador de 1. Piense en cómo sus usuarios podrían usar esta clase.
¿No verifica el denominador cero? La programación por contrato le haría agregarlo.
fuente
Voy a tercero o quinto o lo que sea la recomendación para hacer inmutable tu fracción. También te recomiendo que amplíes la clase Number . Probablemente miraría la clase Double , ya que probablemente querrá implementar muchos de los mismos métodos.
Probablemente también debería implementar Comparable y Serializable, ya que probablemente se esperará este comportamiento. Por lo tanto, deberá implementar compareTo (). También necesitará anular equals () y no puedo enfatizar lo suficiente que también anula hashCode (). Sin embargo, este podría ser uno de los pocos casos en los que no desea que compareTo () y equals () sean consistentes, ya que las fracciones reducibles entre sí no son necesariamente iguales.
fuente
Una práctica de limpieza que me gusta es tener solo una vuelta.
fuente
Utilice la clase Rational de la biblioteca JScience . Es lo mejor para aritmética fraccionaria que he visto en Java.
fuente
Limpié respuesta de Cletus :
valueOf(String)
con elBigInteger(String)
que es más flexible y rápido.fuente
Observación inicial:
Nunca escribas esto:
Esto es mucho mejor
Solo crea para crear un buen hábito.
Al hacer que la clase sea inmutable como se sugiere, también puede aprovechar el doble para realizar las operaciones equals y hashCode y compareTo
Aquí está mi versión rápida y sucia:
Acerca del método de la fábrica estática, puede ser útil más adelante, si subclasifica la Fracción para manejar cosas más complejas, o si decide usar un grupo para los objetos usados con más frecuencia.
Puede que no sea el caso, solo quería señalarlo. :)
Consulte el primer elemento de Java efectivo .
fuente
Podría ser útil agregar cosas simples como corresponder, obtener el resto y completar.
fuente
Aunque tenga los métodos compareTo (), si desea utilizar utilidades como Collections.sort (), también debe implementar Comparable.
Además, para una visualización bonita, recomiendo anular toString ()
Y finalmente, haría pública la clase para que puedas usarla desde diferentes paquetes.
fuente
Esta función simplifica el uso del algoritmo euclediano es bastante útil al definir fracciones
fuente
Para la implementación de Fracción / Racional de grado industrial, lo implementaría para que pueda representar NaN, infinito positivo, infinito negativo y, opcionalmente, cero negativo con semántica operativa exactamente igual que los estados estándar IEEE 754 para aritmética de punto flotante (también facilita la conversión a / desde valores de coma flotante). Además, dado que la comparación con cero, uno y los valores especiales anteriores solo necesita una comparación simple pero combinada del numerador y denominador contra 0 y 1, agregaría varios métodos isXXX y compareToXXX para facilitar su uso (por ejemplo, eq0 () use numerador == 0 && denominador! = 0 detrás de escena en lugar de dejar que el cliente compare con una instancia de valor cero). Algunos valores predefinidos estáticamente (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN, etc.) también son útiles, ya que aparecen en varios lugares como valores constantes. Esta es la mejor manera en mi humilde opinión.
fuente
Fracción de clase:
El programa principal:
fuente