【问题标题】:What is the best way to separate double into two parts "integer & fraction" in java在java中将double分成两部分“整数和分数”的最佳方法是什么
【发布时间】:2011-08-29 03:03:18
【问题描述】:

我曾尝试通过以下方法分离5.6(例如):

private static double[] method(double d)
{
    int integerPart = 0;
    double fractionPart = 0.0;
    integerPart = (int) d;
    fractionPart = d - integerPart;
    return new double[]{integerPart, fractionPart};
}

但我得到的是:

[0] = 5.0
[1] = 0.5999999999999996

你有什么建议不要把数字转换成字符串吗?

【问题讨论】:

  • 用另一个建议更新了我的答案:-)

标签: java math double fractions


【解决方案1】:

String doubleAsString = Double.toString(123.456);

String beforeDecimal=doubleAsString.substring(0,doubleAsString.indexOf(".")); //123

String afterDecimal=doubleAsString.substring(doubleAsString.indexOf(".")+1); //456

【讨论】:

  • 你为什么要回答一个 3 岁的问题?一个好的答案需要更多的解释。
【解决方案2】:

这是基于BigDecimal 的另一种解决方案(不通过String)。

private static double[] method(double d) {
    BigDecimal bd = new BigDecimal(d);
    return new double[] { bd.intValue(),
                          bd.remainder(BigDecimal.ONE).doubleValue() };
}

您会注意到,您仍然不会只得到0.6 作为小数部分的输出。 (你甚至不能将 0.6 存储在 double 中!)这是因为数学实数 5.6 实际上并没有用 5.6 的双精度表示,而是 5.599999...


你也可以

private static double[] method(double d) {
    BigDecimal bd = BigDecimal.valueOf(d);
    return new double[] { bd.intValue(),
                          bd.remainder(BigDecimal.ONE).doubleValue() };
}

实际上确实产生了[5.0, 0.6]

BigDecimal.valueOf 在大多数 JDK(内部)中通过调用 Double.toString 来实现。但至少与字符串相关的东西不会弄乱你的代码:-)


评论中有很好的后续问题:

如果表示为 5.599999999...,那为什么Double.toString(5.6) 正好给出"5.6"

Double.toString 方法实际上非常复杂。来自documentation of Double.toString

[...]

m或a的小数部分必须打印多少位?必须至少有一个数字来表示小数部分,除此之外,必须有尽可能多的数字,但仅能将参数值与相邻的 double 类型值区分开来。也就是说,假设x 是由该方法为有限非零参数 d 生成的十进制表示所表示的精确数学值。那么 d 必须是最接近 x 的 double 值;或者如果两个 double 值同样接近 x,则 d 必须是其中之一,并且 d 的有效位的最低有效位必须为 0。

[...]

获取字符"5.6"的代码归结为FloatingDecimal.getChars

private int getChars(char[] result) {
    assert nDigits <= 19 : nDigits; // generous bound on size of nDigits
    int i = 0;
    if (isNegative) { result[0] = '-'; i = 1; }
    if (isExceptional) {
        System.arraycopy(digits, 0, result, i, nDigits);
        i += nDigits;
    } else {
        if (decExponent > 0 && decExponent < 8) {
            // print digits.digits.
            int charLength = Math.min(nDigits, decExponent);
            System.arraycopy(digits, 0, result, i, charLength);
            i += charLength;
            if (charLength < decExponent) {
                charLength = decExponent-charLength;
                System.arraycopy(zero, 0, result, i, charLength);
                i += charLength;
                result[i++] = '.';
                result[i++] = '0';
            } else {
                result[i++] = '.';
                if (charLength < nDigits) {
                    int t = nDigits - charLength;
                    System.arraycopy(digits, charLength, result, i, t);
                    i += t;
                } else {
                    result[i++] = '0';
                }
            }
        } else if (decExponent <=0 && decExponent > -3) {
            result[i++] = '0';
            result[i++] = '.';
            if (decExponent != 0) {
                System.arraycopy(zero, 0, result, i, -decExponent);
                i -= decExponent;
            }
            System.arraycopy(digits, 0, result, i, nDigits);
            i += nDigits;
        } else {
            result[i++] = digits[0];
            result[i++] = '.';
            if (nDigits > 1) {
                System.arraycopy(digits, 1, result, i, nDigits-1);
                i += nDigits-1;
            } else {
                result[i++] = '0';
            }
            result[i++] = 'E';
            int e;
            if (decExponent <= 0) {
                result[i++] = '-';
                e = -decExponent+1;
            } else {
                e = decExponent-1;
            }
            // decExponent has 1, 2, or 3, digits
            if (e <= 9) {
                result[i++] = (char)(e+'0');
            } else if (e <= 99) {
                result[i++] = (char)(e/10 +'0');
                result[i++] = (char)(e%10 + '0');
            } else {
                result[i++] = (char)(e/100+'0');
                e %= 100;
                result[i++] = (char)(e/10+'0');
                result[i++] = (char)(e%10 + '0');
            }
        }
    }
    return i;
}

【讨论】:

  • 如果表示为5.599999999...,那为什么System.out.println(Double.toString(5.6));给出的正好是5.6
  • 现在这是一个很好的问题。 Double.toString(5.6) 非常复杂。看看the documentation。 (简单地说:它确实尝试打印双精度的确切值,而是最简单的值,它比任何其他值更接近表示的值。)
  • "" + d 被转换为 String.valueOf(d) 进而调用 Double.toString(..) (或者编译器可能直接转换为 D.toString() )
  • 对。我只是采取了懒惰的方法;)
  • (如果有人感兴趣,请为此创建follow-up question。)
【解决方案3】:

穷人解决方案(使用字符串)

    static double[] sp(double d) {
        String str = String.format(Locale.US, "%f", d);
        int i = str.indexOf('.');
        return new double[] {
            Double.parseDouble(str.substring(0, i)),
            Double.parseDouble(str.substring(i))
        };
    }

(语言环境,所以我们真的得到一个小数

【讨论】:

    【解决方案4】:

    要了解发生了什么,请查看数字的二进制表示:

    double d = 5.6;
    System.err.printf("%016x%n", Double.doubleToLongBits(d));
    double[] parts = method(d);
    System.err.printf("%016x %016x%n",
                      Double.doubleToLongBits(parts[0]),
                      Double.doubleToLongBits(parts[1]));
    

    输出:

    4016666666666666
    4014000000000000 3fe3333333333330
    

    5.6 是 1.4 * 22,但 0.6 是 1.2 * 2-1。因为它具有较低的指数,归一化导致尾数向左移动三位。重复项 (..66666..) 最初是分数 7/5 的近似值这一事实已被遗忘,缺失的位被零替换。

    鉴于原始 double 值作为您方法的输入,因此无法避免这种情况。要保留确切的值,您需要使用准确表示所需值的格式,例如Fraction 来自 Apache commons-math。 (对于这个带有d=5.6 的具体示例,BigDecimal 也可以准确表示它,但还有其他数字不能准确表示,例如 4/3)

    【讨论】:

      【解决方案5】:

      使用BigDecimal 进行同样的计算。 (由于它的表示,使用双精度有精度问题)。

      • 使用new BigDecimal(String.valueOf(yourDouble)) 构造它(这仍在通过字符串,但部分未通过字符串操作分隔)
      • 使用bd.subtract(new BigDecimal(bd.intValue()) 确定分数

      【讨论】:

      • 为什么不直接使用 new BigDecimal(double val) 构造?
      • 因为:@Swati: groovy:000> new BigDecimal(5.6) - new BigDecimal(5.0) ===> 0.5999999999999996447286321199499070644378662109375(这不是减法的错,它是一个 BigDecimal)
      • @Swati: double 是以 2 为底,十进制是以 10 为底。小数部分是因为 5.6 十进制不能用二进制精确表示。如果你用双精度构造小数,那么已经引入了不准确性。
      • 应该是 String.valueOf,但是 +1 是一个好主意。
      • @Swati - 因为因此您没有使用大十进制的精度。它是 Josh Bloch 的一些 API 陷阱演示文稿的一部分。不要使用双重构造函数。
      猜你喜欢
      • 1970-01-01
      • 2012-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-19
      • 1970-01-01
      • 1970-01-01
      • 2020-11-21
      相关资源
      最近更新 更多