我使用下面的代码查找最低分母Rational,它位于double的某个增量中。
其基本原理是,I从数据库中提取浮点数,并在许多情况下对它们进行求和。所有这些数字都是使用简单的数学计算的,例如+、-、*和/。没有超越的数字涉及,也没有任何三角学。在大多数情况下,找到离浮点数最近的Rational会得到原来的数字,而不是将杂凑的数字相加在一起的结果。
// 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 sign and 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;
}在最近的一次使用0.000001作为我的增量的测试中,这个代码占用了75%的CPU。将其降至0.0001,这一数字大幅下降,但仍是一个重大瓶颈。
有更快的方法吗?
我对理性的实现迫使分子和分母在任何时候都要完全减少。我承认,这可能是最大的开销,但正如在维基百科中提到的,为了使中介函数正确工作,理性主义必须被完全减少。
以下是从上述站点借用并增强的完整类:
/**
* ***********************************************************************
* Immutable ADT for Rational numbers.
*
* Invariants
* -----------
* - gcd(num, den) = 1, i.e, the rational number is in reduced form
* - den >= 1, the denominator is always a positive integer
* - 0/1 is the unique representation of 0
*
* We employ some tricks to stave of overflow, but if you
* need arbitrary precision rationals, use BigRational.java.
*
* Borrowed from http://introcs.cs.princeton.edu/java/92symbolic/Rational.java.html
* because it has a mediant method.
*
************************************************************************
*/
public class Rational extends Number implements Comparable<Rational> {
public static final Rational ZERO = new Rational(0, 1);
public static final Rational ONE = new Rational(1, 1);
private long num; // the numerator
private long den; // the denominator
// create and initialize a new Rational object
public Rational(long numerator, long denominator) {
// deal with x/0
if (denominator == 0) {
throw new IllegalArgumentException("Denominator cannot be 0.");
}
// reduce fraction
long g = gcd(numerator, denominator);
num = numerator / g;
den = denominator / g;
// only needed for negative numbers
if (den < 0) {
den = -den;
num = -num;
}
}
public Rational(Rational from) {
num = from.num;
den = from.den;
}
// return the numerator and denominator of (this)
public long numerator() {
return num;
}
public long denominator() {
return den;
}
// return double precision representation of (this)
public double toDouble() {
return (double) num / den;
}
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();
}
// return string representation of (this)
@Override
public String toString() {
if (den == 1) {
return num + "";
} else {
return num + "/" + den;
}
}
public int compareTo(Rational b) {
// return { -1, 0, +1 } if a < b, a = b, or a > b
Rational a = this;
long lhs = a.num * b.den;
long rhs = a.den * b.num;
if (lhs < rhs) {
return -1;
}
if (lhs > rhs) {
return +1;
}
return 0;
}
@Override
public boolean equals(Object y) {
// is this Rational object equal to y?
if (y == null) {
return false;
}
if (y.getClass() != this.getClass()) {
return false;
}
Rational b = (Rational) y;
return compareTo(b) == 0;
}
@Override
public int hashCode() {
int hash = 5;
hash = 97 * hash + (int) (this.num ^ (this.num >>> 32));
hash = 97 * hash + (int) (this.den ^ (this.den >>> 32));
return hash;
}
// create and return a new rational (r.num + s.num) / (r.den + s.den)
public static Rational mediant(Rational r, Rational s) {
return new Rational(r.num + s.num, r.den + s.den);
}
// return gcd(|m|, |n|)
private static long gcd(long m, long n) {
if (m < 0) {
m = -m;
}
if (n < 0) {
n = -n;
}
if (0 == n) {
return m;
} else {
return gcd(n, m % n);
}
}
// return lcm(|m|, |n|)
private static long lcm(long m, long n) {
if (m < 0) {
m = -m;
}
if (n < 0) {
n = -n;
}
return m * (n / gcd(m, n)); // parentheses important to avoid overflow
}
// return a * b, staving off overflow as much as possible by cross-cancellation
public Rational times(Rational b) {
Rational a = this;
// reduce p1/q2 and p2/q1, then multiply, where a = p1/q1 and b = p2/q2
Rational c = new Rational(a.num, b.den);
Rational d = new Rational(b.num, a.den);
return new Rational(c.num * d.num, c.den * d.den);
}
// return a + b, staving off overflow
public Rational plus(Rational b) {
Rational a = this;
// special cases
if (a.compareTo(ZERO) == 0) {
return b;
}
if (b.compareTo(ZERO) == 0) {
return a;
}
// Find gcd of numerators and denominators
long f = gcd(a.num, b.num);
long g = gcd(a.den, b.den);
// add cross-product terms for numerator
Rational s = new Rational((a.num / f) * (b.den / g) + (b.num / f) * (a.den / g),
lcm(a.den, b.den));
// multiply back in
s.num *= f;
return s;
}
// return -a
public Rational negate() {
return new Rational(-num, den);
}
// return a - b
public Rational minus(Rational b) {
return plus(b.negate());
}
public Rational reciprocal() {
return new Rational(den, num);
}
// return a / b
public Rational divides(Rational b) {
Rational a = this;
return a.times(b.reciprocal());
}
// Default delta to apply.
public static final double DELTA = 0.0001;
public static Rational valueOf(double dbl) {
return valueOf(dbl, DELTA);
}
public static Rational valueOf(BigDecimal dbl) {
return valueOf(dbl.doubleValue(), DELTA);
}
public static Rational valueOf(double dbl, int digits) {
return valueOf(dbl, Math.pow(10, -digits));
}
public static Rational valueOf(BigDecimal dbl, int digits) {
return valueOf(dbl.doubleValue(), Math.pow(10, -digits));
}
// 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 sign and 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;
}
private static void print(String name, Rational r) {
System.out.println(name + "=" + r + "(" + r.toDouble() + ")");
}
private enum TestNumber {
OneTenth(0.100000001490116119384765625),
Pi(Math.PI),
E(Math.E),
OneThird(0.3333333333333),
MinusOneThird(-0.3333333333333),
ABig1(1.87344227533222758141533568138280569154340745619495504034120344898213260187710089517712780269958755185722145694193999220);
final double v;
TestNumber(double v) {
this.v = v;
}
}
private static void test2() {
for (TestNumber n : TestNumber.values()) {
print(n.name(), Rational.valueOf(n.v));
}
}
private static void test1() {
Rational x, y, z;
// 1/2 + 1/3 = 5/6
x = new Rational(1, 2);
y = new Rational(1, 3);
z = x.plus(y);
System.out.println(z);
// 8/9 + 1/9 = 1
x = new Rational(8, 9);
y = new Rational(1, 9);
z = x.plus(y);
System.out.println(z);
// 1/200000000 + 1/300000000 = 1/120000000
x = new Rational(1, 200000000);
y = new Rational(1, 300000000);
z = x.plus(y);
System.out.println(z);
// 1073741789/20 + 1073741789/30 = 1073741789/12
x = new Rational(1073741789, 20);
y = new Rational(1073741789, 30);
z = x.plus(y);
System.out.println(z);
// 4/17 * 17/4 = 1
x = new Rational(4, 17);
y = new Rational(17, 4);
z = x.times(y);
System.out.println(z);
// 3037141/3247033 * 3037547/3246599 = 841/961
x = new Rational(3037141, 3247033);
y = new Rational(3037547, 3246599);
z = x.times(y);
System.out.println(z);
// 1/6 - -4/-8 = -1/3
x = new Rational(1, 6);
y = new Rational(-4, -8);
z = x.minus(y);
System.out.println(z);
}
// test client
public static void main(String[] args) {
//test1();
test2();
}
// Implement Number.
@Override
public int intValue() {
return (int) doubleValue();
}
@Override
public long longValue() {
return (long) doubleValue();
}
@Override
public float floatValue() {
return (float) doubleValue();
}
@Override
public double doubleValue() {
return toDouble();
}
}发布于 2014-04-30 18:56:36
为什么不简单地让java.util.BigDecimal来做繁重的工作:
public Rational valueOf(double d) {
BigDecimal bigDecimal = BigDecimal.valueOf(d);
long numerator = bigDecimal.unscaledValue().longValue();
long denominator = pow(10L, bigDecimal.scale());
return new Rational(numerator, denominator);
}
private static long pow(long base, int exponent) {
long result = 1;
for (int i = 0; i < exponent; i++) {
result *= base;
}
return result;
}这只需要对超出范围的值进行额外的检查。
https://codereview.stackexchange.com/questions/48577
复制相似问题