Получение мантиссы (числа с плавающей запятой) либо целого числа без знака, либо числа с плавающей запятой (C)

Итак, я пытаюсь запрограммировать функцию, которая печатает заданное число с плавающей запятой (n) в его формате (мантисса * 2 ^ экспонента). Мне удалось получить знак и показатель степени, но не мантиссу (каким бы ни было число, мантисса всегда равна 0,000000). Что у меня есть:

unsigned int num = *(unsigned*)&n;
unsigned int m = num & 0x007fffff;
mantissa = *(float*)&m;

Любые идеи о том, что проблема может быть?


person Khosrow    schedule 12.04.2018    source источник
comment
Здесь может пригодиться frexp и т. д..   -  person Oliver Charlesworth    schedule 12.04.2018
comment
То, что у вас есть, нарушает строгое сглаживание и, следовательно, является неопределенным поведением. Если вы хотите использовать каламбуры, вам нужно использовать союзы.   -  person Christian Gibbons    schedule 12.04.2018
comment
Почему вам нужна мантисса как float, когда она соответствует 32-битному int? Не забудьте включить неявный (не сохраненный) старший 1 бит мантиссы/мантиссы.   -  person Weather Vane    schedule 12.04.2018
comment
@WeatherVane Мне нужна мантисса в виде числа с плавающей запятой, чтобы я мог представить число в формате m * 2^e (я думаю)   -  person Khosrow    schedule 12.04.2018
comment
@OliverCharlesworth не может использовать frexp   -  person Khosrow    schedule 12.04.2018
comment
Вы ходите по кругу? Разве для этого нет спецификатора формата printf?   -  person Weather Vane    schedule 12.04.2018
comment
Неопределенное поведение в двух, поведение, определенное реализацией, в одной строке. Три лески, три проблемы… прежде чем приступать к разборке поплавков, следует изучить основы.   -  person too honest for this site    schedule 12.04.2018
comment
Вы не придерживаетесь строгих правил использования псевдонимов, не так ли? Стандарт C11 — §6.5 Выражения (стр. 6,7) Тип каламбурные указатели - это плохо.   -  person David C. Rankin    schedule 13.04.2018


Ответы (3)


Вы убираете биты экспоненты, оставляя 0. Экспонента 0 особенная, это означает, что число денормализовано и довольно мало, в самом низу диапазона представимых чисел. Я думаю, вы обнаружите, если внимательно присмотритесь, что ваш результат не совсем равен нулю, а настолько мал, что вам трудно заметить разницу.

Чтобы получить разумное число для мантиссы, вам нужно вернуть соответствующую экспоненту. Если вы хотите, чтобы мантисса находилась в диапазоне от 1,0 до 2,0, вам нужна экспонента, равная 0, но добавление смещения означает, что вы действительно< /em> нужен показатель степени 127.

unsigned int m = (num & 0x007fffff) | (127 << 23);
mantissa = *(float*)&m;

Если вы предпочитаете полностью целочисленную мантиссу, вам нужен показатель степени 23, смещение становится равным 150.

unsigned int m = (num & 0x007fffff) | ((23+127) << 23);
mantissa = *(float*)&m;
person Mark Ransom    schedule 13.04.2018

Библиотека C включает функцию, которая выполняет именно эту задачу, frexp:

int expon;
float mant = frexpf(n, &expon);
printf("%g = %g * 2^%d\n", n, mant, expon);

Другой способ сделать это с помощью log2f и exp2f:

if (n == 0) {
  mant  = 0;
  expon = 0;
} else {
  expon = floorf(log2f(fabsf(n)));
  mant = n * exp2f(-expon);
}

Эти два метода, вероятно, дадут разные результаты для одних и тех же входных данных. Например, на моем компьютере метод frexpf описывает 4 как 0,5  23, а метод log2f описывает 4 как 1  22. Оба правильны с математической точки зрения. Кроме того, frexp даст вам точные биты мантиссы, тогда как log2f и exp2f, вероятно, округлят последний бит или два.


Вы должны знать, что *(unsigned *)&n и *(float *)&m нарушают правило против "каламбура" и имеют неопределенное поведение. Если вы хотите получить целое число с тем же битовым представлением, что и число с плавающей запятой, или наоборот, используйте объединение:

union { uint32_t i; float f; } u;
u.f = n;
num = u.i;

(Примечание: такое использование союзов четко определено в C примерно с 2003 года, но из-за давней привычки комитета C++ не уделять достаточного внимания изменениям, происходящим в C, оно официально не определено в C++.)

Вы также должны знать, что числа с плавающей запятой IEEE используют «смещенные» показатели степени. Когда вы инициализируете поле мантиссы переменной float, но оставляете ее поле экспоненты равным нулю, это дает вам представление числа с большим отрицательным показателем: другими словами, число настолько маленькое, что printf("%f", n) напечатает его. как ноль. Всякий раз, когда printf("%f", variable) печатает ноль, измените %f на %g или %a и перезапустите программу, прежде чем предположить, что variable на самом деле равно нулю.

person zwol    schedule 12.04.2018
comment
@Khosrow Почему бы и нет? - person zwol; 12.04.2018
comment
потому что это для колледжа - person Khosrow; 12.04.2018
comment
Можете ли вы указать стандарт/обновление 2003 года? IIRC был официально введен в 1999 г. (C99) и раньше был обычной практикой. - person too honest for this site; 12.04.2018
comment
@Olaf Первоначальная версия C99 явно не требовала этого, формулировка была изменена в виде опечатки. К сожалению, я не помню номер DR. - person zwol; 12.04.2018
comment
@Khosrow В этом случае это может помочь. - person zwol; 12.04.2018
comment
@zwol: А, это может объяснить. Стандарт точно знаю только с 200х. Возможно, опечатки уже были отредактированы в версии. - person too honest for this site; 12.04.2018

В дополнение к замечаниям zwol: если вы хотите сделать это самостоятельно, вы должны получить некоторые знания о внутренностях поплавка IEEE-754. Как только вы это сделаете, вы можете написать что-то вроде

#include <stdlib.h>
#include <stdio.h>
#include <math.h>               // for testing only

typedef union {
  float value;
  unsigned int bits; // assuming 32 bit large ints (better: uint32_t)
} ieee_754_float;



// clang -g3 -O3 -W -Wall -Wextra -Wpedantic -Weverything -std=c11 -o testthewest testthewest.c -lm
int main(int argc, char **argv)
{

  unsigned int m, num;
  int exp; // the exponent can be negative
  float n, mantissa;
  ieee_754_float uf;

  // neither checks nor balances included!

  if (argc == 2) {
    n = atof(argv[1]);
  } else {
    exit(EXIT_FAILURE);
  }

  uf.value = n;
  num = uf.bits;
  m = num & 0x807fffff;         // extract mantissa (i.e.: get rid of sign bit and exponent)
  num = num & 0x7fffffff;       // full number without sign bit
  exp = (num >> 23) - 126;      // extract exponent and subtract bias
  m |= 0x3f000000;              // normalize mantissa (add bias)
  uf.bits = m;
  mantissa = uf.value;
  printf("n = %g, mantissa = %g, exp = %d, check %g\n", n, mantissa, exp, mantissa * powf(2, exp));

  exit(EXIT_SUCCESS);
}

Примечание: приведенный выше код является одним из видов quick&dirty(tm) и не предназначен для производства. В нем также отсутствует обработка субнормальных (денормальных) чисел, что вы должны включить. Подсказка: умножьте мантиссу на большую степень двойки (например: 2 ^ 25 или в этом приблизительном смысле) и соответствующим образом отрегулируйте показатель степени (если вы взяли значение моего примера, вычтите 25).

person deamentiaemundi    schedule 12.04.2018
comment
Вы также можете добавить #ifdef __STDC_IEC_559__, чтобы проверить, использует ли ваша реализация число с плавающей запятой IEEE-754. - person Bob__; 13.04.2018