Как быть точным в преобразовании поплавков в двоичный - PullRequest
0 голосов
/ 30 ноября 2018

Я кодирую функцию C, которая позволяет мне преобразовывать любое число с плавающей запятой или двойное число в строку, содержащую 32 бита из 0 и 1 (согласно стандарту IEEE754).Я не собираюсь использовать printf, поскольку цель состоит в том, чтобы понять, как она работает, и уметь хранить строку.

Я взял метод исчисления из этого видео: https://www.youtube.com/watch?v=8afbTaA-gOQ. Этопозволил мне разобрать числа с плавающей точкой в ​​1 бит для знака, 8 бит для экспоненты и 23 бита для мантиссы.

Я получаю довольно неплохие результаты, но мой конвертер все еще не точен, и мойМантисса часто ошибается в последних битах.Метод, который я использую для вычисления мантиссы: (где strnew - это просто маллок соответствующей длины):

   char *ft_double_decimals(double n, int len)
   {
        char    *decimals;
        int     i;

        if (!(decimals = ft_strnew(len)))
            return (NULL);
        i = 0;
        while (i < len)
        {
            n = n * 2;
            decimals[i++] = (n >= 1) ? '1' : '0';
            n = n - (int)n;
        }
        return (decimals);
    }

Для числа с плавающей запятой, такого как 0,1, я получаю эту мантиссу: 1001 1001 1001 1001 1001 100, где Iдолжен получить 1001 1001 1001 1001 1001 101. Это так расстраивает!Я, очевидно, что-то здесь упускаю, и, думаю, это как-то связано с неправильным приближением десятичных дробей, поэтому, если кто-то знает, какой метод мне следует использовать вместо того, который я использую, я буду очень признателен!

1 Ответ

0 голосов
/ 30 ноября 2018

моя мантисса часто неверна в последних битах.

Если преобразование не завершено, результаты должны быть округлены. @ Eric Postpischil

Нижеследующие округления делений на полпути от нуля.

char *ft_double_decimals(double n, int len) {
  char *decimals;
  int i;

  if (!(decimals = ft_strnew(len)))
    return (NULL);
  i = 0;
  while (i < len) {
    n = n * 2;
    decimals[i++] = (n >= 1) ? '1' : '0';
    n = n - (int) n;
  }

  // Add rounding code
  if (n >= 0.5) {
    int carry = 1;
    while (i > 0) {
      i--;
      int sum = decimals[i] - '0' + carry;
      decimals[i] = sum % 2 + '0';
      carry = sum / 2;
    }
    if (i == 0 && carry > 0) {
      // Rounding into the "one's" digit"
      // TBD code to indicate to the caller that event
    }
  }

  return (decimals);
}

int main(void) {
  printf("%s\n", ft_double_decimals(0.1f, 23)); // --> 00011001100110011001101
  return 0;
}

Более распространенное округление: округления на полпути до ближайших четных.

if (n >= 0.5 && (n > 0.5 || ((i > 0) && decimals[i-1] > '0'))) {

Далее, код вызова должен быть проинформирован, когда округленный результат равен "1.00000..."

...