将两个表示无符号数的字节数组加在一起无法在c#中添加最后一个进位

5w9g7ksd  于 2023-08-03  发布在  C#
关注(0)|答案(2)|浏览(87)

我一直在写一个大的数字库的实践原因在C和加法运算是给我带来了一些问题。它工作正常,但忽略了最后的进位,结果不正确。
我一直在尝试用c写一个大数字库,这里是到目前为止的加法操作代码。到目前为止的代码:
BIG_INT类型是typedef,用于8字节的unsigned char数组。8字节用于测试,之后将是512字节。

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

#define BIG_INT_SIZE 8
typedef unsigned char BIG_INT[BIG_INT_SIZE];

int add_big_int(BIG_INT *left, size_t right)
{
    if (!right)
        return 0;

    char buffer[sizeof(size_t)];
    memcpy(buffer, &right, sizeof(size_t));

    unsigned int carry = 0;
    for (int64_t i = BIG_INT_SIZE - 1; i >= 0; --i) {
        unsigned int sum = (*left[i] & 0xff) + (buffer[i] & 0xff) + carry;
        *left[i] = sum;
        carry = sum >> 8;
    }

    printf("carry: %u\n", carry);
    return 0;
}

int main() 
{
    BIG_INT big = { 0, 0, 0, 0, 0, 0, 0, 1 };
    add_big_int(&big, 255);
    return 0;
}

字符串
当添加像256 + 1 = 257这样的数字时,字节数组是[0, 0, 0, 0, 0, 0, 1, 1](逆序),但是当添加255 + 1 = 256时,字节数组是[0, 0, 0, 0, 0, 0, 0, 0]而不是预期的[0, 0, 0, 0, 0, 0, 1, 0],进位变量被设置为1

k10s72fa

k10s72fa1#

代码是无序的,一些组织将帮助修复它。
看看这个循环:它使用left[i]buffer[i]迭代BIG_INT_SIZE个元素。buffer中有多少个元素?
buffer被声明为char buffer[sizeof(size_t)];,因此,如果sizeof(size_t)BIG_INT_SIZE不同,则循环被大量中断。您需要bufferleft具有相同数量的元素,或者需要循环中的代码来处理不同的大小。
让我们使用char buffer[BIG_INT_SIZE];使它们的大小相同。
接下来,比较这两个声明:

typedef unsigned char BIG_INT[BIG_INT_SIZE];

个字符
有什么不一样?一个使用unsigned char,另一个使用char。C标准没有说明char是有符号的还是无符号的。当你把255放在带符号的char中时,它很可能取-1,尽管这是由实现定义的。您需要unsigned char

unsigned char buffer[BIG_INT_SIZE];


接下来,您有了memcpy(buffer, &right, sizeof(size_t));,但我们需要考虑buffer大小设置方式的变化。此外,memcpy按照字节在内存中的任何顺序复制字节,但根据其他代码的判断,您希望低字节位于较高的地址。所以你可以做的是:

for (int i = 0; i < sizeof right; ++i)
   {
       buffer[BIG_INT_SIZE - 1 - i] = right & 0xff;
       right >>= 8;
   }
   for (int i = sizeof right; i < BIG_INT_SIZE; ++i)
       buffer[BIG_INT_SIZE - 1 - i] = 0;


第一个循环将right的低值字节放入buffer的高地址元素,然后将right的字节右移。第二个循环用零填充buffer的任何其他元素。
这里的一个潜在危险是right大于buffer,这会使第一个循环溢出缓冲区。我们可以通过以下措施来防范这种情况:

_Static_assert(sizeof right <= sizeof buffer, "right is too big for buffer");


我在上面使用了& 0xff,模仿了您现有的代码。通常,人们会设计一个8位字节,在这种情况下,& 0xff是不必要的;所述指派可仅为buffer[BIG_INT_SIZE - 1 - i] = right;,且rightunsigned char的自动转换将有效地移除较高位。如果进行了该更改,则可能需要插入CHAR_BIT为8的_Static_assert。在<limits.h>中定义了CHAR_BIT
接下来,看一下unsigned int sum = (*left[i] & 0xff) + (buffer[i] & 0xff) + carry;。数组下标的优先级高于指针解引用,因此*left[i]*(left[i]),而不是(*left)[i]left是指向BIG_INT的指针,并且只传递了一个BIG_INT,因此不能在i为非零的情况下使用left[i]。您需要(*left)来获取BIG_INT,然后需要[i]来从中选择一个元素,因此您需要unsigned int sum = ((*left)[i] & 0xff) + (buffer[i] & 0xff) + carry;
同样,*left[i] = sum;应该是(*left)[i] = sum;
进行这些更改后,您的代码将获得所显示的情况的正确答案。但是,您应该考虑其他设计方面,包括:

  • 是否要将BIG_INT定义为数组?作为包含数组的结构,它可能更安全。这会在更早的时候捕获*left[i]错误吗?
  • 为什么right的类型是size_tsize_t有一个含义,即它与对象大小一起使用,而不是与大整数类型一起使用的任意算术值。是否要使用带符号的类型?
  • 为什么循环使用int64_t代替i?这是固定长度的型别。您可能需要像ptrdiff_t这样的类型,它适用于索引数组(它是有符号的,因此可以与i >= 0测试一起使用,而且它是一种设计用于根据目标平台调整其宽度的类型)。
  • 当最后一个carry不为零时,您会怎么做?
  • add_big_int总是返回零吗,或者你有什么未来的计划吗?
ie3xauqp

ie3xauqp2#

对于初学者来说,函数的第一个参数是指针

int add_big_int(BIG_INT* left, size_t right)
                ^^^^^^^^

字符串
如果BIG_INT定义为

typedef unsigned char BIG_INT[8];


则它意味着该参数具有类型char ( * )[8]
通过指向数组的指针传递数组没有多大意义。这样声明函数就足够了

int add_big_int(BIG_INT left, size_t right)


就像

add_big_int(big, 255);


该函数始终返回0,该值对函数的用户没有任何信息。函数应该返回有意义的值,或者其返回类型应该是void
使用类型size_t作为示例

char buffer[sizeof(size_t)];


只会让代码的读者感到困惑。
你可以使用表达式sizeof( BIG_INT ),前提是数组BIG_INT足够大,可以存储size_t类型的值。
使用的表达式*left[i]调用未定义的行为。相反,您必须使用表达式( *left )[i]。但在任何情况下,使用带有位AND运算符的表达式,如

(buffer[i] & 0xff)


没有效果,因为您有元素类型为unsigned char的数组。
一般来说,由于使用进位标志,您应该使用包含在结构中的动态分配数组,因为可能会出现溢出。

相关问题