Estou tentando trabalhar com frações em Java.
Eu quero implementar funções aritméticas. Para isso, primeiro exigirei uma maneira de normalizar as funções. Eu sei que não posso somar 1/6 e 1/2 até que tenha um denominador comum. Terei que adicionar 1/6 e 3/6. Uma abordagem ingênua me faria adicionar 2/12 e 6/12 e depois reduzir. Como posso alcançar um denominador comum com a menor penalidade de desempenho? Qual algoritmo é melhor para isso?
Versão 8 (graças a hstoerr ):
As melhorias incluem:
- o método equals () agora é consistente com o 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;
}
}
Eu removi todas as versões anteriores. Meus agradecimentos a:
Respostas:
Acontece que escrevi uma classe BigFraction não muito tempo atrás, para problemas do Projeto Euler . Ele mantém um numerador e denominador BigInteger, para que nunca transborde. Mas vai ser um pouco lento para muitas operações que você sabe que nunca irão transbordar ... de qualquer maneira, use-o se quiser. Estou morrendo de vontade de mostrar isso de alguma forma. :)
Edit : a versão mais recente e melhor deste código, incluindo testes de unidade, agora está hospedada no GitHub e também disponível via Maven Central . Estou deixando meu código original aqui para que esta resposta não seja apenas um link ...
fonte
BigInteger
para armazenar valores arbitrariamente precisos. Se nãolong
, então , que tem uma implementação mais fácil;Number
;Comparable<T>
;equals()
ehashCode()
;String
;toString()
; eSerializable
.Na verdade, experimente isso para ver o tamanho. Ele funciona, mas pode ter alguns problemas:
O resultado é:
fonte
O Apache Commons Math teve uma classe de frações por algum tempo. Na maioria das vezes, a resposta para "Rapaz, eu gostaria que o Java tivesse algo como X na biblioteca principal!" pode ser encontrado sob a égide da biblioteca Apache Commons .
fonte
Por favor, torne-o um tipo imutável! O valor de uma fração não muda - a metade não se torna um terço, por exemplo. Em vez de setDenominator, você poderia ter withDenominator, que retorna uma nova fração com o mesmo numerador, mas o denominador especificado.
A vida é muito mais fácil com tipos imutáveis.
Substituir equals e hashcode também seria sensato, portanto, pode ser usado em mapas e conjuntos. Os pontos do Outlaw Programmer sobre operadores aritméticos e formatação de strings também são bons.
Como um guia geral, dê uma olhada em BigInteger e BigDecimal. Eles não estão fazendo a mesma coisa, mas são semelhantes o suficiente para lhe dar boas ideias.
fonte
Bem, por um lado, eu me livraria dos setters e tornaria Frações imutáveis.
Você provavelmente também desejará métodos para adicionar, subtrair, etc., e talvez alguma forma de obter a representação em vários formatos de String.
EDITAR: provavelmente, marcaria os campos como 'finais' para sinalizar minha intenção, mas acho que não é grande coisa ...
fonte
fonte
Não é estritamente necessário. (Na verdade, se você quiser lidar com a igualdade corretamente, não confie no double para funcionar corretamente.) Se b * d for positivo, a / b <c / d se ad <bc. Se houver números inteiros negativos envolvidos, isso pode ser tratado adequadamente ...
Posso reescrever como:
O uso de
long
aqui é para garantir que não haja um estouro se você multiplicar doisint
s grandes . handle Se você pode garantir que o denominador é sempre não negativo (se for negativo, apenas negue o numerador e o denominador), então você pode se livrar de ter que verificar se b * d é positivo e salvar alguns passos. Não tenho certeza de qual comportamento você está procurando com denominador zero.Não tenho certeza de como o desempenho se compara ao uso de duplos para comparar. (isto é, se você se preocupa tanto com o desempenho) Aqui está um método de teste que usei para verificar. (Parece funcionar corretamente.)
(ps você pode considerar a reestruturação para implementar
Comparable
ouComparator
para sua classe.)fonte
Uma melhoria muito pequena poderia ser potencialmente salvar o valor duplo que você está computando para que você o calcule apenas no primeiro acesso. Não será uma grande vitória a menos que você acesse muito esse número, mas também não é muito difícil de fazer.
Um ponto adicional pode ser a verificação de erro que você faz no denominador ... você muda automaticamente de 0 para 1. Não tenho certeza se isso é correto para sua aplicação em particular, mas em geral se alguém está tentando dividir por 0, algo está muito errado . Eu deixaria isso lançar uma exceção (uma exceção especializada se você achar que é necessário) em vez de alterar o valor de uma forma aparentemente arbitrária que não seja conhecida pelo usuário.
Em contraste com alguns outros comentários, sobre adicionar métodos para adicionar subtrair, etc ... já que você não mencionou a necessidade deles, estou assumindo que não. E a menos que você esteja construindo uma biblioteca que realmente será usada em muitos lugares ou por outras pessoas, vá com YAGNI (você não vai precisar dela, então ela não deveria estar lá).
fonte
Existem várias maneiras de melhorar este ou qualquer tipo de valor:
Basicamente, dê uma olhada na API para outras classes de valor como Double , Integer e faça o que eles fazem :)
fonte
Se você multiplicar o numerador e o denominador de uma Fração pelo denominador da outra e vice-versa, você acaba com duas frações (que ainda são os mesmos valores) com o mesmo denominador e você pode comparar os numeradores diretamente. Portanto, você não precisaria calcular o valor duplo:
fonte
como eu poderia melhorar esse código:
fonte
Você já tem uma função compareTo ... Eu implementaria a interface Comparable.
Pode não importar muito para o que você vai fazer com ele.
fonte
Se você está se sentindo aventureiro, dê uma olhada no JScience . Tem uma
Rational
classe que representa frações.fonte
Eu diria que lance uma ArithmeticException para dividir por zero, já que é isso que realmente está acontecendo:
Em vez de "Dividir por zero.", Você pode querer fazer a mensagem dizer "Dividir por zero: Denominador para Fração é zero."
fonte
Depois de criar um objeto de fração, por que você deseja permitir que outros objetos definam o numerador ou o denominador? Eu acho que estes devem ser apenas para leitura. Isso torna o objeto imutável ...
Além disso ... definir o denominador como zero deve lançar uma exceção de argumento inválido (não sei o que é em Java)
fonte
Timothy Budd tem uma excelente implementação de uma classe Rational em seu "Data Structures in C ++". Linguagem diferente, é claro, mas é muito bem adaptada para Java.
Eu recomendaria mais construtores. Um construtor padrão teria numerador 0, denominador 1. Um único construtor arg assumiria um denominador de 1. Pense em como seus usuários podem usar esta classe.
Sem verificação de denominador zero? A programação por contrato requer que você o adicione.
fonte
Vou em terceiro, quinto ou qualquer outra recomendação para tornar sua fração imutável. Eu também recomendo que você estenda a classe Number . Eu provavelmente olharia para o Double classe , já que você provavelmente vai querer implementar muitos dos mesmos métodos.
Você provavelmente também deve implementar Comparable e Serializable, pois esse comportamento provavelmente será o esperado. Portanto, você precisará implementar compareTo (). Você também precisará substituir equals () e eu não posso enfatizar o suficiente para que você também substitua hashCode (). Esse pode ser um dos poucos casos em que você não deseja que compareTo () e equals () sejam consistentes, pois as frações redutíveis entre si não são necessariamente iguais.
fonte
Uma prática de limpeza que gosto é ter apenas uma devolução.
fonte
Use a classe Rational da biblioteca JScience . É a melhor coisa para aritmética fracionária que vi em Java.
fonte
Limpei a resposta de Cletus :
valueOf(String)
pela,BigInteger(String)
que é mais flexível e rápida.fonte
Observação inicial:
Nunca escreva isso:
Isto é muito melhor
Basta criar para criar um bom hábito.
Ao tornar a classe imutável conforme sugerido, você também pode aproveitar o double para realizar as operações equals e hashCode e compareTo
Aqui está minha versão suja rápida:
Sobre o método de fábrica estático, pode ser útil posteriormente, se você criar uma subclasse de Fração para lidar com coisas mais complexas ou se decidir usar um pool para os objetos usados com mais frequência.
Pode não ser o caso, eu apenas queria salientar. :)
Consulte o primeiro item Java efetivo .
fonte
Pode ser útil adicionar coisas simples como retribuir, obter o resto e ficar inteiro.
fonte
Mesmo que você tenha os métodos compareTo (), se quiser usar utilitários como Collections.sort (), você também deve implementar Comparable.
Além disso, para uma exibição bonita, recomendo substituir toString ()
E, por fim, tornaria a classe pública para que você pudesse usá-la em diferentes pacotes.
fonte
Esta função simplifica usando o algoritmo euclediano é bastante útil na definição de frações
fonte
Para implementação de Fração / Racional de nível de indústria, eu iria implementá-lo para que possa representar NaN, infinito positivo, infinito negativo e, opcionalmente, zero negativo com semântica operacional exatamente igual aos estados do padrão IEEE 754 para aritmética de ponto flutuante (também facilita o conversão de / para valores de ponto flutuante). Além disso, uma vez que a comparação com zero, um e os valores especiais acima precisa apenas de comparação simples, mas combinada do numerador e denominador contra 0 e 1 - eu adicionaria vários métodos isXXX e compareToXXX para facilidade de uso (por exemplo, eq0 (). use numerador == 0 && denominador! = 0 nos bastidores em vez de deixar o cliente comparar com uma instância de valor zero). Alguns valores predefinidos estaticamente (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN, etc.) também são úteis, já que eles aparecem em vários lugares como valores constantes. Esta é a melhor maneira IMHO.
fonte
Fração de classe:
O programa principal:
fonte