Перемещение десятичных знаков в двойном
Таким образом, у меня есть двойной набор, равный 1234, я хочу переместить десятичное место, чтобы сделать его 12,34
Так что для этого я умножаю.1 до 1234 два раза, вроде как
double x = 1234;
for(int i=1;i<=2;i++)
{
x = x*.1;
}
System.out.println(x);
Это напечатает результат, "12.340000000000002"
Есть ли способ, без простого форматирования его до двух десятичных разрядов, чтобы правильно хранить двойное хранилище 12,34?
9 ответов
Если вы используете double
или же float
, вы должны использовать округление или ожидать появления ошибок округления. Если вы не можете сделать это, используйте BigDecimal
,
Ваша проблема в том, что 0.1 не является точным представлением, и, выполнив вычисление дважды, вы усугубите эту ошибку.
Тем не менее, 100 могут быть представлены точно, поэтому попробуйте:
double x = 1234;
x /= 100;
System.out.println(x);
который печатает:
12.34
Это работает, потому что Double.toString(d)
выполняет небольшое количество округлений от вашего имени, но это не так много. Если вам интересно, как это может выглядеть без округления:
System.out.println(new BigDecimal(0.1));
System.out.println(new BigDecimal(x));
печатает:
0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375
Короче говоря, округление неизбежно для разумных ответов с плавающей запятой независимо от того, делаете вы это явно или нет.
Замечания: x / 100
а также x * 0.01
не совсем то же самое, когда дело доходит до ошибки округления. Это связано с тем, что ошибка округления для первого выражения зависит от значений x, тогда как 0.01
во втором исправлена ошибка округления.
for(int i=0;i<200;i++) {
double d1 = (double) i / 100;
double d2 = i * 0.01;
if (d1 != d2)
System.out.println(d1 + " != "+d2);
}
печать
0.35 != 0.35000000000000003
0.41 != 0.41000000000000003
0.47 != 0.47000000000000003
0.57 != 0.5700000000000001
0.69 != 0.6900000000000001
0.7 != 0.7000000000000001
0.82 != 0.8200000000000001
0.83 != 0.8300000000000001
0.94 != 0.9400000000000001
0.95 != 0.9500000000000001
1.13 != 1.1300000000000001
1.14 != 1.1400000000000001
1.15 != 1.1500000000000001
1.38 != 1.3800000000000001
1.39 != 1.3900000000000001
1.4 != 1.4000000000000001
1.63 != 1.6300000000000001
1.64 != 1.6400000000000001
1.65 != 1.6500000000000001
1.66 != 1.6600000000000001
1.88 != 1.8800000000000001
1.89 != 1.8900000000000001
1.9 != 1.9000000000000001
1.91 != 1.9100000000000001
Нет - если вы хотите хранить десятичные значения точно, используйте BigDecimal
, double
просто не может точно представить число, такое как 0,1, больше, чем вы можете написать значение третьего точно с конечным числом десятичных цифр.
Если это просто форматирование, попробуйте printf
double x = 1234;
for(int i=1;i<=2;i++)
{
x = x*.1;
}
System.out.printf("%.2f",x);
выход
12.34
В финансовом программном обеспечении принято использовать целые числа за копейки. В школе нас учили, как использовать фиксированную точку вместо плавающего, но обычно это степени двух. Хранение пенни в целых числах также можно назвать "фиксированной точкой".
int i=1234;
printf("%d.%02d\r\n",i/100,i%100);
В классе нас вообще спрашивали, какие числа могут быть точно представлены в базе.
За base=p1^n1*p2^n2
... вы можете представить любое N, где N=n*p1^m1*p2^m2.
Позволять base=14=2^1*7^1
... Вы можете представить 1/7 1/14 1/28 1/49, но не 1/3
Я знаю о финансовом программном обеспечении - я конвертировал финансовые отчеты Ticketmaster из VAX asm в PASCAL. У них был свой formatln() с кодами за копейки. Причиной преобразования было 32-х разрядные целые числа, которых уже недостаточно. Я забыл, что +/- 2 миллиарда копеек - это 20 миллионов долларов, и это переполнено на Чемпионат мира или Олимпийские игры.
Я был приведен к присяге в тайне. Ну что ж. В академии, если это хорошо, вы публикуете; в промышленности ты держишь это в секрете.
Вы можете попробовать представление целых чисел
int i =1234;
int q = i /100;
int r = i % 100;
System.out.printf("%d.%02d",q, r);
Это связано с тем, что компьютеры хранят числа с плавающей запятой. Они не делают это точно. Как программист, вы должны прочитать это руководство с плавающей запятой, чтобы ознакомиться с испытаниями и невзгодами обработки чисел с плавающей запятой.
Забавно, что во многих постах упоминается использование BigDecimal, но никто не мешает дать правильный ответ на основе BigDecimal? Потому что даже с BigDecimal вы все равно можете пойти не так, как показано в этом коде
String numstr = "1234";
System.out.println(new BigDecimal(numstr).movePointLeft(2));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal(0.01)));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal("0.01")));
Дает этот вывод
12.34
12.34000000000000025687785232264559454051777720451354980468750
12.34
В конструкторе BigDecimal конкретно упоминается, что лучше использовать конструктор String, чем числовой конструктор. Предельная точность также зависит от опционального MathContext.
Согласно BigDecimal Javadoc можно создать BigDecimal, который в точности равен 0,1, при условии, что вы используете конструктор String.
Да, есть. С каждой двойной операцией вы можете потерять точность, но степень точности отличается для каждой операции и может быть минимизирована путем выбора правильной последовательности операций. Например, при умножении набора чисел перед сортировкой лучше всего сортировать набор по показателю степени.
Любая приличная книга о сокращении чисел описывает это. Например: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
И чтобы ответить на ваш вопрос:
Используйте деление вместо умножения, чтобы получить правильный результат.
double x = 1234;
for(int i=1;i<=2;i++)
{
x = x / 10.0;
}
System.out.println(x);
Нет, поскольку типы Java с плавающей точкой (в действительности все типы с плавающей точкой) являются компромиссом между размером и точностью. Хотя они очень полезны для многих задач, если вам нужна произвольная точность, вы должны использовать BigDecimal
,