C: преобразовать действительное число в 64-битный двоичный файл с плавающей запятой

Я пытаюсь написать код, который преобразует действительное число в 64-битный двоичный файл с плавающей запятой. Для этого пользователь вводит действительное число (например, 547,4242), а программа должна вывести 64-битный двоичный код с плавающей запятой.

Мои идеи:

  • Знаковая часть проста.
  • Программа преобразует целую часть (547 в предыдущем примере) и сохраняет результат в переменной типа int. Затем программа преобразует дробную часть (0,4242 для предыдущего примера) и сохраняет результат в массив (каждая позиция массива хранит «1» или «0»).

Вот где я застрял. Подводя итог, у меня есть: «Целая часть = 1000100011» (тип int) и «Дробная часть = 0110110010011000010111110000011011110110100101000100» (массив).

Как я могу продолжить?


person mandlao    schedule 23.08.2014    source источник
comment
вы говорите, что храните его как двойной, но он выглядит как двоичный... не уверен, что вы на самом деле спрашиваете   -  person Noctis    schedule 23.08.2014
comment
двойной х = (двойной) у; ?   -  person Ivan Ivanov    schedule 23.08.2014
comment
@Noctis Извините, я имел в виду переменную int.   -  person mandlao    schedule 23.08.2014
comment
Это легко сделать почти правильно, но, если я правильно помню, требуется довольно много работы, чтобы получить абсолютно правильное преобразование.   -  person fuz    schedule 23.08.2014
comment
Типичный 64-битный двоичный файл с плавающей запятой может иметь значение, подобное 100... (300 нулей) 00.0. Преобразование целой части этого действительного числа в int, long, long long и т. д., безусловно, приведет к усечению. Целая часть действительного числа --> int подход, предлагаемый вашей идеей, далеко не ограничен. Предложите 1) другой подход 2) и опубликуйте свой код.   -  person chux - Reinstate Monica    schedule 23.08.2014
comment
Поскольку вы конвертируете «настоящее» число (с плавающей запятой) в 64-битное с плавающей запятой, вам необходимо изучить фактическое побитовое представление как 32-битной с плавающей запятой, так и 64-битной с плавающей запятой. Как подсказка, 1-битный знак, нормализованное значение Xbits, Ybits mantisa. Обратите внимание, что мантисса не +/- от нуля, а средняя точка максимального значения мантиссы.   -  person user3629249    schedule 24.08.2014


Ответы (4)


следующий код используется для определения внутреннего представления числа с плавающей запятой в соответствии с нотацией IEEE754. Этот код сделан в Turbo C++ ide, но вы можете легко преобразовать его в универсальный ide.

#include<conio.h>
#include<stdio.h>

void decimal_to_binary(unsigned char);

union u
{
    float f;
    char c;
};

int main()
{
    int i;
    char*ptr;
    union u a;

    clrscr();
    printf("ENTER THE FLOATING POINT NUMBER : \n");
    scanf("%f",&a.f);

    ptr=&a.c+sizeof(float);

    for(i=0;i<sizeof(float);i++)
    {
        ptr--;
        decimal_to_binary(*ptr);
    }

    getch();
    return 0;
}

void decimal_to_binary(unsigned char n)
{
    int arr[8];
    int i;
    //printf("n = %u  ",n);

    for(i=7;i>=0;i--)
    {
        if(n%2==0)
            arr[i]=0;
        else
            arr[i]=1;
        n/=2;
    }

    for(i=0;i<8;i++)
        printf("%d",arr[i]);
    printf(" ");
}

Для получения дополнительной информации посетите страницу Нажмите здесь!

person Lavish Kothari    schedule 23.08.2014
comment
Почему-то мне кажется, что это противоположно тому, о чем просит ОП. - person Rudy Velthuis; 23.08.2014
comment
@RudyVelthuis, ты прав. Я хочу сделать наоборот. - person mandlao; 23.08.2014

Чтобы правильно округлить все возможные десятичные представления до ближайшего double, вам нужны большие целые числа. Использование только базовых целочисленных типов из C оставит вас для повторной реализации арифметики больших целых чисел. Возможен каждый из этих двух подходов, дополнительная информация о каждом из них приведена ниже:

  1. Для первого подхода вам понадобится большая целочисленная библиотека: GMP — это хорошо. Вооружившись такой большой целочисленной библиотекой, вы обрабатываете ввод, например 123.456E78, как целое число 123456 * 1075 и начинаете задаваться вопросом, какие значения M в [253… 254) и P в [-1022 … 1023] делают (M / 253) * 2P ближайшим к этому числу. На этот вопрос можно ответить с помощью операций с большими целыми числами, следуя шагам, описанным в этот пост в блоге (резюме: сначала определите P. Затем используйте деление для вычисления M). Полная реализация должна учитывать субнормальные числа и бесконечности (inf — правильный результат для любого десятичного представления числа, у которого показатель степени больше +1023).

  2. Второй подход, если вы не хотите включать или реализовывать полную библиотеку больших целых чисел общего назначения, по-прежнему требует реализации нескольких основных операций над массивами целых чисел C, представляющих большие числа. Функция decfloat() в этой реализации представляет большие числа в базе 109, поскольку это упрощает преобразование исходного десятичного представления во внутреннее представление в виде массива x из uint32_t.

person Pascal Cuoq    schedule 23.08.2014
comment
Я не думаю, что большинство реализаций atof() используют внутри какой-то тип BigInteger или BigDecimal. ISTM, что реальный OP - это строка, но я могу ошибаться. - person Rudy Velthuis; 25.08.2014
comment
@RudyVelthuis Не могли бы вы привести одну реализацию atof в авторитетной библиотеке, которая не использует большие целые числа для анализа 1.0E300? Большинство библиотек используют код Дэвида М. Гэя, вот описание реализации Glibc: Explorebinary.com/how-glibc-strtod-works , и я уже связался с Муслом. Их уже большинство, в зависимости от того, как вы считаете, но мне интересна эта тема, и я хотел бы увидеть одну реализацию без каких-либо больших целых чисел (это работает. Меня не интересует, производит ли она результаты в пределах десяти ULP от правильного результата). - person Pascal Cuoq; 25.08.2014
comment
Что престижно, а что нет? Я очень сомневаюсь, что большинство реализаций atof используют большие целые числа. - person Rudy Velthuis; 25.08.2014
comment
@RudyVelthuis Хорошо, пожалуйста, процитируйте одну реализацию atof, в которой нет какой-либо большой целочисленной реализации из какой-либо libc. - person Pascal Cuoq; 25.08.2014
comment
Я не использую Unix, здесь у меня нет реализации libc. Я знаю, что многие языки могут сделать это, даже не имея большой целочисленной реализации. - person Rudy Velthuis; 25.08.2014
comment
@RudyVelthuis Можно реализовать специальные операции bigint, которые решают только эту проблему, не привязываясь к полной реализации bigint. Это то, что (я надеялся) мой ответ показывает, что Мусл делает. Давайте продолжим это обсуждение, когда у вас будет пример реализации atof, чтобы предложить, иначе это будет просто «—я думаю… —я не думаю…» и это может длиться долго. - person Pascal Cuoq; 25.08.2014
comment
Хорошо, может быть, некоторые вещи решаются с помощью минимального массива больших целых чисел, но, не зная реализаций libc, я чувствую, что это может быть намного проще. - person Rudy Velthuis; 25.08.2014
comment
@RudyVelthuis Тогда, пожалуйста, покажите, как вы конвертируете 1.23456789123E300 в ближайшее double (это точность, рекомендуемая стандартом C, например, C99 7.20.1.3:9, поскольку 1.23456789123E300 имеет меньше, чем DECIMAL_DIG значащих цифр). - person Pascal Cuoq; 25.08.2014
comment
Я не знаю, как бы я это сделал. Я знаю, что многие языки управляются (и правильно) без больших целых чисел. - person Rudy Velthuis; 25.08.2014
comment
@RudyVelthuis Нет, вы думаете, что многие языки управляются правильно без больших целых чисел, потому что вы не смотрели, и потому что это кажется проще, чем есть на самом деле. Я могу только порекомендовать блог, на который я несколько раз ссылался в этом вопросе, и эти комментарии, exploringbinary.com , если вам интересно посмотреть, как это работает на самом деле. Я предоставил ссылку на сообщение, объясняющее реализацию Glibc, и прямую ссылку на реализацию Musl. Другие используют код Дэвида М. Гэя, первоначально описанный в ampl.com/REFS/rounding.pdf (и объяснено в блоге). - person Pascal Cuoq; 25.08.2014
comment
Я почти уверен, что многие языки справляются с этим правильно без больших целых чисел. - person Rudy Velthuis; 25.08.2014
comment
@RudyVelthuis Питон? Ява? PHP? - person Pascal Cuoq; 25.08.2014
comment
Я только что посмотрел ваши ссылки. Они используют 160-192-битные целые числа. Если это то, что вы имеете в виду под большими целыми числами, то я согласен. Но для меня большие целые числа намного больше. - person Rudy Velthuis; 25.08.2014
comment
@RudyVelthuis Это ваша цитата «160-192 бит» из последней статьи в блоге, exploringbinary.com/ ? Боюсь, что это не имеет к этому никакого отношения. 160–192 — это промежуточная точность, с которой GCC округляет константы с плавающей запятой, используя округление до нечетного, прежде чем округлять их до целевой точности. Но как GCC приходит к этим 160-192 битам, я слышу ваш вопрос. Он использует библиотеку с множественной точностью, MPFR: exploringbinary.com/ - person Pascal Cuoq; 26.08.2014
comment
Да, GCC, кажется, делает это. Другие этого не делают. - person Rudy Velthuis; 26.08.2014
comment
@RudyVelthuis Мне и авторам GCC, Java, PHP, Python, Glibc, Musl было бы очень интересно узнать, как «другие» справляются с этим подвигом, поэтому, если вы когда-нибудь найдете ссылку на один «другой», который не , не стесняйтесь поделиться. - person Pascal Cuoq; 26.08.2014
comment
Поэтому некоторые так и делают. Прохладный. - person Rudy Velthuis; 26.08.2014

Ниже приведено основное преобразование. Достаточно, чтобы начать ОП.

«Целая часть действительного числа» OP -> int слишком ограничивает. Лучше просто преобразовать всю строку в большое целое число, например uintmax_t. Обратите внимание на десятичную точку '.' и учтите переполнение при сканировании.

Этот код не обрабатывает экспоненты и отрицательные числа. Он может быть отключен в последнем бите или около того из-за ограниченного целого числа ui или последнего num = ui * pow10(expo). Он обрабатывает большинство случаев переполнения.

#include <inttypes.h>

double my_atof(const char *src) {
  uintmax_t ui = 0;
  int dp = '.';
  size_t dpi;
  size_t i = 0;
  size_t toobig = 0;
  int ch;
  for (i = 0; (ch = (unsigned char) src[i]) != '\0'; i++) {
    if (ch == dp) {
      dp = '\0';  // only get 1 dp
      dpi = i;
      continue;
    }
    if (!isdigit(ch)) {
      break; // illegal character
    }
    ch -= '0';
    // detect overflow
    if (toobig || 
        (ui >= UINTMAX_MAX / 10 && 
        (ui > UINTMAX_MAX / 10 || ch > UINTMAX_MAX % 10))) {
      toobig++;
      continue;
    }
    ui = ui * 10 + ch;
  }
  intmax_t expo = toobig;
  if (dp == '\0') {
    expo -= i - dpi - 1;
  }

  double num;
  if (expo < 0) {
    // slightly more precise than: num = ui * pow10(expo);
    num = ui / pow10(-expo);
  } else {
    num = ui * pow10(expo);
  }
  return num;
}
person chux - Reinstate Monica    schedule 23.08.2014

Хитрость заключается в том, чтобы рассматривать значение как целое число, поэтому читайте 547.4242 как unsigned long long (то есть 64-битный или более), то есть 5474242, подсчитывая количество цифр после «.», в данном случае 4. Теперь вы иметь значение, которое на 10 ^ 4 больше, чем должно быть. Итак, вы плаваете 5474242 (как двойное или длинное двойное) и делите на 10 ^ 4.

Преобразование десятичного числа в двоичное обманчиво просто. Когда у вас больше битов, чем вмещает число с плавающей запятой, оно должно округляться. Гораздо интереснее, когда у вас больше цифр, чем вмещает 64-битное целое число, учитывая, что конечные нули имеют особое значение, и вам нужно решить, округлять или нет (и какое округление происходит, когда вы плаваете). Тогда есть дело с E+/-99. Затем, когда вы выполняете окончательное деление (или умножение) на 10^n, у вас есть (а) другое потенциальное округление и (б) проблема, заключающаяся в том, что большие 10^n не точно представлены в вашем плавающем точка - что является еще одним источником ошибки. (А для форм E+/-99 вам может понадобиться до и чуть больше 10^300 для последнего шага.)

Наслаждаться !

person Community    schedule 23.08.2014
comment
Если я введу 547,4242 и прочитаю его как int, я потеряю дробную часть (0,4242), верно? - person mandlao; 23.08.2014
comment
Он означает, что вы опускаете десятичную точку '.' и затем читаете число 547.4242 как целое, т.е. как 5474242, при этом запоминая, сколько цифр следует за десятичной точкой. Затем вы делите целое число на 10^x, где x — количество знаков после запятой. Обязательно выполните деление с плавающей запятой. - person Rudy Velthuis; 23.08.2014
comment
Другой способ взглянуть на это состоит в том, что вы перемещаете десятичную точку вправо. Для каждой цифры, которую вы перемещаете после десятичной точки, вы, конечно же, умножаете число на 10. Поэтому, когда у вас есть полное число, вы конвертируете его в подходящий формат с плавающей запятой, а затем делите его на требуемую степень десяти. чтобы восстановить его истинное значение. (Вы можете взять первую цифру после запятой, разделить на 10 и прибавить к целой части числа, затем следующую цифру разделить на 100, прибавить и т. д. Но каждая из этих операций добавит еще одну. ошибка округления!) - person ; 23.08.2014