Как преобразовать беззнаковое целое в число с плавающей запятой?

Мне нужно создать функцию, которая возвращает битовый эквивалент (float)x без использования плавающих типов данных, операций или констант. Я думаю, что он у меня есть, но когда я запускаю тестовый файл, он возвращает, что есть бесконечный цикл. Любая помощь по отладке будет оценена по достоинству.

Мне разрешено использовать любые целочисленные/беззнаковые операции, включая ||, &&, if, while. Кроме того, я могу использовать только 30 операций

unsigned float_i2f(int x) {
    printf("\n%i", x);
    if (!x) {return x;}
    int mask1 = (x >> 31);
    int mask2 = (1 << 31);
    int sign = x & mask2;
    int complement = ~x + 1;
    //int abs = (~mask1 & x) + (mask1 & complement);
    int abs = x;
    int i = 0, temp = 0;
    while (!(temp & mask2)){
        temp = (abs <<i);
        i = i + 1;
    }
    int E = 32 - i;
    int exp = 127 + E;
    abs = abs & (-1 ^ (1 << E));
    int frac;
    if ((23 - E)>0)
        frac = (abs << (23 - E));
    else
        frac = (abs >> (E - 23));
    int rep = sign + (exp << 23) + frac;
    return rep;
}

В ответ на очень полезные комментарии и ответы, вот обновленный код, теперь он не работает только для 0x80000000:

unsigned float_i2f(int x) {
    int sign;
    int absX;
    int E = -1;
    int shift;
    int exp;
    int frac;
    // zero is the same in int and float:
    if (!x) {return x;}

    // sign is bit 31: that bit should just be transferred to the float:
    sign = x & 0x80000000;

    // if number is < 0, take two's complement:
    if (sign != 0) {
        absX = ~x + 1;
    }
    else
        absX = x;

    shift = absX;
    while ((!!shift) && (shift != -1)) {
        //std::cout << std::bitset<32>(shift) << "\n";
        E++;
        shift = (shift >> 1);
    }
    if (E == 30) { E++;}
    exp = E + 127+24;
    exp = (exp << 23);
    frac = (absX << (23 - E)) & 0x007FFFFF;
    return sign + exp + frac;
}

Кто-нибудь знает, где ошибка в исправленном коде? Всем еще раз спасибо!


person singmotor    schedule 22.10.2013    source источник
comment
Что вы подразумеваете под эквивалентом битового уровня. Не могли бы вы привести пару примеров - если ввод такой, я ожидаю, что вывод будет таким. Кроме того, каковы доказательства того, что у вас бесконечный цикл, и пытались ли вы распечатать значения внутри этого цикла, чтобы выяснить, что происходит?   -  person Floris    schedule 23.10.2013
comment
Я попытался уточнить вопрос для исходного плаката (ожидает утверждения редактирования).   -  person danfuzz    schedule 23.10.2013
comment
@danfuzz - спасибо за разъяснение. Я не одобрил редактирование, так как не уверен, что это действительно то, чего хотел ОП, но я основывал свой ответ на предположении, что вы правы...   -  person Floris    schedule 23.10.2013


Ответы (3)


Вы можете многое сделать, чтобы улучшить свой код и очистить его. Для начала добавьте комментарии! Во-вторых (и чтобы уменьшить количество операций), вы можете комбинировать некоторые вещи. В-третьих, различать «целые числа, которые могут быть представлены точно» от «тех, которые не могут быть представлены».

Вот пример кода, чтобы применить некоторые из этих вещей на практике; На самом деле я не мог скомпилировать и протестировать это, поэтому, возможно, есть некоторые ошибки - я пытаюсь показать подход, а не выполнять ваше задание за вас...

unsigned float_i2f(int x) {
// convert integer to its bit-equivalent floating point representation
// but return it as an unsigned integer
// format: 
// 1 sign bit
// 8 exponent bits
// 23 mantissa bits (plus the 'most significant bit' which is always 1
printf("\n%i", x);

// zero is the same in int and float:
if (x == 0) {return x;}

// sign is bit 31: that bit should just be transferred to the float:
sign = x & 0x8000;

// if number is < 0, take two's complement:
int absX;
if(sign != 0) { 
  absX = ~x + 1;
}
else 
  absX = x;
}

// Take at most 24 bits:
unsigned int bits23 = 0xFF800000;
unsigned int bits24 = 0xFF000000;
unsigned E = 127-24;  // could be off by 1

// shift right if there are bits above bit 24:
while(absX & bits24) {
  E++;   // check that you add and don't subtract...
  absX >>= 1;
}
// shift left if there are no bits above bit 23:
// check that it terminates at the right point.
while (!(absX & bits23))
  E--;   // check direction
  absX <<= 1;
}

// now put the numbers we have together in the return value:
// check that they are truncated correctly
return sign | (E << 23) | (absX & ~bits23);

}

person Floris    schedule 22.10.2013
comment
(Следующее предполагает 32-битный int.) sign = x & 0x8000 должно быть sign = x & 0x80000000. absX = ~x + 1 переполняет int, когда x равен -2147483648. Даже если прерывания не происходит, более поздние сдвиги absX вызывают затруднения, поскольку бит знака остается установленным. Сдвиги для ограничения значимости усекают, но обычно предпочтительнее округление. На другие ошибки не проверял. - person Eric Postpischil; 23.10.2013
comment
Предложить E = 127+24-1, unsigned sign = x & 0x80000000; - person chux - Reinstate Monica; 23.10.2013
comment
@Floris, я переработал весь свой код примерно в соответствии с вашими рекомендациями. Я действительно ценю твою помощь. Теперь все по-другому, но просто не работает значение 0x80800000. Как опубликовать новый код? Должен ли я опубликовать это как новый вопрос? Или я могу отредактировать свой старый? - person singmotor; 23.10.2013
comment
Я вижу, вы сами нашли ответ на свой последний комментарий... Это заслуживает большего моего внимания, но я не могу дать его сегодня вечером. Посмотрю утром, если вы все еще боретесь. А пока печатайте множество операторов отладки (предлагается в шестнадцатеричном формате) в своих циклах, чтобы увидеть, делают ли они то, что вы ожидаете. Вы явно приближаетесь... - person Floris; 23.10.2013
comment
@Floris Возможно, мне придется задать новый вопрос, но я подумал, что сначала задам здесь, так как вы написали решение. Какой смысл использовать маску в последней строке: (absX & ~bits23)? И имеет ли отношение 127-24 к диапазону битов внутри байта? Спасибо. - person JustBlossom; 15.07.2016
comment
Маска существует, потому что мы хотим использовать только младшие 23 бита числа, чтобы избежать перетекания в поле экспоненты. - person Floris; 15.07.2016
comment
127-24 пытается получить правильное значение показателя степени, но, как я уже сказал, я могу ошибаться на единицу. Если вы посмотрите, как представлена ​​плавающая запятая, вы сможете понять это. Извините, это старый ответ... - person Floris; 15.07.2016

Пробовал решение, которое работает для любого размера int.
Не зависит от дополнения 2.
Работает с INT_MIN.
Многому научился у @Floris

[Изменить] Скорректировано для округления и других улучшений

#include <stdio.h>

int Round(uint32_t Odd, unsigned RoundBit, unsigned StickyBit, uint32_t Result);
int Inexact;

// Select your signed integer type: works with any one
//typedef int8_t integer;
//typedef int16_t integer;
//typedef int32_t integer;
typedef int64_t integer;
//typedef intmax_t integer;

uint32_t int_to_IEEEfloat(integer x) {
  uint32_t Result;
  if (x < 0) {  // Note 1
    Result = 0x80000000;
  } else {
    Result = 0;
    x = -x;  // Use negative absolute value. Note 2
  }
  if (x) {
    uint32_t Expo = 127 + 24 - 1;
    static const int32_t m2Power23 = -0x00800000;
    static const int32_t m2Power24 = -0x01000000;
    unsigned RoundBit = 0;
    unsigned StickyBit = 0;
    while (x <= m2Power24) {  // Note 3
      StickyBit |= RoundBit;
      RoundBit = x&1;
      x /= 2;
      Expo++;
    }
    // Round. Note 4
    if (Round(x&1, RoundBit, StickyBit, Result) && (--x <= m2Power24)) {
      x /= 2;
      Expo++;
    }
    if (RoundBit | StickyBit) {  // Note 5
      Inexact = 1; // TBD: Set FP inexact flag
    }
    int32_t i32 = x;  // Note 6
    while (i32 > m2Power23) {
      i32 *= 2;
      Expo--;
    }
    if (Expo >= 0xFF) {
      Result |= 0x7F800000; // Infinity  Note 7
    } else {
      Result |=  (Expo << 23) | ((-i32) & 0x007FFFFF);
    }
  }
  return Result;
}

/*
Note 1  If `integer` was a signed-magnitude or 1s compliment, then +0 and -0 exist.
Rather than `x<0`, this should be a test if the sign bit is set.
The following `if (x)` will not be taken on +0 and -0.
This provides the corresponding float +0.0 and -0.0 be returned.

Note 2 Overflow will _not_ occur using 2s compliment, 1s compliment or sign magnitude.
We are insuring x at this point is < 0.

Note 3 Right shifting may shift out a 1.  Use RoundBit and StickyBit to keep
track of bits shifted out for later rounding determination.

Note 4 Round as needed here.  Possible to need to shift once more after rounding.

Note 5 If either RoundBit or StickyBit set, the floating point inexact flag may be set.

Note 6 Since the `Integer` type maybe be less than 32 bits, we need to convert
to a 32 bit integer as IEEE float is 32 bits.FILE

Note 7 Infinity only expected in Integer was 129 bits or larger.
*/

int Round(uint32_t Odd, unsigned RoundBit, unsigned StickyBit, uint32_t Result) {
  // Round to nearest, ties to even
  return (RoundBit) && (Odd || StickyBit);

  // Truncate toward 0
  // return 0;

  // Truncate away from 0
  // return RoundBit | StickyBit

  // Truncate toward -Infinity
  // return (RoundBit | StickyBit) || Result
}

// For testing
float int_to_IEEEfloatf(integer x) {
  union {
    float f;
    uint32_t u;
  } xx;  // Overlay a float with a 32-bit unsigned integer
  Inexact = 0;
  printf("%20lld ", (long long) x);
  xx.u = int_to_IEEEfloat(x);
  printf("%08lX ", (long) xx.u);
  printf("%d : ", Inexact);
  printf("%.8e\n", xx.f);
  return xx.f;
}

int main() {
  int_to_IEEEfloatf(0x0);
  int_to_IEEEfloatf(0x1);
  int_to_IEEEfloatf(-0x1);
  int_to_IEEEfloatf(127);
  int_to_IEEEfloatf(-128);
  int_to_IEEEfloatf(12345);
  int_to_IEEEfloatf(32767);
  int_to_IEEEfloatf(-32768);
  int_to_IEEEfloatf(16777215);
  int_to_IEEEfloatf(16777216);
  int_to_IEEEfloatf(16777217);
  int_to_IEEEfloatf(2147483647L);
  int_to_IEEEfloatf(-2147483648L);
  int_to_IEEEfloatf( 9223372036854775807LL);
  int_to_IEEEfloatf(-9223372036854775808LL);
  return 0;
}
person chux - Reinstate Monica    schedule 23.10.2013
comment
Спасибо за ответ на мой вопрос! Хотя ваш ответ великолепен, я чувствую, что я бы победил цель переполнения стека, если бы просто скопировал его. Есть ли способ определить, чего мне не хватает, чтобы помочь мне отладить его? Кроме того, мне нужно округлить до четного - person singmotor; 23.10.2013
comment
@Acoustic77 Какой профессиональный программист! Рассмотрю больше. - person chux - Reinstate Monica; 23.10.2013
comment
@ Acoustic77 Shift запутался. Вам нужна двойная смена. Сдвиньте вправо мантиссу пополам и увеличьте экспоненту до тех пор, пока MSbit мантиссы не станет равным 0x00800000. ИЛИ сдвиг влево (удвоение мантиссы) и уменьшение экспоненты до тех пор, пока MSbit не станет равным 0x00800000. - person chux - Reinstate Monica; 23.10.2013
comment
@Acoustic7 Предложите unsigned absX и unsigned shift. - person chux - Reinstate Monica; 23.10.2013
comment
хорошо, я переключил absX на неподписанный absX. Как добавить вторую часть смены? Имеет ли это значение, так как мой E начинается с -1? Может быть, мне следует отказаться от переменной exp и просто использовать E, как это сделал @floris? Я думаю, что что-то не так с моим E или exp, что вызывает беспорядок - person singmotor; 23.10.2013
comment
давайте продолжим это обсуждение в чате - person chux - Reinstate Monica; 23.10.2013
comment
Здесь много хороших вещей - как и ваш, я очень хочу научиться это делать! - person Floris; 23.10.2013

Говоря 30 operations, вы считаете повторения циклов?

if (!x) {return x;}

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

if (!(x & 0x7FFFFFFF)) {return x;}

Кроме того, многие инструкции не нужны, например

complement = ~x + 1;

Просто x = -x достаточно, потому что x больше не используется позже, absX или дополнение просто избыточны. И одна инструкция отрицания быстрее, чем 2 операции, верно?

!!shift также медленнее, чем shift != 0. Это полезно только тогда, когда вам нужно использовать его как выражение только 0 и 1, в противном случае это избыточно.

Другая проблема заключается в том, что операции со знаком иногда могут быть медленнее, чем без знака, поэтому, если в этом нет необходимости, вы не должны объявлять переменную как int. Например, shift = (shift >> 1) будет выполнять арифметический сдвиг (в большинстве реализаций компилятора), что может привести к неожиданному результату.

И чтобы найти первый битовый набор, доступны инструкции для этого, нет необходимости в сдвиге и тестовое задание. Просто найдите позицию бита и сдвиньте значение один раз. Если вам не разрешено использовать встроенные функции, есть много быстрых способов сделать это на Bit. Twiddling Hacks тоже.

person phuclv    schedule 23.10.2013