Ответ 1
Вы можете использовать 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
}
Я реализовал два небольших расширения по сравнению с книгой, потому что общая точность для 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-битное и обратно приводит к тому же полуполяному значению независимо от того, какой из 3219-разрядных 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-битных значений, получая повышение до бесконечности. Затрагиваемые значения - это те, которые были бы меньше, чем бесконечность без округления, и стали бы бесконечными только благодаря округлению. Вы можете безопасно удалить строки, показанные выше, если вы не хотите этого расширения.
Я попытался максимально оптимизировать путь для нормальных значений в функции fromFloat()
, что сделало его менее понятным из-за использования предварительно вычисленных и непересекающихся констант. Я не приложил столько усилий к 'toFloat()', так как он все равно не превысит производительность таблицы поиска. Поэтому, если скорость действительно имеет значение, можно использовать функцию toFloat()
только для заполнения статической таблицы поиска с помощью элементов 0x10000, а не для использования этой таблицы для фактического преобразования. Это примерно в 3 раза быстрее с текущей VM-сервером x64 и примерно в 5 раз быстрее с клиентской виртуальной машиной x86.
Я помещаю код в это общедоступное.