float к двойному назначению


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

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 5

6 ответов:

Аппроксимация на самом деле имеет место, когда вы преобразуете десятичную дробь в float. Я могу удивить вас, но 281.583 не может быть представлено точно Как число с плавающей точкой в PC. это происходит потому, что числа с плавающей запятой представлены в виде суммы двоичных дробей в ПК. 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

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

С самими поплавками это замалчивается, потому что println отпечатки

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

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

Для получения более подробной информации прочитайте руководство с плавающей запятой.

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

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

Float допускает около 23 бит, в то время как double допускает около 52 бит. (Не могу точно вспомнить) 100011001.1001_0101_0011_11, что равно 281.582946777 в десятичной системе счисления.

В качестве эталона, единичная точность хранит до 7 десятичных разрядов и двойной точности до 16 десятичных разрядов. Это включает в себя все числа, так что ваш только около 1 цифры меньше, чем точность float.

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

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

...отпечатки

281.583
281.5830078125

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

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

Введите 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 печатается.

Короче говоря, не используйте 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

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

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.

Теперь 0х1.19954p8 = 100011001.100101010100 = 281.5830078125, и 0x1.19947ap8 = 100011001.1001010001111010 = 281.579986572265625. Все, что происходит, это то, что они округляются по-разному при печати (поплавки округляются до меньшее количество цифр, чем удваивается).