Перемещение десятичных знаков в двойном формате
Итак, у меня есть двойной набор равный 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 миллионов долларов и что переполнило для Чемпионата мира или Олимпиады, я забыл.
Я поклялся хранить тайну. Ну что ж. В academea, если это хорошо, вы публикуете; в промышленности вы держите его секрет.
вы можете попробовать целочисленное представление
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 конструктор специально упоминает, что лучше использовать строковый конструктор, чем числовой конструктор. Предельная точность также зависит от дополнительного MathContext.
по в то bigdecimal у javadoc возможно чтобы создать BigDecimal, который является ровно равно 0.1, если вы используете строковый конструктор.
Да, есть. С каждой двойной операцией вы можете потерять точность, но количество точности отличается для каждой операции и может быть сведено к минимуму, выбрав правильную последовательность операций. Например, при умножении набора чисел, лучше всего сортировать набор по экспоненте перед умножением.
любая приличная книга о хрусте чисел описывает это. Например: 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
.