add Fraction

This commit is contained in:
Looly 2025-01-17 19:40:47 +08:00
parent 2c8b6316d2
commit 62a2c78e7b

View File

@ -0,0 +1,710 @@
/*
* Copyright (c) 2025 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.core.math;
import java.math.BigInteger;
import java.util.Objects;
/**
* {@code Fraction} 是一个 {@link Number} 实现
* 可以准确地存储分数
*
* <p>此类是不可变的并且与大多数接受
* {@link Number} 的方法兼容</p>
*
* <p>请注意此类适用于常见用例它是基于 <em>int</em>
* 因此容易受到各种溢出问题的影响对于基于 BigInteger 的等效类
* 请参见 Commons Math 库中的 BigFraction </p>
* <p>
* 此类来自Apache Commons Lang3
*
* @since 6.0.0
*/
public final class Fraction extends Number implements Comparable<Fraction> {
private static final long serialVersionUID = 1L;
/**
* {@link Fraction} 表示 0.
*/
public static final Fraction ZERO = new Fraction(0, 1);
/**
* {@link Fraction} 表示 1.
*/
public static final Fraction ONE = new Fraction(1, 1);
/**
* {@code double} 值创建一个 {@code Fraction} 实例
*
* <p>此方法使用 <a href="https://web.archive.org/web/20210516065058/http%3A//archives.math.utk.edu/articles/atuyl/confrac/">
* 连分数算法</a>最多计算 25 个收敛项并将分母限制在 10,000 以内</p>
*
* @param value 要转换的 double
* @return 一个新的分数实例接近该值
* @throws ArithmeticException 如果 {@code |value| > Integer.MAX_VALUE} {@code value = NaN}
* @throws ArithmeticException 如果计算出的分母为 {@code zero}
* @throws ArithmeticException 如果算法不收敛
*/
public static Fraction of(double value) {
final int sign = value < 0 ? -1 : 1;
value = Math.abs(value);
if (value > Integer.MAX_VALUE || Double.isNaN(value)) {
throw new ArithmeticException("The value must not be greater than Integer.MAX_VALUE or NaN");
}
final int wholeNumber = (int) value;
value -= wholeNumber;
int numer0 = 0; // the pre-previous
int denom0 = 1; // the pre-previous
int numer1 = 1; // the previous
int denom1 = 0; // the previous
int numer2; // the current, setup in calculation
int denom2; // the current, setup in calculation
int a1 = (int) value;
int a2;
double x1 = 1;
double x2;
double y1 = value - a1;
double y2;
double delta1, delta2 = Double.MAX_VALUE;
double fraction;
int i = 1;
do {
delta1 = delta2;
a2 = (int) (x1 / y1);
x2 = y1;
y2 = x1 - a2 * y1;
numer2 = a1 * numer1 + numer0;
denom2 = a1 * denom1 + denom0;
fraction = (double) numer2 / (double) denom2;
delta2 = Math.abs(value - fraction);
a1 = a2;
x1 = x2;
y1 = y2;
numer0 = numer1;
denom0 = denom1;
numer1 = numer2;
denom1 = denom2;
i++;
} while (delta1 > delta2 && denom2 <= 10000 && denom2 > 0 && i < 25);
if (i == 25) {
throw new ArithmeticException("Unable to convert double to fraction");
}
return getReducedFraction((numer0 + wholeNumber * denom0) * sign, denom0);
}
/**
* 使用分数的两个部分 Y/Z 创建一个 {@code Fraction} 实例
*
* <p>任何负号都会被解析到分子上</p>
*
* @param numerator 分子例如 '3/7' 中的 3
* @param denominator 分母例如 '3/7' 中的 7
* @return 一个新的分数实例
* @throws ArithmeticException 如果分母为 {@code zero} 或分母为 {@code negative} 且分子为 {@code Integer#MIN_VALUE}
*/
public static Fraction of(int numerator, int denominator) {
if (denominator == 0) {
throw new ArithmeticException("The denominator must not be zero");
}
if (denominator < 0) {
if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) {
throw new ArithmeticException("overflow: can't negate");
}
numerator = -numerator;
denominator = -denominator;
}
return new Fraction(numerator, denominator);
}
/**
* 使用分数的三个部分 X Y/Z 创建一个 {@code Fraction} 实例
*
* <p>负号必须传递到整数部分</p>
*
* @param whole 整数部分例如 '一又七分之三' 中的
* @param numerator 分子例如 '一又七分之三' 中的
* @param denominator 分母例如 '一又七分之三' 中的
* @return 一个新的分数实例
* @throws ArithmeticException 如果分母为 {@code zero}
* @throws ArithmeticException 如果分母为负数
* @throws ArithmeticException 如果分子为负数
* @throws ArithmeticException 如果结果分子超过 {@code Integer.MAX_VALUE}
*/
public static Fraction of(final int whole, final int numerator, final int denominator) {
if (denominator == 0) {
throw new ArithmeticException("The denominator must not be zero");
}
if (denominator < 0) {
throw new ArithmeticException("The denominator must not be negative");
}
if (numerator < 0) {
throw new ArithmeticException("The numerator must not be negative");
}
final long numeratorValue;
if (whole < 0) {
numeratorValue = whole * (long) denominator - numerator;
} else {
numeratorValue = whole * (long) denominator + numerator;
}
if (numeratorValue < Integer.MIN_VALUE || numeratorValue > Integer.MAX_VALUE) {
throw new ArithmeticException("Numerator too large to represent as an Integer.");
}
return new Fraction((int) numeratorValue, denominator);
}
/**
* {@link String} 创建一个 Fraction
*
* <p>接受的格式有</p>
*
* <ol>
* <li>包含点的 {@code double} 字符串</li>
* <li>'X Y/Z'</li>
* <li>'Y/Z'</li>
* <li>'X'一个简单的整数</li>
* </ol>
* <p>和一个 .</p>
*
* @param str 要解析的字符串必须不为 {@code null}
* @return 新的 {@code Fraction} 实例
* @throws NullPointerException 如果字符串为 {@code null}
* @throws NumberFormatException 如果数字格式无效
*/
public static Fraction of(String str) {
Objects.requireNonNull(str, "str");
// parse double format
int pos = str.indexOf('.');
if (pos >= 0) {
return of(Double.parseDouble(str));
}
// parse X Y/Z format
pos = str.indexOf(' ');
if (pos > 0) {
final int whole = Integer.parseInt(str.substring(0, pos));
str = str.substring(pos + 1);
pos = str.indexOf('/');
if (pos < 0) {
throw new NumberFormatException("The fraction could not be parsed as the format X Y/Z");
}
final int numer = Integer.parseInt(str.substring(0, pos));
final int denom = Integer.parseInt(str.substring(pos + 1));
return of(whole, numer, denom);
}
// parse Y/Z format
pos = str.indexOf('/');
if (pos < 0) {
// simple whole number
return of(Integer.parseInt(str), 1);
}
final int numer = Integer.parseInt(str.substring(0, pos));
final int denom = Integer.parseInt(str.substring(pos + 1));
return of(numer, denom);
}
/**
* 使用分数的两个部分 Y/Z 创建一个简化后的 {@code Fraction} 实例
*
* <p>例如如果输入参数表示 2/4则创建的分数将是 1/2</p>
*
* <p>任何负号都会被解析到分子上</p>
*
* @param numerator 分子例如 '3/7' 中的 3
* @param denominator 分母例如 '3/7' 中的 7
* @return 一个新的分数实例分子和分母已简化
* @throws ArithmeticException 如果分母为 {@code zero}
*/
public static Fraction ofReduced(int numerator, int denominator) {
if (denominator == 0) {
throw new ArithmeticException("The denominator must not be zero");
}
if (numerator == 0) {
return ZERO; // normalize zero.
}
// allow 2^k/-2^31 as a valid fraction (where k>0)
if (denominator == Integer.MIN_VALUE && (numerator & 1) == 0) {
numerator /= 2;
denominator /= 2;
}
if (denominator < 0) {
if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) {
throw new ArithmeticException("overflow: can't negate");
}
numerator = -numerator;
denominator = -denominator;
}
// simplify fraction.
final int gcd = MathUtil.gcd(numerator, denominator);
numerator /= gcd;
denominator /= gcd;
return new Fraction(numerator, denominator);
}
/**
* 分子数部分三个七分之三的 3
*/
private final int numerator;
/**
* 分母是分数的一部分三个七分之一中的 7
*/
private final int denominator;
/**
* 缓存输出 hashCode类是不可变的
*/
private transient int hashCode;
/**
* 缓存输出 toString类是不可变的
*/
private transient String toString;
/**
* 缓存输出到 ProperString类是不可变的
*/
private transient String toProperString;
/**
* 使用分数的两个部分构造一个 {@code Fraction} 实例例如 Y/Z
*
* @param numerator 分子例如在七分之三中的三
* @param denominator 分母例如在七分之三中的七
*/
private Fraction(final int numerator, final int denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
/**
* 获取与此分数等值的正分数
* <p>更精确地说{@code (fraction >= 0 ? this : -fraction)}</p>
* <p>返回的分数不会被约简</p>
*
* @return 如果此分数为正则返回 {@code this}否则返回一个新的正分数实例其分子符号相反
*/
public Fraction abs() {
if (numerator >= 0) {
return this;
}
return negate();
}
/**
* 将此分数与另一个分数相加并返回约简后的结果
* 该算法遵循 Knuth 4.5.1
*
* @param fraction 要添加的分数不能为空
* @return 包含结果值的 {@code Fraction} 实例
* @throws NullPointerException 如果传入的分数为 {@code null}
* @throws ArithmeticException 如果结果的分子或分母超出 {@code Integer.MAX_VALUE}
*/
public Fraction add(final Fraction fraction) {
return addSub(fraction, true /* add */);
}
/**
* 使用 Knuth 4.5.1 节中描述的算法实现加法和减法
*
* @param fraction 要加或减的分数不能为空
* @param isAdd 如果为 true则执行加法如果为 false则执行减法
* @return 包含结果值的 {@code Fraction} 实例
* @throws IllegalArgumentException 如果传入的分数为 {@code null}
* @throws ArithmeticException 如果结果的分子或分母无法表示为 {@code int}
*/
private Fraction addSub(final Fraction fraction, final boolean isAdd) {
Objects.requireNonNull(fraction, "fraction");
// zero is identity for addition.
if (numerator == 0) {
return isAdd ? fraction : fraction.negate();
}
if (fraction.numerator == 0) {
return this;
}
// if denominators are randomly distributed, d1 will be 1 about 61%
// of the time.
final int d1 = MathUtil.gcd(denominator, fraction.denominator);
if (d1 == 1) {
// result is ( (u*v' +/- u'v) / u'v')
final int uvp = mulAndCheck(numerator, fraction.denominator);
final int upv = mulAndCheck(fraction.numerator, denominator);
return new Fraction(isAdd ? addAndCheck(uvp, upv) : subAndCheck(uvp, upv), mulPosAndCheck(denominator,
fraction.denominator));
}
// the quantity 't' requires 65 bits of precision; see knuth 4.5.1
// exercise 7. we're going to use a BigInteger.
// t = u(v'/d1) +/- v(u'/d1)
final BigInteger uvp = BigInteger.valueOf(numerator).multiply(BigInteger.valueOf(fraction.denominator / d1));
final BigInteger upv = BigInteger.valueOf(fraction.numerator).multiply(BigInteger.valueOf(denominator / d1));
final BigInteger t = isAdd ? uvp.add(upv) : uvp.subtract(upv);
// but d2 doesn't need extra precision because
// d2 = gcd(t,d1) = gcd(t mod d1, d1)
final int tmodd1 = t.mod(BigInteger.valueOf(d1)).intValue();
final int d2 = tmodd1 == 0 ? d1 : MathUtil.gcd(tmodd1, d1);
// result is (t/d2) / (u'/d1)(v'/d2)
final BigInteger w = t.divide(BigInteger.valueOf(d2));
if (w.bitLength() > 31) {
throw new ArithmeticException("overflow: numerator too large after multiply");
}
return new Fraction(w.intValue(), mulPosAndCheck(denominator / d1, fraction.denominator / d2));
}
/**
* 将此分数除以另一个分数
*
* @param fraction 要除以的分数不能为空
* @return 包含结果值的 {@code Fraction} 实例
* @throws NullPointerException 如果传入的分数为 {@code null}
* @throws ArithmeticException 如果要除以的分数为零
* @throws ArithmeticException 如果结果的分子或分母超出 {@code Integer.MAX_VALUE}
*/
public Fraction divideBy(final Fraction fraction) {
Objects.requireNonNull(fraction, "fraction");
if (fraction.numerator == 0) {
throw new ArithmeticException("The fraction to divide by must not be zero");
}
return multiplyBy(fraction.invert());
}
/**
* 获取分数的分母部分
*
* @return 分数的分母部分
*/
public int getDenominator() {
return denominator;
}
/**
* 获取分数的分子部分
*
* <p>此方法可能返回一个大于分母的值即一个假分数例如 7/4 中的七</p>
*
* @return 分数的分子部分
*/
public int getNumerator() {
return numerator;
}
/**
* 获取真分数的分子部分始终为正数
*
* <p>一个假分数 7/4 可以分解为真分数 1 3/4
* 此方法返回真分数中的 3</p>
*
* <p>如果分数为负数例如 -7/4可以分解为 -1 3/4
* 因此此方法返回正数的真分数分子 3</p>
*
* @return 真分数的分子部分始终为正数
*/
public int getProperNumerator() {
return Math.abs(numerator % denominator);
}
/**
* 获取真分数的整数部分包括符号
*
* <p>一个假分数 7/4 可以分解为真分数 1 3/4
* 此方法返回真分数中的 1</p>
*
* <p>如果分数为负数例如 -7/4可以分解为 -1 3/4
* 因此此方法返回真分数的整数部分 -1</p>
*
* @return 真分数的整数部分包括符号
*/
public int getProperWhole() {
return numerator / denominator;
}
/**
* 获取此分数的倒数 (1/fraction)
*
* <p>返回的分数不会被约简</p>
*
* @return 一个新分数实例其分子和分母互换
* @throws ArithmeticException 如果分数表示零
*/
public Fraction invert() {
if (numerator == 0) {
throw new ArithmeticException("Unable to invert zero.");
}
if (numerator == Integer.MIN_VALUE) {
throw new ArithmeticException("overflow: can't negate numerator");
}
if (numerator < 0) {
return new Fraction(-denominator, -numerator);
}
return new Fraction(denominator, numerator);
}
/**
* 将此分数与另一个分数相乘并返回约简后的结果
*
* @param fraction 要乘以的分数不能为空
* @return 包含结果值的 {@code Fraction} 实例
* @throws NullPointerException 如果传入的分数为 {@code null}
* @throws ArithmeticException 如果结果的分子或分母超出 {@code Integer.MAX_VALUE}
*/
public Fraction multiplyBy(final Fraction fraction) {
Objects.requireNonNull(fraction, "fraction");
if (numerator == 0 || fraction.numerator == 0) {
return ZERO;
}
// knuth 4.5.1
// make sure we don't overflow unless the result *must* overflow.
final int d1 = MathUtil.gcd(numerator, fraction.denominator);
final int d2 = MathUtil.gcd(fraction.numerator, denominator);
return ofReduced(mulAndCheck(numerator / d1, fraction.numerator / d2), mulPosAndCheck(denominator / d2, fraction.denominator / d1));
}
/**
* 获取此分数的负数 (-fraction)
*
* <p>返回的分数不会被约简</p>
*
* @return 一个新分数实例其分子符号相反
*/
public Fraction negate() {
// the positive range is one smaller than the negative range of an int.
if (numerator == Integer.MIN_VALUE) {
throw new ArithmeticException("overflow: too large to negate");
}
return new Fraction(-numerator, denominator);
}
/**
* 获取此分数的指定幂次
*
* <p>返回的分数已约简</p>
*
* @param power 要将分数提升到的幂次
* @return 如果幂次为一则返回 {@code this}如果幂次为零即使分数等于零则返回 {@link #ONE}
* 否则返回一个新的分数实例提升到相应的幂次
* @throws ArithmeticException 如果结果的分子或分母超出 {@code Integer.MAX_VALUE}
*/
public Fraction pow(final int power) {
if (power == 1) {
return this;
}
if (power == 0) {
return ONE;
}
if (power < 0) {
if (power == Integer.MIN_VALUE) { // MIN_VALUE can't be negated.
return this.invert().pow(2).pow(-(power / 2));
}
return this.invert().pow(-power);
}
final Fraction f = this.multiplyBy(this);
if (power % 2 == 0) { // if even...
return f.pow(power / 2);
}
return f.pow(power / 2).multiplyBy(this);
}
/**
* 将分数约简为分子和分母的最小值并返回结果
*
* <p>例如如果此分数表示 2/4则结果将是 1/2</p>
*
* @return 一个新的约简后的分数实例如果无法进一步简化则返回此实例
*/
public Fraction reduce() {
if (numerator == 0) {
return equals(ZERO) ? this : ZERO;
}
final int gcd = MathUtil.gcd(Math.abs(numerator), denominator);
if (gcd == 1) {
return this;
}
return of(numerator / gcd, denominator / gcd);
}
/**
* 从此分数中减去另一个分数的值并返回约简后的结果
*
* @param fraction 要减去的分数不能为空
* @return 包含结果值的 {@code Fraction} 实例
* @throws NullPointerException 如果传入的分数为 {@code null}
* @throws ArithmeticException 如果结果的分子或分母无法表示为 {@code int}
*/
public Fraction subtract(final Fraction fraction) {
return addSub(fraction, false /* subtract */);
}
/**
* 获取此分数的真分数形式的字符串表示格式为 X Y/Z
*
* <p>使用的格式为 '<em>整数部分</em> <em>分子</em>/<em>分母</em>'
* 如果整数部分为零则省略整数部分如果分子为零则仅返回整数部分</p>
*
* @return 分数的字符串表示形式
*/
public String toProperString() {
if (toProperString == null) {
if (numerator == 0) {
toProperString = "0";
} else if (numerator == denominator) {
toProperString = "1";
} else if (numerator == -1 * denominator) {
toProperString = "-1";
} else if ((numerator > 0 ? -numerator : numerator) < -denominator) {
// note that we do the magnitude comparison test above with
// NEGATIVE (not positive) numbers, since negative numbers
// have a larger range. otherwise numerator == Integer.MIN_VALUE
// is handled incorrectly.
final int properNumerator = getProperNumerator();
if (properNumerator == 0) {
toProperString = Integer.toString(getProperWhole());
} else {
toProperString = getProperWhole() + " " + properNumerator + "/" + getDenominator();
}
} else {
toProperString = getNumerator() + "/" + getDenominator();
}
}
return toProperString;
}
@Override
public double doubleValue() {
return (double) numerator / (double) denominator;
}
@Override
public float floatValue() {
return (float) numerator / (float) denominator;
}
@Override
public int intValue() {
return numerator / denominator;
}
@Override
public long longValue() {
return (long) numerator / denominator;
}
@Override
public int compareTo(final Fraction other) {
if (equals(other)) {
return 0;
}
// otherwise see which is less
final long first = (long) numerator * (long) other.denominator;
final long second = (long) other.numerator * (long) denominator;
return Long.compare(first, second);
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Fraction)) {
return false;
}
final Fraction other = (Fraction) obj;
return getNumerator() == other.getNumerator() && getDenominator() == other.getDenominator();
}
@Override
public int hashCode() {
if (hashCode == 0) {
// hash code update should be atomic.
hashCode = 37 * (37 * 17 + getNumerator()) + getDenominator();
}
return hashCode;
}
@Override
public String toString() {
if (toString == null) {
toString = getNumerator() + "/" + getDenominator();
}
return toString;
}
// region ----- private methods
/**
* 添加两个整数检查溢出
*
* @param x 加数
* @param y 加数
* @return {@code x+y}
* @throws ArithmeticException 如果结果不能表示为 int
*/
private static int addAndCheck(final int x, final int y) {
final long s = (long) x + (long) y;
if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) {
throw new ArithmeticException("overflow: add");
}
return (int) s;
}
/**
* 乘以两个整数并检查是否溢出
*
* @param x 一个乘数
* @param y 另一个乘数
* @return 乘积 {@code x*y}
* @throws ArithmeticException 如果结果不能表示为 int 类型
*/
private static int mulAndCheck(final int x, final int y) {
final long m = (long) x * (long) y;
if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) {
throw new ArithmeticException("overflow: mul");
}
return (int) m;
}
/**
* 乘以两个正整数并检查是否溢出
*
* @param x 一个正乘数
* @param y 另一个正乘数
* @return 乘积 {@code x*y}
* @throws ArithmeticException 如果结果不能表示为 int 类型 或者任意一个参数不是正数
*/
private static int mulPosAndCheck(final int x, final int y) {
/* assert x>=0 && y>=0; */
final long m = (long) x * (long) y;
if (m > Integer.MAX_VALUE) {
throw new ArithmeticException("overflow: mulPos");
}
return (int) m;
}
/**
* 从一个整数中减去另一个整数并检查是否溢出
*
* @param x 被减数
* @param y 减数
* @return 差值 {@code x - y}
* @throws ArithmeticException 如果结果不能表示为 int 类型
*/
private static int subAndCheck(final int x, final int y) {
final long s = (long) x - (long) y;
if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) {
throw new ArithmeticException("overflow: add");
}
return (int) s;
}
// endregion
}