这是我所做的大棒方式 - 我欢迎更优雅的解决方案。
我从Rational 中选择了一个implementation,其中有一个为我准备好的mediant 方法。
我将其重构为使用long 而不是int,然后添加:
// Default delta to apply.
public static final double DELTA = 0.000001;
public static Rational valueOf(double dbl) {
return valueOf(dbl, DELTA);
}
// Create a good rational for the value within the delta supplied.
public static Rational valueOf(double dbl, double delta) {
// Primary checks.
if ( delta <= 0.0 ) {
throw new IllegalArgumentException("Delta must be > 0.0");
}
// Remove the integral part.
long integral = (long) Math.floor(dbl);
dbl -= integral;
// The value we are looking for.
final Rational d = new Rational((long) ((dbl) / delta), (long) (1 / delta));
// Min value = d - delta.
final Rational min = new Rational((long) ((dbl - delta) / delta), (long) (1 / delta));
// Max value = d + delta.
final Rational max = new Rational((long) ((dbl + delta) / delta), (long) (1 / delta));
// Start the fairey sequence.
Rational l = ZERO;
Rational h = ONE;
Rational found = null;
// Keep slicing until we arrive within the delta range.
do {
// Either between min and max -> found it.
if (found == null && min.compareTo(l) <= 0 && max.compareTo(l) >= 0) {
found = l;
}
if (found == null && min.compareTo(h) <= 0 && max.compareTo(h) >= 0) {
found = h;
}
if (found == null) {
// Make the mediant.
Rational m = mediant(l, h);
// Replace either l or h with mediant.
if (m.compareTo(d) < 0) {
l = m;
} else {
h = m;
}
}
} while (found == null);
// Bring back the sign and the integral.
if (integral != 0) {
found = found.plus(new Rational(integral, 1));
}
// That's me.
return found;
}
public BigDecimal toBigDecimal() {
// Do it to just 4 decimal places.
return toBigDecimal(4);
}
public BigDecimal toBigDecimal(int digits) {
// Do it to n decimal places.
return new BigDecimal(num).divide(new BigDecimal(den), digits, RoundingMode.DOWN).stripTrailingZeros();
}
基本上 - 算法从 0-1 的范围开始。在每次迭代中,我检查范围的任一端是否位于我的 d-delta - d+delta 范围之间。如果是这样,我们已经找到了答案。
如果没有找到答案,我们将取两个限制中的mediant 并用它替换其中一个限制。选择要替换的限制以确保限制始终围绕d。
这实质上是在 0 和 1 之间进行二分查找,以找到落在所需范围内的第一个有理数。
从数学上讲,我爬下Stern-Brocot Tree,选择让我封闭所需数字的分支,直到我落入所需的三角洲。
注意:我还没有完成测试,但它肯定会找到 1/10 用于我输入的 0.100000001490116119384765625 和 1/3 用于 1.0/3.0 和经典的 355/113 用于 π。