Я пытаюсь работать с дробями на Java.
Я хочу реализовать арифметические функции. Для этого мне сначала потребуется способ нормализации функций. Я знаю, что не могу сложить 1/6 и 1/2, пока не получу общий знаменатель. Придется сложить 1/6 и 3/6. Наивный подход заставил бы меня добавить 2/12 и 6/12, а затем уменьшить. Как достичь общего знаменателя с наименьшими потерями в производительности? Какой алгоритм лучше всего подходит для этого?
Версия 8 (спасибо hstoerr ):
Улучшения включают:
- метод equals () теперь совместим с методом 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;
}
}
Я удалил все предыдущие версии. Благодарю:
Ответы:
Так уж получилось, что не так давно я написал класс BigFraction для задач Project Euler . У него есть числитель и знаменатель BigInteger, поэтому он никогда не переполнится. Но это будет немного медленным для многих операций, которые, как вы знаете, никогда не переполнятся ... в любом случае, используйте его, если хотите. Я очень хотел как-нибудь это показать. :)
Изменить : последняя и самая лучшая версия этого кода, включая модульные тесты, теперь размещена на GitHub, а также доступна через Maven Central . Я оставляю здесь свой исходный код, чтобы этот ответ не был просто ссылкой ...
источник
BigInteger
для хранения произвольно точных значений. Если не то, то уlong
которого есть более легкая реализация;Number
;Comparable<T>
;equals()
иhashCode()
;String
;toString()
; иSerializable
.Фактически, примерьте это на размер. Он работает, но может иметь некоторые проблемы:
Выход:
источник
В Apache Commons Math уже довольно давно есть класс дроби . В большинстве случаев ответ на вопрос: «Боже, я бы хотел, чтобы у Java было что-то вроде X в основной библиотеке!» можно найти в библиотеке Apache Commons .
источник
Пожалуйста, сделайте его неизменяемым типом! Значение дроби не меняется - например, половина не становится третьей. Вместо setDenominator вы можете использовать withDenominator, который возвращает новую дробь с тем же числителем, но с указанным знаменателем.
С неизменными типами жизнь намного проще.
Было бы разумно переопределить equals и hashcode, поэтому его можно использовать в картах и наборах. Замечания программиста-преступника об арифметических операторах и форматировании строк тоже хороши.
В качестве общего руководства взгляните на BigInteger и BigDecimal. Они не делают то же самое, но достаточно похожи, чтобы дать вам хорошие идеи.
источник
Ну, во-первых, я бы избавился от установщиков и сделал Fractions неизменяемыми.
Возможно, вам также понадобятся методы для сложения, вычитания и т. Д., И, возможно, какой-то способ получить представление в различных строковых форматах.
РЕДАКТИРОВАТЬ: Я бы, вероятно, пометил поля как «окончательные», чтобы сигнализировать о моем намерении, но я думаю, это не имеет большого значения ...
источник
источник
Не обязательно. (На самом деле, если вы хотите правильно обрабатывать равенство, не полагайтесь на правильную работу double.) Если b * d положительно, a / b <c / d, если ad <bc. Если задействованы отрицательные целые числа, с этим можно справиться соответствующим образом ...
Я мог бы переписать как:
Это используется
long
для предотвращения переполнения при умножении двух большихint
s. handle Если вы можете гарантировать, что знаменатель всегда неотрицателен (если он отрицательный, просто отрицайте и числитель, и знаменатель), то вы можете избавиться от необходимости проверять, является ли b * d положительным, и сэкономить несколько шагов. Я не уверен, какое поведение вы ищете с нулевым знаменателем.Не уверен, как производительность сравнивается с использованием удвоений для сравнения. (то есть, если вы так сильно заботитесь о производительности) Вот метод, который я использовал для проверки. (Кажется, работает правильно.)
(ps вы можете подумать о реструктуризации для реализации
Comparable
илиComparator
для вашего класса.)источник
Одно очень незначительное улучшение потенциально может заключаться в сохранении двойного значения, которое вы вычисляете, чтобы вы вычисляли его только при первом доступе. Это не будет большой победой, если вы не пользуетесь этим номером часто, но это тоже не слишком сложно.
Еще одним моментом может быть проверка ошибок, которую вы выполняете в знаменателе ... вы автоматически меняете 0 на 1. Не уверен, что это правильно для вашего конкретного приложения, но в целом, если кто-то пытается разделить на 0, что-то очень не так . Я бы позволил этому сгенерировать исключение (специальное исключение, если вы считаете, что это необходимо) вместо того, чтобы изменять значение, казалось бы, произвольным образом, который не известен пользователю.
В отличие от некоторых других комментариев о добавлении методов для добавления вычитания и т. Д., Поскольку вы не упомянули о необходимости в них, я предполагаю, что вы этого не сделаете. И если вы не создаете библиотеку, которая действительно будет использоваться во многих местах или другими людьми, используйте YAGNI (она вам не понадобится, поэтому ее там не должно быть).
источник
Есть несколько способов улучшить этот или любой другой тип значения:
В принципе, взгляните на API для других классов значений, таких как Double , Integer, и сделайте то, что они делают :)
источник
Если вы умножите числитель и знаменатель одной дроби на знаменатель другой и наоборот, вы получите две дроби (которые по-прежнему имеют одинаковые значения) с одним и тем же знаменателем, и вы можете напрямую сравнить числители. Следовательно, вам не нужно рассчитывать двойное значение:
источник
как бы улучшить этот код:
источник
У вас уже есть функция compareTo ... Я бы реализовал интерфейс Comparable.
Хотя это может не иметь большого значения для того, что вы собираетесь с ним делать.
источник
Если вы любите приключения, взгляните на JScience . У него есть
Rational
класс, представляющий дроби.источник
Я бы сказал, выбросить ArithmeticException для деления на ноль, поскольку это действительно то, что происходит:
Вместо «Разделить на ноль» вы можете сделать так, чтобы в сообщении говорилось «Разделить на ноль: знаменатель дроби равен нулю».
источник
После того, как вы создали объект дроби, почему вы хотите разрешить другим объектам устанавливать числитель или знаменатель? Я думаю, что их нужно только читать. Это делает объект неизменным ...
Также ... установка знаменателя на ноль должна вызывать исключение недопустимого аргумента (я не знаю, что это такое в Java)
источник
Тимоти Бадд имеет прекрасную реализацию класса Rational в его «Структурах данных в C ++». Другой язык, конечно, но он очень хорошо переносится на Java.
Я бы порекомендовал больше конструкторов. Конструктор по умолчанию будет иметь числитель 0, знаменатель 1. Конструктор с одним аргументом принимает знаменатель 1. Подумайте, как ваши пользователи могут использовать этот класс.
Нет проверки на нулевой знаменатель? Программирование по контракту требует, чтобы вы его добавили.
источник
Я буду третьим или пятым, или как-нибудь еще, рекомендую сделать вашу дробь неизменной. Я также рекомендую вам расширить класс Number . Я бы, наверное, посмотрел на Double класс , поскольку вы, вероятно, захотите реализовать многие из тех же методов.
Вероятно, вам также следует реализовать Comparable и Serializable, поскольку такое поведение, вероятно, будет ожидаемым. Таким образом, вам нужно будет реализовать compareTo (). Вам также потребуется переопределить equals (), и я не могу особо подчеркнуть, что вы также переопределяете hashCode (). Это может быть один из немногих случаев, когда вы не хотите, чтобы compareTo () и equals () были согласованными, поскольку дроби, сводимые друг к другу, не обязательно равны.
источник
Мне нравится делать уборку только один раз.
источник
Используйте класс Rational из библиотеки JScience . Это лучшее, что я видел на Java для дробной арифметики.
источник
Я подчистил ответ Клетуса :
valueOf(String)
наBigInteger(String)
более гибкий и быстрый.источник
Начальное замечание:
Никогда не пиши это:
Это намного лучше
Просто создавайте, чтобы создать хорошую привычку.
Сделав класс неизменяемым, как было предложено, вы также можете воспользоваться преимуществом double для выполнения операций equals и hashCode и compareTo.
Вот моя быстрая грязная версия:
Что касается статического фабричного метода, он может быть полезен позже, если вы подклассифицируете Fraction для обработки более сложных вещей или если вы решите использовать пул для наиболее часто используемых объектов.
Возможно, это не так, я просто хотел указать на это. :)
См. Первый пункт « Эффективная Java» .
источник
Может быть полезно добавить такие простые вещи, как «ответить взаимностью», «получить остаток» и «получить целое».
источник
Даже если у вас есть методы compareTo (), если вы хотите использовать такие утилиты, как Collections.sort (), вам также следует реализовать Comparable.
Кроме того, для красивого отображения я рекомендую переопределить toString ()
И, наконец, я бы сделал класс общедоступным, чтобы вы могли использовать его из разных пакетов.
источник
Эта функция упрощения с использованием алгоритма эвклида весьма полезна при определении дробей
источник
Для реализации Fraction / Rational промышленного уровня я бы реализовал его так, чтобы он мог представлять NaN, положительную бесконечность, отрицательную бесконечность и, необязательно, отрицательный ноль с операционной семантикой, точно такой же, как стандартные состояния IEEE 754 для арифметики с плавающей запятой (это также упрощает преобразование в / из значений с плавающей запятой). Кроме того, поскольку сравнение с нулем, единицей и специальными значениями выше требует только простого, но комбинированного сравнения числителя и знаменателя с 0 и 1, я бы добавил несколько методов isXXX и compareToXXX для простоты использования (например, eq0 () используйте числитель == 0 && знаменатель! = 0 за кулисами вместо того, чтобы позволить клиенту сравнивать с экземпляром с нулевым значением). Также полезны некоторые статически предопределенные значения (ZERO, ONE, TWO, TEN, ONE_TENTH, NAN и т. Д.), поскольку они появляются в нескольких местах как постоянные значения. Это лучший способ ИМХО.
источник
Доля класса:
Основная программа:
источник