将双精度数分成两部分“整数和整数”的最佳方法是什么?分数”在java中

发布于 2024-11-08 05:19:51 字数 384 浏览 2 评论 0原文

我尝试通过以下方法分离 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

您对在不将数字转换为字符串的情况下执行此操作有什么建议吗?

I have tried to separate 5.6 (for example) by the following method:

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

But what I got is:

[0] = 5.0
[1] = 0.5999999999999996

Do you have any suggestion about doing this without converting the number to string?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

顾冷 2024-11-15 05:19:52

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

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 值作为方法的输入,则无法避免这种情况。要保留准确的值,您需要使用准确表示所需值的格式,例如 分数 来自 Apache commons-math。 (对于这个 d=5.6 的具体示例,BigDecimal 也能够准确地表示它,但还有其他数字它无法准确地表示,例如 4/3)

To see what is going on, take a look at the binary representations of the numbers:

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]));

output:

4016666666666666
4014000000000000 3fe3333333333330

5.6 is 1.4 * 22, but 0.6 is 1.2 * 2-1. Because it has a lower exponent, normalization causes the mantissa to be shifted three bits to the left. The fact that the recurring terms (..66666..) were originally an approximation of the fraction 7/5 has been forgotten, and the missing bits are replaced with zeros.

Given the original double value as input to your method, there is no way to avoid this. To preserve the exact value you would need to use a format that represents the desired value exactly, e.g. Fraction from Apache commons-math. (For this specific example with d=5.6 a BigDecimal would also be able to represent it exactly, but there are other numbers it cannot represent exactly, e.g. 4/3)

晨光如昨 2024-11-15 05:19:52

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

    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))
        };
    }

(区域设置,因此我们确实得到了一个小数

poor-man solution (using String)

    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))
        };
    }

(Locale so we really get a decimal point)

焚却相思 2024-11-15 05:19:52

字符串 doubleAsString = Double.toString(123.456);

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

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

String doubleAsString = Double.toString(123.456);

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

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

只是我以为 2024-11-15 05:19:51

使用 BigDecimal进行同样的计算。 (由于其表示形式,使用双精度数存在精度问题)。

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

Use BigDecimal to do that same calculation. (using doubles has precision problems because of its representation).

  • Construct it with new BigDecimal(String.valueOf(yourDouble)) (this is still going through string, but the parts are not separated via string manipulation)
  • use bd.subtract(new BigDecimal(bd.intValue()) to determine the fraction
怎樣才叫好 2024-11-15 05:19:51

这是另一个基于 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 实际上并不完全由 double 表示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 方法实际上非常复杂。来自 的文档Double.toString

[...]

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

[...]

获取字符的代码 "5.6"< /code> 归结为 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;
}

Here is another solution based on BigDecimal (that does not go through a String).

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

As you'll note, you still won't get just 0.6 as output for the fractional part. (You can't even store 0.6 in a double!) This is due to the fact that the mathematical, real number, 5.6 is actually not represented by a double exactly as 5.6 but as 5.599999...


You could also do

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

which actually does yield [5.0, 0.6].

The BigDecimal.valueOf is in most JDK's (internally) implemented through a call to Double.toString however. But at least the string-related stuff doesn't clutter your code :-)


Good follow-up question in comment:

If it is represented as 5.599999999..., then why Double.toString(5.6) gives exactly "5.6"

The Double.toString method is actually very sophisticated. From the documentation of Double.toString:

[...]

How many digits must be printed for the fractional part of m or a? There must be at least one digit to represent the fractional part, and beyond that as many, but only as many, more digits as are needed to uniquely distinguish the argument value from adjacent values of type double. That is, suppose that x is the exact mathematical value represented by the decimal representation produced by this method for a finite nonzero argument d. Then d must be the double value nearest to x; or if two double values are equally close to x, then d must be one of them and the least significant bit of the significand of d must be 0.

[...]

The code for getting the characters "5.6" boils down to 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文