【问题标题】:Recover the original number from a float从浮点数中恢复原始数字
【发布时间】:2014-03-06 12:08:37
【问题描述】:

数字以浮点数/双精度数等形式存储在数据库中(不受我的控制)。

当我将它们拔出时,它们会损坏 - 例如,0.1 会以0.100000001490116119384765625 的形式出现(格式化后)。

有没有可靠的方法来恢复这些数字?

我尝试过new BigDecimal(((Number) o).doubleValue())BigDecimal.valueOf(((Number) o).doubleValue()),但这些都不起作用。我仍然得到损坏的结果。

我知道我可以对小数位数做出假设并将它们四舍五入,但这会破坏例如故意为 0.33333333333 的数字。

有没有一种适用于大多数理性人的简单方法?

我想我在问有没有一种简单的方法可以找到在浮点数的小增量内的最小有理数?

【问题讨论】:

  • 我昨天问了一个类似的问题:stackoverflow.com/q/22189081/2959259>
  • 我不会称它们为“损坏的”——它实际上只是成为双打工作方式的牺牲品。这也会给.3333....之类的东西带来问题,它们也无法精确表示(除非它被存储为实际分数,否则你无法判断它是1/3还是实际上.3333) .这些值代表什么?也许丢失确切的值并不重要?如果您确实需要实际精度,为什么没有写入匹配的数据库?

标签: java database numbers bigdecimal


【解决方案1】:

您可以将数据库中的数字存储为字符串,在检索时只需 parseDouble() 它们。这样号码就不会被损坏,它会和你存放在那里的一样。

【讨论】:

  • 我无法控制在数据库中创建记录的系统。
【解决方案2】:

有没有一种简单的方法可以找到一个在浮点数 0.00001 以内的有理数?

这称为舍入。

double d = ((Number) o).doubleValue();
double d2 = Math.round(d * 1e5) / 1e5;
BigDecimal bd = BigDecimal.valueOf(d2);

或者您可以使用 BigDecimal 执行舍入(我避免使用 BigDecimal,因为一旦您知道如何使用双精度舍入,它就会不必要地缓慢)

double d = ((Number) o).doubleValue();
BigDecimal bd = BigDecimal.valueOf(d).setScale(5, RoundingMode.HALF_UP);

注意:除非您了解它的作用,否则切勿使用 new BigDecimal(double)BigDecial.valueOf(double) 很可能是您想要的。

【讨论】:

  • 这不会满足我对 0.333333333333333333333 的要求。在那种情况下,我仍然想要 0.333333333333333333333。 BigDecial.valueOf(double) 没有帮助。
  • @OldCurmudgeon 如果您使用double(或任何其他类型)丢失了信息,那么使用BigDecimal 将不会恢复它。
【解决方案3】:

这是我所做的大棒方式 - 我欢迎更优雅的解决方案。

我从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.1000000014901161193847656251/3 用于 1.0/3.0 和经典的 355/113 用于 π。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-12
    • 1970-01-01
    • 1970-01-01
    • 2017-08-06
    • 2011-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多