плавать, чтобы удвоить назначение - PullRequest
6 голосов
/ 11 ноября 2010

Рассмотрим следующий фрагмент кода

float num = 281.583f;
int amount = (int) Math.round(num*100f);
float rounded = amount/100.0f;
double dblPrecision = rounded;
double dblPrecision2 = num;
System.out.println("num : " + num + " amount: " + amount + " rounded: " + rounded + " dbl: " + dblPrecision + " dbl2: " + dblPrecision2);

Я получаю вывод

num : 281.583 amount: 28158 rounded: 281.58 dbl: 281.5799865722656 dbl2: 281.5830078125

Почему существует приближение, когда число с плавающей запятой присваивается двойной переменной?

Ответы [ 6 ]

6 голосов
/ 11 ноября 2010

Аппроксимация фактически имеет место при преобразовании десятичной дроби в float.Я могу удивить вас, но 281.583 не может быть представлен точно как число с плавающей запятой на ПК.это происходит потому, что числа с плавающей запятой представляются в виде суммы двоичных дробей в ПК.0.5, 0.25 и 0.125 могут быть преобразованы точно, но не 0.583.

Плавающие (и двойные) представлены как Σ( 1/2^i*Bi ), где Bi - i-й бит (0|1).0.625 = 1/2 + 1/4 например.Проблема заключается в том, что не все десятичные дроби можно преобразовать в конечную сумму двоичных дробей.

Вот как преобразуется это число (первая строка - определение столбцов).

i|  *2 and trim|    Bit value|  (2^-1)*bit
    0,583       
1   1,166   1   0,5
2   0,332   0   0
3   0,664   0   0
4   1,328   1   0,0625
5   0,656   0   0
6   1,312   1   0,015625
7   0,624   0   0
8   1,248   1   0,00390625
9   0,496   0   0
10  0,992   0   0
11  1,984   1   0,000488281
12  1,968   1   0,000244141
13  1,936   1   0,00012207
14  1,872   1   6,10352E-05
15  1,744   1   3,05176E-05
16  1,488   1   1,52588E-05
17  0,976   0   0
18  1,952   1   3,8147E-06
19  1,904   1   1,90735E-06
        SUM=    0,582998276
4 голосов
/ 11 ноября 2010

Поскольку числа с плавающей запятой представляют собой двоичные дроби и, таким образом, могут представлять ваше десятичное число только приблизительно.Аппроксимация происходит, когда литерал 281.583f в исходном коде анализируется в значение с плавающей точкой IEEE 754.

С самими числами с плавающей точкой это скрывается, потому что println печатает

столько, но только столько, сколько цифр необходимо, чтобы однозначно отличить значение аргумента от смежных значений типа float.

Во многих случаях это означает десятичное числоЗначение литерала будет напечатано.Однако, когда вы присваиваете значение double, «смежные значения типа double» обычно намного, намного ближе, чем значения типа float, поэтому вы можете увидеть истинное значение вашего аппроксимированного числа с плавающей запятой.

Подробнее см. Руководство с плавающей точкой .

0 голосов
/ 12 ноября 2010

Плавающие и двойные на самом деле имеют одинаковое значение внутри;они просто напечатаны по-разному.Добавьте эти строки в вашу программу, чтобы просмотреть их в шестнадцатеричном виде:

System.out.printf("num:           %a\n",num);
System.out.printf("dblPrecision2: %a\n",dblPrecision2);

System.out.printf("rounded:       %a\n",rounded);
System.out.printf("dblPrecision:  %a\n",dblPrecision);

Это напечатает

num:           0x1.19954p8
dblPrecision2: 0x1.19954p8
rounded:       0x1.19947ap8
dblPrecision:  0x1.19947ap8

num = dblPrecision2 и округлено = dblPrecision.

Сейчас 0x1.19954p8= 100011001.100101010100 = 281,5830078125 и 0x1,19947ap8 = 100011001.1001010001111010 = 281,579986572265625.Все, что происходит, это то, что при печати они округляются по-разному (числа с плавающей точкой округляются до меньшего числа цифр, чем у двойных).

0 голосов
/ 11 ноября 2010

Короче говоря, не используйте float, если вам действительно не нужно. Вы потеряете точность и, скорее всего, сэкономите очень мало. Используйте двойной, и вы сэкономите много горя.

double num = 281.583;
long amount = (long) (num*100);
double rounded = (double) amount/100;
double dblPrecision = rounded;
double dblPrecision2 = num;

печать

num : 281.583 amount: 28158 rounded: 281.58 dbl: 281.58 dbl2: 281.583
0 голосов
/ 11 ноября 2010

Насколько я понимаю, ваша проблема в том, почему этот код ...

float f = 281.583f;
System.out.println(f);
System.out.println((double) f);

... печать

281.583
281.5830078125

( эй, двойной обеспечивает больше точность! )

Вот почему ...

Введите 438ccaa0 (шестнадцатеричный формат битов, представляющих 281.583f, как указано Integer.toHexString(Float.floatToRawIntBits(281.583f))) в форму здесь . Что вы увидите, так это то, что число с плавающей точкой на самом деле представлено как 281.58301. (@Michael Borgwardt отвечает, почему это так не печатается.)

Таким образом, 281.583 печатается для 281.58301, когда представлено как число с плавающей точкой. Но когда вы конвертируете 281.58301 в двойное число, вы можете на ближе к 281.58301, чем 281.583!

Глядя на вычисления вышеупомянутой веб-страницы, вы можете приблизиться к 281.58300781250000, поэтому вы видите печатное значение 281.5830078125.

0 голосов
/ 11 ноября 2010

Приближение есть все время. Просто так получается, что дубль дает достаточно лишних битов, и появляется дополнительный материал.

281.583, например, в двоичном формате (с большим количеством цифр, но с двойной точностью): 100011001.1001_0101_0011_1111_0111_1100_1110_1101_1001 ...

Число с плавающей запятой допускает около 23 битов, в то время как double допускает около 52 битов. (Точно не помню) 100011001.1001_0101_0011_11, что составляет 281,582946777 в десятичном виде.

Для справки: с одинарной точностью сохраняется до 7 десятичных цифр, а с двойной точностью - до 16 десятичных. Это включает в себя все числа, так что ваша точность примерно на 1 цифру меньше точности числа с плавающей точкой.

...