Я создал альтернативу BigFraction для BigInteger и BigDecimal. Имя для этого класса еще не совсем придумано.
Недавно я закончил курс, который рассказал нам об ООП и документации. Мы узнали о предварительных и постусловиях, LSP, государственном представительстве и многом другом. Я создал этот проект, чтобы показать (некоторые) плоды своих усилий. Особое внимание было уделено тому, чтобы этот класс был хорошо документирован, поскольку Javadoc также был очень большой частью этого класса.
Я приветствую и приглашаю любой вклад/критику/комментарии/и т.д. Пожалуйста, не обижайтесь и не ограничивайте/исключайте для краткости — я хочу получить все отзывы и намереваюсь прочитать каждое слово.
Наконец, вот GitHub для него, на случай, если история коммитов поможет предоставить обратную связь. Здесь нет ничего, что не было бы вставлено сюда (за исключением некоторых файлов конфигурации git и проекта jGRASP, содержащего этот код).
https://github.com/davidalayachew/BigNumber
Спасибо за ваше время и помощь!
BigInteger.java
import java.math.BigInteger;
import java.util.Objects;
/** If you know BigInteger and BigDecimal, think of this as BigFraction. This class is immutable. */
public final class BigNumber
{
/**
*
* The numerator.
*
* Can be any whole number.
* Must always contain the negative symbol if the whole "BigNumber" is negative.
* Cannot be null;
*
*/
private final BigInteger numerator;
/**
*
* The denominator.
*
* Can be any whole number except for zero.
* Must always be unsigned.
* Cannot be null.
*
*/
private final BigInteger denominator;
/**
*
* Constructor.
*
* @param param the new number.
*
*/
public BigNumber(long param)
{
this(BigInteger.valueOf(param), BigInteger.ONE);
}
/**
*
* Constructor.
*
* @param param the new number.
* @throws NullPointerException if parameter is null
*
*/
public BigNumber(BigInteger param)
{
Objects.requireNonNull(param, "parameter cannot be null");
this.numerator = param;
this.denominator = BigInteger.ONE;
}
/**
*
* Constructor.
*
* @param numerator the numerator.
* @param denominator the denominator.
* @throws NullPointerException if numerator or denominator is null
* @throws IllegalArgumentException if denominator is 0
*
*/
public BigNumber(BigInteger numerator, BigInteger denominator)
{
Objects.requireNonNull(numerator, "numerator cannot be null");
Objects.requireNonNull(denominator, "denominator cannot be null");
if (denominator.intValue() == 0)
{
throw new IllegalArgumentException("denominator cannot be 0");
}
this.numerator = isPositive(denominator) ? numerator : numerator.negate();
this.denominator = denominator.abs();
}
/**
*
* Constructor.
*
* @param param the new number.
* @throws NullPointerException if parameter is null
*
*/
public BigNumber(BigNumber param)
{
this(Objects.requireNonNull(param, "BigNumber cannot be null").numerator, param.denominator);
}
/**
*
* Returns the (signed) numerator.
*
* @return the numerator.
*
*/
public BigInteger getNumerator()
{
return this.numerator;
}
/**
*
* Returns the (unsigned) denominator.
*
* @return the denominator.
*
*/
public BigInteger getDenominator()
{
return this.denominator;
}
/**
*
* Returns an equivalent BigNumber, but with the sign changed from positive to negative, or vice versa.
*
* @return a BigNumber that has had its sign flipped
*
*/
public BigNumber negate()
{
return this.multiply(-1);
}
/**
*
* Returns true if positive.
*
* @return the result
*
*/
public boolean isPositive()
{
return isPositive(this.numerator);
}
/**
*
* Returns true if num is >0.
*
* @param num the number we are checking the sign of
* @return boolean result
*
*/
private static boolean isPositive(BigInteger num)
{
return num.signum() > 0;
}
/**
*
* Method to simplify the numerator and denominator before creating a BigNumber from them.
*
* Numerator cannot be null.
* Denominator cannot be null.
* Denominator cannot be 0.
*
* @param numerator the numerator
* @param denominator the denominator
* @return the simplified BigNumber
*
*/
private static BigNumber simplify(BigInteger numerator, BigInteger denominator)
{
final var gcd = numerator.gcd(denominator);
return new BigNumber(numerator.divide(gcd), denominator.divide(gcd));
}
/**
*
* Standard add function.
*
* @param param the number to add.
* @return The answer.
*
*/
public BigNumber add(long param)
{
return this.add(new BigNumber(param));
}
/**
*
* Standard add function.
*
* @param param the number to add.
* @return The answer.
* @throws NullPointerException if parameter is null
*
*/
public BigNumber add(BigNumber param)
{
Objects.requireNonNull(param, "parameter cannot be null");
BigInteger resultNumerator;
BigInteger resultDenominator;
if (this.denominator.equals(param.getDenominator()))
{
resultNumerator = this.numerator.add(param.getNumerator());
resultDenominator = BigInteger.ONE;
}
else
{
//our goal here is to end up with a shared denominator
//So, in order to accomplish that, we will need to do
//that cross multiplication thing that you do to end
//up with the same denominator
BigInteger numerator1 = this.numerator.multiply(param.denominator);
BigInteger numerator2 = param.numerator.multiply(this.denominator);
resultNumerator = numerator1.add(numerator2);
resultDenominator = param.getDenominator().multiply(this.getDenominator());
}
//simplify before we return. We don't want to waste memory when the number can be simplified into something smaller.
return simplify(resultNumerator, resultDenominator);
}
/**
*
* Standard subtract function.
*
* @param param the number to subtract from this.
* @return The answer.
*
*/
public BigNumber subtract(long param)
{
return this.add(new BigNumber(param * -1));
}
/**
*
* Standard subtract function.
*
* @param param the number to subtract from this.
* @return The answer.
*
*/
public BigNumber subtract(BigNumber param)
{
Objects.requireNonNull(param, "BigNumber cannot be null");
return this.add(param.negate());
}
/**
*
* Standard multiply function.
*
* @param param the number to multiply.
* @return The answer.
*
*/
public BigNumber multiply(long param)
{
return this.multiply(new BigNumber(param));
}
/**
*
* Standard multiply function.
*
* @param param the number to multiply.
* @return The answer.
* @throws NullPointerException if parameter is null
*
*/
public BigNumber multiply(BigNumber param)
{
Objects.requireNonNull(param, "parameter cannot be null");
BigInteger resultNumerator = this.numerator.multiply(param.numerator);
BigInteger resultDenominator = this.denominator.multiply(param.denominator);
return simplify(resultNumerator, resultDenominator);
}
/**
*
* Standard divide function.
*
* @param param the number to divide this by.
* @return The answer.
* @throws IllegalArgumentException if param == 0
*
*/
public BigNumber divide(long param)
{
if (param == 0) {
throw new IllegalArgumentException("param cannot be 0"); }
return this.divide(new BigNumber(param));
}
/**
*
* Standard divide function.
*
* @param param the number to divide this by.
* @return The answer.
* @throws NullPointerException if parameter is null
* @throws IllegalArgumentException if param == 0
*
*/
public BigNumber divide(BigNumber param)
{
Objects.requireNonNull(param, "parameter cannot be null");
if (param.numerator.equals(BigInteger.ZERO)) {
throw new IllegalArgumentException("param cannot be 0"); }
return this.multiply(new BigNumber(param.denominator, param.numerator));
}
/** {@inheritDoc} */
public String toString()
{
return this.numerator + " / " + this.denominator;
}
}
Давидалайачу
3 ответа
Терминология
* Can be any whole number except for zero. * Must always be unsigned. * Cannot be null.
Лично я бы сказал, что она должна быть строго положительной. Не только потому, что он короче, но я думаю, что это более правильная терминология: BigInteger всегда имеет знак, потому что это свойство типа (у него всегда есть знак), но некоторые экземпляры BigInteger являются положительными, и это то, что требуется здесь.
Это также относится к другому использованию «без знака» в другом комментарии.
BigInteger.intValue()
Обратите внимание, что intValue возвращается младшие 32 бита BigInteger. Если полное значение было равно нулю, то младшие 32 бита, конечно же, тоже равны нулю. Но есть и другие случаи, когда младшие 32 бита равны нулю, даже если полное значение не равно нулю, а именно каждое ненулевое кратное 2.32.
Итак, у этого есть ошибка:
if (denominator.intValue() == 0) { throw new IllegalArgumentException("denominator cannot be 0"); }
А именно отбрасывание некоторых знаменателей, которые не равны нулю, но чьи младшие 32 бита равны нулю. Это, скорее всего, не будет обнаружено модульными тестами, если они специально не нацелены на это.
отрицать
Наверняка есть более простая вещь, чем умножать на -1
public BigNumber negate() { return this.multiply(-1); }
Отрицание long не может на самом деле отрицать это
В этом коде:
public BigNumber subtract(long param) { return this.add(new BigNumber(param * -1)); }
Есть ошибка, которая происходит только тогда, когда param == Long.MIN_VALUE.
Есть два значения, для которых x * -1 == x когда x это long:0 конечно, но тоже Long.MIN_VALUE. Этот пограничный случай довольно типичен для пропуска.
Правильнее всего преобразовать значение param либо в BigInteger или прямо в BigNumber а затем отрицание этого.
Сложение дробей с равными знаменателями
if (this.denominator.equals(param.getDenominator())) { resultNumerator = this.numerator.add(param.getNumerator()); resultDenominator = BigInteger.ONE; }
Считают, что 1/3 + 1/3 должен быть 2/3
Гарольд
Param неправильное имя параметра. Его имя должно описывать то, что оно представляет в контексте предметной области, а не то, что оно представляет на языке программирования. Вместо этого используйте «значение».
public BigNumber(BigInteger value)
Предоставьте фабричные методы, чтобы помочь пользователю оптимизировать инициализацию объекта. Иметь public static BigNumber valueOf(long value) метод позволяет пропустить создание объекта для общих значений, таких как ноль и единица, и вместо этого вернуть константу (см. BigInteger.ZERO например).
Решите, хотите ли вы, чтобы BigNumber всегда был упрощен или пользователь должен запрашивать его, и придерживайтесь этого во всех операциях. Сейчас конструкторы не упрощают полученный BigNumber но математические операции делают. Это может сбивать с толку и должно быть задокументировано. Лично я бы всегда упрощал, поскольку это позволяет более эффективно оптимизировать вышеупомянутые фабричные методы. Постоянное упрощение значений также упрощает equals и hashCode методы.
Нулевой знаменатель должен бросать ArithmeticException. Вот что получится, если разделить на ноль.
Почему вы предоставляете статический isPositive(BigNumber) метод? Предоставление метода, изящно обрабатывающего null, было бы логичным, но метод этого не обеспечивает. Это похоже на расползание / беспорядок.
Мне не нравится разбивать операторы if-else наподобие этого. Они разрушают цепочку и затрудняют чтение кода.
}
else
{
Вы обязательно должны предоставить equals метод (и hashCode что с этим связано).
Предоставление метода для создания BigNumber из double или BigDecimal хорошее упражнение на будущее. Точно так же valueOf(String) который может анализировать вывод toString() вернуться к BigNumber было бы аккуратно (и тогда вы можете начать беспокоиться о таких символах, как ½ и ¾ :D). Что касается метода toString(), следует ли вообще выводить знаменатель, если он 1 или если числитель 0?
ТорбенПутконен
- Знаменатель не может быть равен нулю.
Вы, конечно, можете сделать это. Вы также можете выбрать, чтобы знаменатель был равен нулю. В этом случае вы можете представить значения PositiveInfinity, NegativeInfinity и NaN. Вам не нужно. Это зависит от того, для чего вы его используете, я полагаю. Это немного усложнит ваш класс, потому что вам придется проверять нулевой знаменатель в каждом методе. - общественное логическое значение isPositive()
Почему isPositive(), а не isNegative() или isZero()? Это странно. Все, что вам нужно, это метод signum(). - Имеет смысл реализовать интерфейс Comparable, равно как и equals() и hashCode().
(Другие вещи уже упоминались в других ответах)
