C ++ Переносимое представление битов с плавающей точкой?

Существует ли совместимый со стандартами C ++ способ определения структуры типа «float», «double» и «long double» во время компиляции (или во время выполнения, в качестве альтернативы)?

Если я предполагаю std::numeric_limits< T >::is_iec559 == true а также std::numeric_limits< T >::radix == 2Я подозреваю, что это возможно по следующим правилам:

  • первые X-биты являются значимыми.
  • следующие Y-биты — это показатель степени.
  • последний 1-бит является знаковым битом.

со следующими выражениями смутно, как:

  • size_t num_significand_bits = std::numeric_limits< T >::digits;
  • size_t num_exponent_bits = log2( 2 * std::numeric_limits< T >::max_exponent );
  • size_t num_sign_bits = 1u;

кроме я знаю

  • std::numeric_limits< T >::digits включает в себя «целочисленный бит», независимо от того, действительно ли формат представляет его явным образом, поэтому я не знаю, как программно обнаружить и откорректировать это.
  • я догадываюсь std::numeric_limits< T >::max_exponent всегда 2^(num_exponent_bits)/2,

ФонЯ пытаюсь преодолеть две проблемы:

  • установить / получить, какие биты находятся в значении.
  • определить, где находится конец long long, так что я знаю, что не нужно читать неявные биты заполнения, которые будут иметь неинициализированную память.

8

Решение

Короче нет. Если std::numeric_limits<T>::is_iec559, затем вы
знать формат Tболее или менее: вам все равно придется
определить порядок байтов. Во всем остальном все ставки сняты.
(Другие известные мне форматы даже не используются.
база 2: мэйнфреймы IBM используют базу 16, например.)
«стандартное» расположение плавающей запятой МЭК имеет знак
старший разряд, затем показатель степени и мантисса на
биты младшего разряда; если вы можете успешно просмотреть его как
uint64_tНапример, (через memcpy, reinterpret_cast или же
union— mempy работает гарантированно, но меньше
эффективнее двух других), то:

за double:

uint64_t tmp;
memcpy( &tmp, &theDouble, sizeof( double ) );
bool isNeg = (tmp & 0x8000000000000000) != 0;
int  exp   = (int)( (tmp & 0x7FF0000000000000) >> 52 ) - 1022 - 53;
long mant  = (tmp & 0x000FFFFFFFFFFFFF) | 0x0010000000000000;

для `float:

uint32_t tmp;
memcpy( &tmp, &theFloat, sizeof( float ) );
bool isNeg = (tmp & 0x80000000) != 0;
int  exp   = (int)( (tmp & 0x7F800000) >> 23 ) - 126 - 24 );
long mant  = (tmp & 0x007FFFFF) | 0x00800000;

Что касается long doubleХуже, потому что разные
компиляторы относятся к этому по-разному, даже на одной машине.
Номинально, это десять байтов, но по причинам выравнивания
факт 12 или 16. Или просто синоним double, Если его
больше 10 байт я считать Вы можете рассчитывать на упаковку
в первые 10 байтов, так что &myLongDouble дает
адрес 10-байтового значения. Но, вообще говоря, я бы избегал
long double,

4

Другие решения

Я бы сказал, что единственный переносимый способ — хранить число в виде строки. Это не полагается на «интерпретацию битовых паттернов»

Даже если вы знаете, сколько битов что-то есть, это не значит, что оно имеет одно и то же представление — показатель степени на основе нуля или смещенный. Есть ли невидимая 1 в передней части мантиссы? То же самое относится ко всем остальным частям номера. И это еще хуже для кодированных BCD или «шестнадцатеричных» операций с плавающей запятой — они доступны в некоторых архитектурах …

Если вас беспокоят неинициализированные биты в структуре (класс, массив и т. Д.), Используйте memset, чтобы установить всю структуру на ноль [или другое известное значение].

1

Для потомков это то, чем я в конечном итоге занимаюсь.

Чтобы сгенерировать и проверить мои значения сигнализации NEE IEEE-754, я использую этот шаблон для «float» и «double».

#include <cstdint> // uint32_t, uint64_t
#include <limits> // numeric_limits

union IEEE754_Float_Union
{
float value;
uint32_t bits;
};

float generate_IEEE754_float()
{
IEEE754_Float_Union u = { -std::numeric_limits< float >::signaling_NaN() };
size_t const num_significand_bits_to_set = std::numeric_limits< float >::digits
- 1 // implicit "integer-bit"- 1; // the "signaling-bit"u.bits |= ( static_cast< uint32_t >( 1 ) << num_significand_bits_to_set ) - 1;
return u.value;
}

bool test_IEEE754_float( float const& a_r_val )
{
IEEE754_Float_Union const u = { a_r_val };
IEEE754_Float_Union const expected_u = { generate_IEEE754_float() };
return u.bits == expected_u.bits;
}

Для «long double» я использую функции «double» с приведением типов. В частности, я генерирую значение «double» и преобразую его в «long double», прежде чем оно возвращается, и я проверяю «long double», приводя значение «double», а затем проверяю это значение. Моя идея заключается в том, что, хотя формат «long double» может варьироваться, приведение «double» к «long double», затем последующее приведение его к «double» должно быть последовательным (то есть не терять никакой информации).

0
По вопросам рекламы [email protected]