首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >找到最接近于双的理性--有没有一个更有效的机制?

找到最接近于双的理性--有没有一个更有效的机制?
EN

Code Review用户
提问于 2014-04-30 14:17:42
回答 1查看 1.9K关注 0票数 8

我使用下面的代码查找最低分母Rational,它位于double的某个增量中。

其基本原理是,I从数据库中提取浮点数,并在许多情况下对它们进行求和。所有这些数字都是使用简单的数学计算的,例如+、-、*和/。没有超越的数字涉及,也没有任何三角学。在大多数情况下,找到离浮点数最近的Rational会得到原来的数字,而不是将杂凑的数字相加在一起的结果。

代码语言:javascript
复制
// 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,这一数字大幅下降,但仍是一个重大瓶颈。

有更快的方法吗?

我对理性的实现迫使分子和分母在任何时候都要完全减少。我承认,这可能是最大的开销,但正如在维基百科中提到的,为了使中介函数正确工作,理性主义必须被完全减少。

以下是从上述站点借用并增强的完整类:

代码语言:javascript
复制
/**
 * ***********************************************************************
 * 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();
  }

}
EN

回答 1

Code Review用户

发布于 2014-04-30 18:56:36

为什么不简单地让java.util.BigDecimal来做繁重的工作:

代码语言:javascript
复制
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;
}

这只需要对超出范围的值进行额外的检查。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/48577

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档