Вы можете использовать Float.intBitsToFloat()
и Float.floatToIntBits()
для преобразования их в примитивные значения с плавающей точкой. Если вы можете жить с усеченной точностью (в отличие от округления), преобразование должно быть реализовано с помощью нескольких сдвигов.
Теперь я приложил немного больше усилий, и все оказалось не так просто, как я ожидал в начале. Эта версия теперь протестирована и проверена во всех аспектах, которые я мог себе представить, и я очень уверен, что она дает точные результаты для всех возможных входных значений. Он поддерживает точное округление и субнормальное преобразование в любом направлении.
// ignores the higher 16 bits
public static float toFloat( int hbits )
{
int mant = hbits & 0x03ff; // 10 bits mantissa
int exp = hbits & 0x7c00; // 5 bits exponent
if( exp == 0x7c00 ) // NaN/Inf
exp = 0x3fc00; // -> NaN/Inf
else if( exp != 0 ) // normalized value
{
exp += 0x1c000; // exp - 15 + 127
if( mant == 0 && exp > 0x1c400 ) // smooth transition
return Float.intBitsToFloat( ( hbits & 0x8000 ) << 16
| exp << 13 | 0x3ff );
}
else if( mant != 0 ) // && exp==0 -> subnormal
{
exp = 0x1c400; // make it normal
do {
mant <<= 1; // mantissa * 2
exp -= 0x400; // decrease exp by 1
} while( ( mant & 0x400 ) == 0 ); // while not normal
mant &= 0x3ff; // discard subnormal bit
} // else +/-0 -> +/-0
return Float.intBitsToFloat( // combine all parts
( hbits & 0x8000 ) << 16 // sign << ( 31 - 15 )
| ( exp | mant ) << 13 ); // value << ( 23 - 10 )
}
// returns all higher 16 bits as 0 for all results
public static int fromFloat( float fval )
{
int fbits = Float.floatToIntBits( fval );
int sign = fbits >>> 16 & 0x8000; // sign only
int val = ( fbits & 0x7fffffff ) + 0x1000; // rounded value
if( val >= 0x47800000 ) // might be or become NaN/Inf
{ // avoid Inf due to rounding
if( ( fbits & 0x7fffffff ) >= 0x47800000 )
{ // is or must become NaN/Inf
if( val < 0x7f800000 ) // was value but too large
return sign | 0x7c00; // make it +/-Inf
return sign | 0x7c00 | // remains +/-Inf or NaN
( fbits & 0x007fffff ) >>> 13; // keep NaN (and Inf) bits
}
return sign | 0x7bff; // unrounded not quite Inf
}
if( val >= 0x38800000 ) // remains normalized value
return sign | val - 0x38000000 >>> 13; // exp - 127 + 15
if( val < 0x33000000 ) // too small for subnormal
return sign; // becomes +/-0
val = ( fbits & 0x7fffffff ) >>> 23; // tmp exp for subnormal calc
return sign | ( ( fbits & 0x7fffff | 0x800000 ) // add subnormal bit
+ ( 0x800000 >>> val - 102 ) // round depending on cut off
>>> 126 - val ); // div by 2^(1-(exp-127+15)) and >> 13 | exp=0
}
Я реализовал два небольших расширения по сравнению с book , потому что общая точность 16-битных операций с плавающей запятой довольно низкая, что может сделать присущие аномалии форматов с плавающей запятой визуально воспринимаемыми по сравнению с более крупными типами с плавающей запятой, где они обычно не замечается из-за достаточной точности.
Первая - это две строки в функции toFloat()
:
if( mant == 0 && exp > 0x1c400 ) // smooth transition
return Float.intBitsToFloat( ( hbits & 0x8000 ) << 16 | exp << 13 | 0x3ff );
Числа с плавающей запятой в нормальном диапазоне размера шрифта принимают показатель степени и, следовательно, точность до величины значения. Но это не гладкое принятие, это происходит поэтапно: переход к следующему более высокому показателю дает половину точности. Точность теперь остается одинаковой для всех значений мантиссы до следующего перехода к следующему более высокому показателю. Приведенный выше код расширения делает эти переходы более плавными, возвращая значение, которое находится в географическом центре покрытого 32-разрядного диапазона с плавающей запятой для этого конкретного значения с половиной с плавающей запятой. Каждое нормальное половинное значение с плавающей запятой соответствует точно 8192 32-битным значениям с плавающей запятой. Возвращаемое значение должно быть точно в середине этих значений. Но при переходе показателя половинного числа с плавающей точкой нижние значения 4096 имеют вдвое большую точность, чем верхние значения 4096, и, таким образом, охватывают числовое пространство, которое вдвое меньше, чем на другой стороне. Все эти 8192 32-битные значения с плавающей запятой отображаются на одно и то же значение с половиной поплавка, поэтому преобразование половины поплавка в 32-битное и обратно приводит к одному и тому же значению поплавка независимо от того, какое из 8192 промежуточных 32-битных значений было выбрано , Расширение теперь приводит к чему-то вроде более плавного полшага с коэффициентом sqrt (2) при переходе, как показано справа картинка ниже, тогда как левая картинка должна визуализировать резкий шаг в два раза без сглаживания. Вы можете безопасно удалить эти две строки из кода, чтобы получить стандартное поведение.
covered number space on either side of the returned value:
6.0E-8 ####### ##########
4.5E-8 | #
3.0E-8 ######### ########
Второе расширение в функции fromFloat()
:
{ // avoid Inf due to rounding
if( ( fbits & 0x7fffffff ) >= 0x47800000 )
...
return sign | 0x7bff; // unrounded not quite Inf
}
Это расширение немного расширяет диапазон чисел формата наполовину с плавающей точкой, сохраняя некоторые 32-битные значения, получая перевод в Infinity. Затронутые значения - это те, которые были бы меньше Бесконечности без округления и стали бы Бесконечностью только из-за округления. Вы можете безопасно удалить приведенные выше строки, если не хотите использовать это расширение.
Я попытался максимально оптимизировать путь для нормальных значений в функции fromFloat()
, что сделало его немного менее читаемым из-за использования предварительно вычисленных и несмещенных констант. Я не приложил столько усилий к функции toFloat (), поскольку она в любом случае не превысила бы производительность таблицы поиска. Поэтому, если скорость действительно имеет значение, можно использовать функцию toFloat()
только для заполнения статической таблицы поиска элементами 0x10000, а затем использовать эту таблицу для фактического преобразования. Это примерно в 3 раза быстрее с текущей виртуальной машиной сервера x64 и примерно в 5 раз быстрее с виртуальной машиной клиента x86.
Я положил этот код в общественное достояние.