如何对实数进行二进制加法?

时间:2018-11-27 14:55:17

标签: c binary

短篇小说。我制作了一个对二进制整数进行加法的程序。我需要使其适用于二进制实数(例如1010.1010(binary)= 10.625(decimal) 输入以二进制字符串形式给出。 我做了很多尝试,却找不到简单的方法来做。请帮助创建这样的程序。

示例:{输入:1010.1010(十进制为10.625)0.1(十进制为0.5) 输出: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 :(得分:2)

您真正想做的是按重要性递增的顺序处理每个数字。为了简化操作,您应该实现以下功能:

/* 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);

要支持负值,您需要实现加法和减法(“无符号”位字符串):

  • 加法:结果的小数位与具有最多小数位的项一样多,并且可能比具有最多整数位的项多一个整数位。从任一术语中的最低有效位开始,然后逐步上升至任一术语中的最高有效位,将每一位相加,并保持“进位”,就像执行by hand一样。如果最后的进位非零,您将再得到一位。

  • 减法:始终从大中减去小。如果那改变了条款的顺序,则取反结果。结果最多具有与具有最多小数位的项一样多的小数位,并且具有与具有最多整数位的项一样多的整数位。这与加法一样,只是减去了位,而使用了“借位”代替“进位”。因为您要从较大的无符号值中减去较小的无符号值,所以“借位”最后将为零。

  • 乘法:整数部分具有整数个的位数以及小数位数的数量,如各项的总和(总和)一样。您可以像将两个无符号整数值相乘一样实现该操作,只需在最后插入该位即可。 (因此,结果的分数位数与输入项的总数相同。)这通常涉及一个双循环,就像手动操作long multiplication一样。

请注意,如果您使用更大的基数而不是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 × B i

数字 D ,其中 B 是基数(使用的唯一数字的位数), i 是所述数字的位置,如果point[-i](和i < 0)位于-i <= fdigits,或者如果point[-i-1](和i >= 0)在i < idigits处。 point[0]本身就是小数点所在的位置(如果有的话)。

例如,如果我们有字符串0100.00,则idigits = 4fdigits = 2,唯一的非零数字位于位置2。 (位置0位于小数点的左侧,位置-1位于小数点的左侧。)

sizedata字段允许重新使用动态分配的缓冲区。由于没有初始化函数,因此必须初始化数字字符串的每个声明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)指向src,则返回1。小数点,否则为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)。

答案 1 :(得分:0)

有两个答案,具体取决于您需要定点还是浮点运算。

第一个问题是读取号码。 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)屏蔽总和的小数部分,移位剩余的位,并将它们加到总和的整数部分的整数部分。浮点数有点挑剔,但难度不大。

答案 2 :(得分:0)

Animals' answer中提出了所有美好的想法之后,我感到奇怪的冲动,提出了自己的解决方案:

#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上要求进行审查。但我认为我宁可不要。

答案 3 :(得分:-2)

对于实数,将非小数和小数部分转换为十进制,进行加法并将其打印为二进制。这将需要函数将数字转换为二进制字符串。请注意,实数是C中的浮点数,它们以2t ^ 3之类的mantessa形式用二进制表示,即2乘以3的幂的指数。