Как сделать двоичное сложение вещественных чисел? - PullRequest
0 голосов
/ 27 ноября 2018

Рассказ.Я сделал программу, которая делает сложение для двоичных чисел.Мне нужно, чтобы это работало для двоичных вещественных чисел (например, 1010,1010 (двоичное) = 10,625 (десятичное). Входные данные даны в виде двоичной строки. Я сделал много попыток, и я не мог найти простой способ сделать это. Пожалуйстапомогите создать такую ​​программу.

Пример: {input: 1010.1010 (10.625 в десятичном формате) 0.1 (0.5 в десятичном виде) output: 1011.001 (11.125 в десятичном виде)} Код:

#include <stdio.h>
#include <string.h>

void bin_add(int c[400], int d[400])
{
    int car[400]; //carry
    int i = 199;
    car[i] = 0;
    while (i >= 0)
    {
        //find carry and shift it left
        //find the sum
        car[i - 1] = (c[i] & d[i]) | (c[i] & car[i]) | (d[i] & car[i]);
        c[i] = (c[i] ^ d[i]) ^ car[i];
        printf("car[i-1]=%d c[i]=%d\n", car[i - 1], c[i]);
        i--;
    }
    //    printf("\n");
}

int main()
{
    int l, l1, i;//l and l1 are lengths
    char a[200], b[200]; //a and b are the inputs
    int c[200], d[200]; //c and d are used for processing
    for (i = 0; i < 200; i++)
    {
        c[i] = 0;
        d[i] = 0;
    }
    gets(a);
    gets(b);
    l = strlen(a);
    l1 = strlen(b);

    for (int i = 0; i < l; i++)
    {
        c[200 - l + i] = a[i] - 48;
    }

    ////////////////////////////////////////////
    for (int i = 0; i < l1; i++)
    {
        d[200 - l1 + i] = b[i] - 48;
    }
    ////////////////////////////////
    bin_add(c, d);
    for (i = 0; i < 200; i++)
        printf("%d", c[i]);
    return 0;
}

Ответы [ 4 ]

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

После всех прекрасных идей в Ответ животных Я почувствовал странное желание представить свое собственное решение:

#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define MAX(x, y) ((x) > (y) ? (x) : (y))

size_t gpp(char const *s)
{
    char *n = strchr(s, '.');
    return n ? n - s + 1 : 0;
}

char* bin_add(char const *a, char const *b)
{
    char const *inp[] = { a, b };
    size_t ll[] = { strlen(a), strlen(b) };
    size_t pp[] = { gpp(a), gpp(b) };
    size_t OO[2], off[2];

    for (size_t i = 0; i < 2; ++i) {
        OO[i] = pp[i] ? pp[i] - 1 : ll[i];
        pp[i] = pp[i] ? ll[i] - pp[i] : 0;}

    for (size_t i = 0; i < 2; ++i)
        off[i] = OO[i] < OO[!i] ? OO[!i] - OO[i] : 0;

    size_t ML = MAX(OO[0], OO[1]) + MAX(pp[0], pp[1]) + (!!pp[0] || !!pp[1]);
    char *Ol = calloc(ML + 2, 1);
    if(!Ol) return Ol;

    char ops[2];
    int xc = 0;
    size_t lO = ML;
    unsigned cc[2] = { 0 };
    for (size_t i = ML; i; --i) {
        bool pt = false;
        for (size_t l = 0; l < 2; ++l) {
            ops[l] = i <= ll[l] + off[l] && i - off[l] - 1
                       <  ll[l] ? inp[l][i - off[l] - 1] : '0';
            if (ops[l] == '.') {
                if (cc[l]) {
                    free(Ol);
                    return NULL;
                }
                pt = true;
                ++cc[l];
            }
            ops[l] -= '0';
        }
        if (pt) {
            Ol[i] = '.';
            continue;
        }
        if ((Ol[i] = ops[0] + ops[1] + xc) > 1) {
            Ol[i] = 0;
            xc = 1;
        }
        else xc = 0;
        lO = (Ol[i] += '0') == '1' ? i : lO;
    }
    if((Ol[0] = '0' + xc) == '1') return Ol;
        for (size_t i = 0; i <= ML - lO + 1; ++i)
            Ol[i] = Ol[lO + i];

    return Ol;
}

int main(void)
{
    char a[81], b[81];

    while (scanf(" %80[0.1] %80[0.1]", a, b) & 1 << 1) {
        char *c = bin_add(a, b);
        if (!c && errno == ENOMEM) {
            fputs("Not enough memory :(\n\n", stderr);
            return EXIT_FAILURE;
        }
        else if (!c) {
            fputs("Input error :(\n\n", stderr);
            goto clear;
        }

        char* O[] = { a, b, c };
        size_t lO[3], Ol = 0;
        for (size_t i = 0; i < 3; ++i) {
            lO[i] = gpp(O[i]);
            lO[i] = lO[i] ? lO[i] : strlen(i[O]) + 1;
            Ol = lO[i] > Ol ? lO[i] : Ol;
        }

        putchar('\n');
        for (size_t i = 0; i < 3; ++i) {
            for (size_t l = 0; l < Ol - lO[i]; ++l, putchar(' '));
                puts(O[i]);
        }
        putchar('\n');

        free(c);
        clear :{ int c; while ((c = getchar()) != '\n' && c != EOF); }
    }
}

Пример вывода:

11001001 .11001001

11001001
        .11001001
11001001.11001001

11001001 1010

11001001
    1010
11010011

111111 1

 111111
      1
1000000

1010101 010111001.0101110101010

  1010101
010111001.0101110101010
100001110.0101110101010

1001001.010111010101 10100101100.10010111101

    1001001.010111010101
10100101100.10010111101
10101110101.111000001111

. .

 .
 .
0

.. .
Input error :(

A
Press any key to continue . . .

Я размышлял, стоит ли мне спрашивать рецензию на codereview.Но я думаю, что не должен.

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

Есть два ответа, в зависимости от того, желаете ли вы арифметику с фиксированной или плавающей точкой.

Первая проблема - чтение числа.strtol() ваш друг здесь:

char input[BUFFER_SIZE];
char * tmp;
long integral, fractional;
fgets(input, BUFFER_SIZE-1, stdin);
integral = strtol(input, &tmp, 2); /* Read BINARY integral part */
tmp++; /* Pass over the binary point. You may want to check that it is actually a dot */
fractional = strtol(tmp, null, 2); /* Read BINARY fractional part */

Следующая проблема - выяснить, как вы будете выполнять арифметику.fractional должно быть смещено в битах на величину, зависящую от того, сколько цифр после точки было предоставлено и от желаемой точности.Арифметика с фиксированной точкой проста: fractional <<= FRAC_BITS - strlen(tmp) затем сложите дробные части вместе.Замаскируйте ((1<<FRAC_BITS)-1) для дробной части суммы, сдвиньте оставшиеся биты и добавьте их к интегральным частям для интегральной части суммы.Плавающая точка немного более привередлива, но не слишком сложна.

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

То, что вы действительно хотите сделать, это обрабатывать каждую цифру в порядке возрастания важности.Чтобы упростить это, вы должны реализовать следующие функции:

/* Return the number of fractional bits in bs */
int bs_fractbits(const char *bs);

/* Return the number of integer bits in bs */
int bs_intbits(const char *bs);

/* Return the bit in bs corresponding to value 2**i,
   0 if outside the bit string */
int bs_bit(const char *bs, int i);

/* Return -1 if bs is negative,
           0 if bs is zero or NULL,
          +1 if bs is positive */
int bs_sign(const char *bs);

/* Return -1 if bs1 < bs2,
           0 if bs1 == bs2,
          +1 if bs1 > bs2. */
int bs_cmp(const char *bs1, const char *bs2);

Чтобы поддерживать отрицательные значения, вам необходимо реализовать сложение и вычитание (из "битов без знака"):

  • Добавление: результат содержит столько дробных битов, сколько содержится в терминах с наибольшим количеством дробных битов, и, возможно, на один целочисленный бит больше, чем в терминах с наибольшим количеством целых битов.Начните с младшего значащего бита в любом из этих терминов и продвигайтесь до самого старшего бита в любом из этих терминов, суммируя каждый бит и сохраняя «бит переноса», точно так же, как вы делали бы вручную .Если в конце перенос будет ненулевым, вы получите этот дополнительный бит.

  • Вычитание: всегда вычитайте меньшее из большего.Если это меняет порядок условий, отмените результат.Результат имеет не более столько дробных бит, сколько термин, который имеет большинство дробных бит, и самое большее столько целочисленных бит, как термин, который имеет большинство целочисленных бит.Это похоже на сложение, за исключением того, что вы вычитаете биты, и вместо «переносить бит» вы используете «бит заимствования».Поскольку вы вычитаете меньшее значение без знака из большего значения без знака, «бит заимствования» будет равен нулю в конце.

  • Умножение: целочисленная часть содержит число целочисленных битов и числодробные биты, поскольку термины имеют в сумме (суммированные).Вы можете реализовать операцию как умножение двух целочисленных значений без знака и просто вставить бит в конце.(Таким образом, в результате получается столько дробных битов, сколько в общей сложности содержится во входных терминах.) Обычно это включает двойной цикл, как при умножении long вручную.

Обратите внимание, что та же логика также работает, если вы используете большее основание вместо 2. "перенос" / "заимствование" - это цифра от нуля до единицы меньше, чем основание.


Лично,Я бы очень хотел использовать структуру для описания каждой строки цифр:

typedef struct {
    int     idigits;    /* Number of integral digits before point */
    int     fdigits;    /* Number of fractional digits after point */  
    int     size;       /* Number of chars dynamically allocated at data */
    char   *point;      /* Location of decimal point */
    char   *data;       /* Dynamically allocated buffer */
} digitstring;
#define  DIGITSTRING_INIT  { 0, 0, 0, NULL, NULL }

с дополнительным флагом, если необходимо поддерживать строки отрицательных цифр.

Цифра D с числовым значением D × B i , где B - основание (количество используемых уникальных цифр) и i , являющаяся положением указанной цифры, находится в point[-i], если i < 0-i <= fdigits), или в point[-i-1], если i >= 0i < idigits).point[0] - это точка десятичной запятой, если она есть.

Например, если у нас есть строка 0100.00, тогда idigits = 4, fdigits = 2, и единственная ненулевая цифра находится в позиции2.(Позиция 0 находится слева от десятичной точки, а -1 справа). Поля

size и data позволяют повторно использовать динамически распределенный буфер.Каждое объявление цепочки цифр должно быть инициализировано, digitstring value = DIGITSTRING_INIT;, потому что нет функции инициализации;таким образом, у вас меньше шансов утечки памяти (если вы не забудете освободить цепочку цифр, когда она больше не нужна):

/* Free the specified digit string. */
static inline void  digitstring_free(digitstring *ds)
{
    if (ds) {
        if (ds->data)
            free(ds->data);
        ds->idigits = 0;
        ds->fdigits = 0;
        ds->size    = 0;
        ds->point   = NULL;
        ds->data    = NULL;
    }
}

Чтобы использовать строку цифр в качестве строки C, используйте вспомогательную функцию для полученияуказатель на самую значимую цифру в строке цифр:

/* Return a pointer to a printable version of the digit string. */
static const char *digitstring_str(const digitstring *ds, const char *none)
{
    if (ds && ds->point)
        return (const char *)(ds->point - ds->idigits);
    else
        return none;
}

Я обнаружил, что вместо сбоя часто полезно передать дополнительный параметр, который используется только для возвращаемого значения, когда возвращаемое значениеиначе не определено.Например, если у вас есть инициализированная цифровая строка foo без какого-либо содержимого, то digitstring_str(&foo, "0") возвращает строковый литерал "0".

Основной смысл структуры цифровой строки состоит в том, чтобы иметь функции доступа, которые получаюти установите каждую отдельную цифру:

/* Get the value of a specific digit. */
static inline unsigned int  digitstring_get(const digitstring *ds, const int position)
{
    if (ds) {
        if (position < 0) {
            if (-position <= ds->fdigits)
                return digit_to_value(ds->point[-position]);
            else
                return 0;
        } else {
            if (position < ds->idigits)
                return digit_to_value(ds->point[-position-1]);
            else
                return 0;
        }
    } else
        return 0;
}

/* Set the value of a specific digit. */
static inline void  digitstring_set(digitstring *ds, const int position, const unsigned int value)
{
    if (!ds) {
        fprintf(stderr, "digitstring_set(): NULL digitstring specified.\n");
        exit(EXIT_FAILURE);
    } else
    if (position < 0) {
        if (-position > ds->fdigits) {
            fprintf(stderr, "digitstring_set(): Digit position underflow (in fractional part).\n");
            exit(EXIT_FAILURE);
        }
        ds->point[-position] = value_to_digit(value);
    } else {
        if (position >= ds->idigits) {
            fprintf(stderr, "digitstring_set(): Digit position overflow (in integer part).\n");
            exit(EXIT_FAILURE);
        }
        ds->point[-position-1] = value_to_digit(value);
    }
}

Выше value_to_digit() - вспомогательная функция, которая преобразует числовое значение в соответствующий символ, а digit_to_value() преобразует символ в соответствующее числовое значение.

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

/* Clear the specified digit string to zero. */
static inline void  digitstring_zero(digitstring *ds, int idigits, int fdigits)
{
    int   size;
    char *data;

    if (!ds) {
        fprintf(stderr, "digitstring_zero(): No digitstring specified.\n");
        exit(EXIT_FAILURE);
    }

    /* Require at least one integral digit. */
    if (idigits < 1)
        idigits = 1;
    if (fdigits < 0)
        fdigits = 0;

    /* Total number of chars needed, including decimal point
       and string-terminating nul char. */
    size = idigits + 1 + fdigits + 1;

    /* Check if dynamically allocated buffer needs resizing. */
    if (ds->size < size) {
        if (ds->data)
            data = realloc(ds->data, size);
        else
            data = malloc(size);
        if (!data) {
            fprintf(stderr, "digitstring_zero(): Out of memory.\n");
            exit(EXIT_FAILURE);
        }
        ds->data = data;
        ds->size = size;
    } else {
        data = ds->data;
        size = ds->size;
    }

    /* Fill it with zeroes. */
    memset(data, value_to_digit(0), idigits + 1 + fdigits);

    /* Pad the unused space with nul chars, terminating the string. */
    memset(data + idigits + 1 + fdigits, '\0', size - idigits - 1 - fdigits);

    /* Assign the decimal point. */
    ds->point = data + idigits;

    /* If there are no decimals, no need for a decimal point either. */
    if (fdigits > 0)
        ds->point[0] = decimal_point;
    else
        ds->point[0] = '\0';

    /* After setting the desired digits, use digitstring_trim(). */
    ds->idigits = idigits;
    ds->fdigits = fdigits;
}

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

Идея состоит в том, что для реализации операции сначала нужно выяснить,максимальное количество целых и дробных цифр, которое может иметь результат.Вы используете выше, чтобы создать строку цифр результата, затем digitstring_set(), чтобы установить каждую цифру в их соответствующие значения.Как правило, вы будете работать с возрастающей цифрой значимости, что означает увеличение «позиции» цифр.

Если у нас есть дополнительные вспомогательные функции int digits(const char *src), который возвращает число последовательных действительных цифровых символов, начиная с src, и int decimal_points(const char *src), который возвращает 1, если src указывает на десятичную точку, и0 в противном случае мы можем разобрать входные строки в строки цифр, используя

/* Parse a string into a digit string, returning the pointer
   to the first unparsed character, or NULL if an error occurs. */
static const char *digitstring_parse(digitstring *ds, const char *src)
{
    const int    zero = value_to_digit(0);
    const char  *idigit, *fdigit;
    int          idigits, fdigits, fextra, n;

    /* Fail if nothing to parse. */
    if (!src)
        return NULL;

    /* Skip leading whitespace. */
    while (isspace((unsigned char)(*src)))
        src++;

    /* Fail if nothing to parse. */
    if (*src == '\0')
        return NULL;

    /* Scan integer digits. */
    idigit = src;
    src += digits(src);
    idigits = (int)(src - idigit);

    /* Decimal point? */
    fextra = 0;
    n = decimal_points(src);    
    if (n > 0) {
        src += n;
        /* Scan fractional digits. */
        fdigit = src;
        src += digits(src);
        fdigits = (int)(src - fdigit);
        if (fdigits < 1)
            fextra = 1;
    } else {
        fdigit = src;
        fdigits = 0;
    }

    /* No digits? */
    if (idigit == 0 && fdigit == 0)
        return NULL;

    /* Trim leading zeroes. */
    while (idigits > 1 && *idigit == zero) {
        idigits--;
        idigit++;
    }

    /* Trim trailing zeroes. */
    while (fdigits > 1 && fdigit[fdigits - 1] == zero)
        fdigits--;

    /* Create the necessary digit string, */
    digitstring_zero(ds, idigits, fdigits + fextra);

    /* copy the integer digits, if any, */        
    if (idigits > 0)
        memcpy(ds->point - idigits, idigit, idigits);

    /* and the fractional digits, if any. */
    if (fdigits > 0)
        memcpy(ds->point + 1, fdigit, fdigits);

    /* Return a pointer to the first unparsed character. */
    return src;
}

После обновления его цифр можно вызвать вспомогательную функцию для удаления любых лишних начальных нулей:

static inline void  digitstring_ltrim(digitstring *ds)
{
    if (ds && ds->point) {
        const int  zero = value_to_digit(0);

        while (ds->idigits > 1 && ds->point[-ds->idigits] == zero)
            ds->idigits--;
    }
}

Добавление двух(беззнаковые) строки цифр, возможно, с повторным использованием одного из терминов, теперь довольно просто реализовать:

static void digitstring_add(digitstring *to, const digitstring *src1, const digitstring *src2)
{
    digitstring  result = DIGITSTRING_INIT;
    unsigned int carry = 0;
    int          i, idigits, fdigits;

    if (!to || !src1 || !src2) {
        fprintf(stderr, "digitstring_add(): NULL digitstring specified.\n");
        exit(EXIT_FAILURE);
    }

    /* For addition, the result has as many digits
       as the longer source term. */
    idigits = (src1->idigits >= src2->idigits) ? src1->idigits : src2->idigits;
    fdigits = (src1->fdigits >= src2->fdigits) ? src1->fdigits : src2->fdigits;

    /* Result needs possibly one more integer digit,
       in case carry overflows. */
    digitstring_zero(&result, idigits + 1, fdigits);

    /* Addition loop, in order of increasing digit significance. */
    for (i = -fdigits; i < idigits; i++) {
        const unsigned int  sum = digitstring_get(src1, i)
                                + digitstring_get(src2, i)
                                + carry;
        digitstring_set(&result, i, sum % RADIX);
        carry = sum / RADIX;
    }
    digitstring_set(&result, idigits, carry);

    /* Trim leading zeroes. */
    digitstring_ltrim(&result);

    /* At this point, we can discard the target, even if it is actually
       one of the sources, and copy the result to it. */
    digitstring_free(to);
    *to = result;
}

, где RADIX - используемый коэффициент (число уникальных цифр, 2 для двоичного).Обратите особое внимание на цифровую петлю.-fdigits - наименее значимая позиция в результате , а idigits-1 - наиболее значимая позиция.Нам нужны функции доступа, потому что исходные термины могут вообще не содержать этих цифр (тогда они логически равны нулю).

Эти функции были протестированы для работы как со строками двоичных, так и с восьмеричными числами.Мне нравится эта реализация, потому что она пропускает десятичную точку, если все термины являются целыми числами (поэтому вы получаете 12 + 33 = 45), но (из-за fextra в digitstring_parse()), если какой-либо из терминов имеет десятичную точку, то результатбудет иметь хотя бы одну дробную цифру (поэтому 12. + 33 = 45.0).

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

Для действительных чисел преобразуйте дробные и дробные части в десятичные, сложите и распечатайте в двоичном виде.Для этого потребуется функция для преобразования числа в двоичную строку.Просто обратите внимание, что действительные числа являются числами с плавающей точкой в ​​C, и они представлены в двоичном виде с формой Мантессы, такой как 2e ^ 3, которая умножается на 2 в степени в степени 3.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...