在java中将double分成两部分“integer&fraction”的最佳方法是什么?
我试图通过以下方法分离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
如果不将数字转换为字符串,您对此有何建议?
使用BigDecimal
进行相同的计算。 (因为它的表现,使用双精度具有精度问题)。
- 用
new BigDecimal(String.valueOf(yourDouble))
构造它(这仍然是通过字符串,但部分不通过字符串操作分开) - 使用
bd.subtract(new BigDecimal(bd.intValue())
来确定分数
这是另一种基于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
来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"
的代码归结为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; }
要查看发生了什么,请查看数字的二进制表示:
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 * 2 2 ,但0.6是1.2 * 2 -1 。 因为它具有较低的指数,所以归一化导致尾数向左移位三位。 已经忘记了重复项( ..66666..
)最初是7/5分数的近似值,并且缺少的位被零替换。
给定原始的double
值作为方法的输入,没有办法避免这种情况。 要保留确切的值,您需要使用完全表示所需值的格式,例如Apache commons-math中的Fraction
。 (对于d=5.6
的具体示例, BigDecimal
也能够准确地表示它,但是还有其他数字不能完全表示,例如4/3)
穷人解决方案(使用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所以我们真的得到一个小数点)
String doubleAsString = Double.toString(123.456);
String beforeDecimal = doubleAsString.substring(0,doubleAsString.indexOf(“。”)); // 123
字符串afterDecimal = doubleAsString.substring(doubleAsString.indexOf(“。”)+ 1); // 456