Каков наилучший способ разделить double на две части "integer & fraction" в java
Я попытался отделить 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
у вас есть какие-либо предложения об этом без преобразования числа в строку?
5 ответов
использовать 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 (внутренне) реализуется через вызов дает именно "5.6"
на Double.toString
метод на самом деле очень утонченный. От документация Double.toString
:
[...]
сколько цифр должно быть напечатано для дробной части m или a? Для представления дробной части должна быть по крайней мере одна цифра, а кроме того, столько, но только столько, сколько необходимо, чтобы однозначно отличить значение аргумента от соседних значений типа double. то есть предположим, что x-точное математическое значение представлен десятичным представлением, полученным этим методом для конечного ненулевого аргумента d. Тогда d должно быть двойным значением, ближайшим к x; или если два двойных значения одинаково близки к 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 * 22, но 0,6 1,2 * 2-1. Поскольку он имеет более низкий показатель, нормализация приводит к смещению мантиссы на три бита влево. Дело в том, что повторяющиеся термины (..66666..
) были первоначально приближение фракции 7/5 было забыто, а недостающие биты заменены на ноли.
в статье double
значение в качестве входных данных для метода, нет никакого способа, чтобы избежать этого. Чтобы сохранить точное значение, вам нужно будет использовать формат, который точно представляет желаемое значение, например Fraction
из Apache commons-математика. (Для этого конкретного примера с d=5.6
a BigDecimal
также смогло бы представить его точно, но есть другие числа, которые он не может точно представить, например 4/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))
};
}
(Locale, поэтому мы действительно получаем decimal точка)
строка doubleAsString = Double.toString (123.456);
String beforeDecimal=doubleAsString.подстрока (0, doubleAsString.indexOf(".")); //123
строка afterDecimal=doubleAsString.подстрока (doubleAsString.indexOf(".")+1); //456