Перемещение десятичных знаков в двойном формате


Итак, у меня есть двойной набор равный 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 90

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.