我需要创建一个十进制到二进制程序,它可以接收多达 100,000,000 的输入并输出整个答案而不显示垃圾

Posted

技术标签:

【中文标题】我需要创建一个十进制到二进制程序,它可以接收多达 100,000,000 的输入并输出整个答案而不显示垃圾【英文标题】:I need to create a decimal to binary program that can receive input of up to 100,000,000 and output the whole answer without displaying rubbish 【发布时间】:2021-12-03 18:11:34 【问题描述】:

如您所读,我创建了一个十进制转二进制程序,它运行良好,但它无法处理等于 100,000,000 的用户输入。我的解决方案是打印每个字符,但我不知道要使用的适当循环是什么,而且我的数学也不是很好,所以我不清楚要使用的主要公式。不允许使用数组。任何建议表示赞赏。谢谢。

#include <stdio.h>

unsigned long long int input,inp,rem=0,ans=0,place_value=1,ans;
int main()

    printf("\nYou have chosen Decimal to Binary and Octal Conversion!\n");
    printf("Enter a decimal number:\n");  
    scanf("%llu", &input);
    inp=input;
    while(input)
        rem=input%2;
        input=input/2;
        ans=ans+(rem*place_value);
        place_value=place_value*10;
    
    printf("%llu in Decimal is %llu in Binary Form.\n", inp,ans);
    return 0;

编辑:我已经阅读了您的所有答案,并且已尽力理解它们。我能够理解所提出的大部分内容,但提到的一些术语或课程需要我更多的时间来学习。我已经提交了我的输出,但没有解决 100,000,000 问题,但我打算利用我现在拥有的知识来创建更好的输出。我试着问我的一个朋友,他告诉我他可以使用这里找到的方法 2:https://www.wikihow.com/Convert-from-Decimal-to-Binary。也许我的导师只是想教我们如何充分利用控制结构和数据类型,这就是为什么会有这么多限制。谢谢大家的时间和上帝保佑。

【问题讨论】:

您的包含在哪里? Edit 并添加它们。它们是您代码的一部分,应该发布。阅读:minimal reproducible example。顺便说一句,您的代码无法编译。发布真实代码,而不是一些虚构的东西。 你很可能会溢出unsigned long long。没有数组就意味着没有字符串? 给定数字的二进制表示的位数是十进制表示的三倍多(因为 10 > 2^3)。这将限制您将二进制数字编码为十进制数字的方法,这首先有点令人担忧。 我建议采用不同的方法:提取二进制数字并一个一个地打印它们,而不是将它们编码为一个unsigned long long 并通过一个printf() 调用来打印它们。由于您想首先打印它们的最高有效位,而首先将它们拉出最低有效位在算术上是最容易的,因此这将变得复杂。这可以在不使用数组的情况下以多种方式克服。有些是凌乱和/或乏味的,但有一个相当整洁的递归解决方案。 绝对是unsigned long long 的溢出。您的代码是正确的,但是您滥用十进制数字来显示二进制,并且在某些时候您的十进制数字变得太大而无法放入unsigned long long。您应该为此使用bitwise operators。 【参考方案1】:

正如 cmets 所解释的,十进制数 100000000 具有 27 位二进制表示 101111101011110000100000000。因此,我们可以毫无问题地将其存储在 32 位 int 中。但是,如果我们尝试存储 十进制 数字 101111101011110000100000000,这恰好 看起来 像一个二进制数字,那么这需要 87 位,所以它不会甚至适合 64 位 long long 整数。

这个问题中的代码确实尝试将其结果ans 计算为一个十进制数,它恰好看起来像一个二进制数。因此,此代码不适用于大于 1048575 的数字(假设为 64 位 unsigned long long int)。

这是“十进制到二进制”转换(或者,就此而言,转换为 any 基数)通常对结果变量进行的原因之一那是一个整数。通常,这种转换的结果——到任何基数——要么被转换成一个 string 形式的结果变量,要么被立即打印出来。 (这里的寓意是,只有当一个数字被打印出来供人类阅读时,基数才重要,这意味着一个字符串和/或打印到 stdout 的东西。)

但是,在 C 中,字符串当然是数组。因此,要求某人在使用数组的情况下进行基本转换是一种不正当的、毫无意义的练习。

如果您立即打印出数字,则不必将它们存储在数组中。但是标准算法——重复除以 2(或任何基数)以相反的顺序生成数字,从最低有效到最高有效,最终是从右到左,这是错误的打印顺序出去。传统的转换为数字的代码通常将计算出的数字存储到一个数组中,然后反转该数组——但如果禁止使用数组,我们就会(再次毫无意义地)拒绝这种策略。

另一种按其他顺序取出数字的方法是使用递归算法,正如@chux 在他的回答中所展示的那样。

但为了我自己的方式变态,我将展示另一种方式来做到这一点。

尽管这通常是一个可怕的想法,但将数字构造为以 10 为底但 看起来 以 2 为底的整数,至少是一种存储内容并获得答案的方法以正确的顺序退出数字。唯一的问题是,正如我们所见,这个数字可能会变得非常大,尤其是对于基数 2。(另一个问题,在这里并不重要,是这种方法不适用于大于 10 的基数,因为显然没有办法构造一个恰好看起来像是以 16 为基数的十进制数。)

问题是,我们如何表示可能高达 87 位的整数?我的回答是,我们可以使用所谓的“多精度算术”。例如,如果我们使用 pair 64 位 unsigned long long int 变量,理论上我们可以表示最大为 128 位的数字,或者 340282366920938463463374607431768211455!

多精度算术是一个高级但引人入胜且具有启发性的主题。通常它也使用数组,但如果我们将自己限制在大数字的两个“一半”,并进行某些其他简化,我们可以很简单地做到这一点,并获得足够强大的东西来解决问题中的问题。

所以,重复一遍,我们将把一个 128 位数字表示为“高半部分”和“低半部分”。实际上,为了简单起见,它实际上并不是一个 128 位的数字。为简单起见,“高半部分”将是 36 位十进制数的前 18 位,“低半部分”将是其他 18 位数字。这将只给我们大约 120 位,但对于我们的目的来说仍然足够。

那么我们如何对表示为“高”和“低”两半的 36 位数字进行算术运算呢?实际上,它最终或多或少地与我们学习如何对表示为数字的数字进行纸笔算术完全一样。

如果我有这些“大”数字之一,分为两半:

  high1  low1

如果我有第二个,也分成两半:

  high2  low2

如果我想计算总和

  high1  low1
+ high2  low2
  -----------
  high3  low3

我这样做的方法是将low1low2 相加得到总和的下半部分,low3。如果low3 小于 1000000000000000000 - 也就是说,如果它有 18 位或更少 - 我没关系,但如果它大于那,我有进位到下一列。然后要得到总和的高半部分,high3,我只需加上 high1 加上 high2 加上进位,如果有的话。

乘法更难,但事实证明,对于这个问题,我们永远不需要计算一个完整的 36 位 × 36 位乘积。我们只需要将一个大数乘以一个小数,比如 2 或 10。问题如下所示:

  high1  low1
×         fac
  -----------
  high3  low3

所以,再次根据我们很久以前学习的纸笔算术规则,low3 将是 low1 × fachigh3 将是 high1 × @987654343 @,再次带有可能的进位。

下一个问题是我们将如何携带这些低半部分和高半部分。正如我所说,通常我们会使用数组,但我们不能在这里。第二个选择可能是结构,但您可能还没有了解这些,如果您的疯狂讲师不允许您使用数组,那么使用结构似乎也可能超出范围。所以我们只写几个函数,接受高半和低半作为单独的参数。

这是我们的第一个函数,将两个 36 位数字相加。其实很简单:

void long_add(unsigned long long int *hi, unsigned long long int *lo,
                unsigned long long int addhi, unsigned long long int addlo)

    *hi += addhi;
    *lo += addlo;

我写它的方式,它不计算c = a + b;它更像a += b。即把addhiaddlo加到hilo中,同时修改hilo。所以hilo作为指针传入,这样就可以修改指向的值了。高半部分是*hi,我们加上要添加的数字的高半部分,addhi。然后我们对下半部分做同样的事情。然后——哎呀——随身携带呢?这并不难,但为了让事情变得简单明了,我将把它推迟到一个单独的函数中。所以我最终的long_add 函数看起来像:

void long_add(unsigned long long int *hi, unsigned long long int *lo,
                unsigned long long int addhi, unsigned long long int addlo)

    *hi += addhi;
    *lo += addlo;
    check_carry(hi, lo);

然后check_carry 也很简单。它看起来像这样:

void check_carry(unsigned long long int *hi, unsigned long long int *lo)

    if(*lo >= 1000000000000000000ULL) 
        int carry = *lo / 1000000000000000000ULL;
        *lo %= 1000000000000000000ULL;
        *hi += carry;
    

同样,它接受指向lohi 的指针,以便可以修改它们。

下半部分是*lo,它最多应该是一个18位的数字,但是如果它有19——也就是说,如果它大于或等于1000000000000000000,那就意味着它已经溢出了,我们必须做随身携带的事情。进位是 *lo 超过 18 位的程度——它实际上只是前 19 位(以及任何更大的位)。如果您对这种数学不太满意,那么取*lo 并将其除以那个大数字(实际上是 1 和 18 个 0)可能不会立即得到第 19 位数字,或者使用 % 会给你低 18 位数字,但 正是 /% 所做的,这是学习这一点的好方法。

无论如何,计算完进位后,我们将其添加到*hi,就完成了。

所以现在我们完成了加法,我们可以处理乘法了。就我们的目的而言,这也很简单:

void long_multiply(unsigned long long int *hi, unsigned long long int *lo,
                                            unsigned int fac)

    *hi *= fac;
    *lo *= fac;
    check_carry(hi, lo);

它看起来与加法案例惊人地相似,但这正是我们的纸笔分析表明我们将不得不做的事情。 (同样,这是一个简化版本。)我们可以重复使用相同的 check_carry 函数,这就是为什么我选择将它作为一个单独的函数拆分出来。

有了这些函数,我们现在可以重写二进制到十进制的程序,以便它可以处理这些更大的数字:

int main()

    unsigned int inp, input;
    unsigned long long int anslo = 0, anshi = 0;
    unsigned long long int place_value_lo = 1, place_value_hi = 0;
    
    printf("Enter a decimal number:\n");
    scanf("%u", &input);
    inp = input;

    while(input)
        int rem = input % 2;
        input = input / 2;

        // ans=ans+(rem*place_value);
        unsigned long long int tmplo = place_value_lo;
        unsigned long long int tmphi = place_value_hi;
        long_multiply(&tmphi, &tmplo, rem);
        long_add(&anshi, &anslo, tmphi, tmplo);

        // place_value=place_value*10;
        long_multiply(&place_value_hi, &place_value_lo, 10);
    

    printf("%u in Decimal is ", inp);
    if(anshi == 0)
         printf("%llu", anslo);
    else printf("%llu%018llu", anshi, anslo);
    printf(" in Binary Form.\n");

这与问题中的程序基本相同,但有以下变化:

ansplace_value 变量必须大于 64 位,因此它们现在以 _hi_lo 的一半存在。 我们正在调用我们的新函数来对大数进行加法和乘法运算。 我们需要一个tmp 变量(实际上是tmp_hitmp_lo)来保存以前的简单表达式ans = ans + (rem * place_value); 中的中间结果。 用户的input 变量不需要很大,所以我将其简化为普通的unsigned int

打印最终答案的两半,anshianslo,还涉及一些轻微的技巧。但是如果你编译并运行这个程序,我想你会发现它现在适用于你可以给它的任何输入数字。 (理论上它应该适用于高达 68719476735 左右的输入,这比适合 32 位输入 inp 的输入要大。)


另外,对于那些还在我身边的人,我必须添加一些免责声明。我能够写出看起来如此小而简单的long_addlong_multiply 函数的唯一原因是它们简单,并且只适用于“简单”问题,不会出现过度溢出。我选择 18 位作为“high”和“lo”两半的最大值,因为 64 位 unsigned long long int 实际上可以容纳相当于 19 位的数字,这意味着我可以检测到溢出 - 最多 1数字 — 简单地说,通过 &gt; 1000000000000000000ULL 测试。如果任何中间结果被 2 位数字溢出,我就会遇到真正的麻烦。但对于简单的加法,只有一位数的进位。而且由于我只进行过微小的乘法运算,因此我也可以作弊并假设(即侥幸逃脱)一个个位数的进位。

如果您尝试完全通用地进行多精度算术运算,则对于乘法,您必须考虑具有最多两倍于其输入的位数/位的部分乘积。因此,您要么需要使用两倍于输入宽度的输出类型,要么必须将输入分成两半(“子半”),并单独处理它们,基本上是做一个 2×2 的小问题,用每个“数字”都有各种进位。

乘法的另一个问题是“显而易见的”算法,基于每个人在小学学习的铅笔和纸技术,对于真正的大问题可能效率低得令人无法接受,因为它基本上是 O(N 2) 的位数。 以做这些为生的人拥有许多他们已经制定的更复杂的技术,用于检测溢出和更有效地进行乘法之类的事情。 然后,如果您想要一些真正的乐趣(或真正的噩梦,充满对小学的糟糕倒叙),那就有很长的分歧......

【讨论】:

【参考方案2】:

OP 的代码在place_value*10 中溢出


避免无数组和范围限制的一种方法是使用递归。

也许超越了现在的 OP。

#include <stdio.h>

void print_lsbit(unsigned long long x) 
  if (x > 1) 
    print_lsbit(x / 2); // Print more significant digits first
  
  putchar(x % 2 + '0'); // Print the LSBit


int main(void) 
  printf("\nYou have chosen Decimal to Binary and Octal Conversion!\n");
  printf("Enter a decimal number:\n");
  //scanf("%llu", &input);
  unsigned long long input = 100000000;
  printf("%llu in Decimal is ", input);
  print_lsbit(input);
  printf(" in Binary Form.\n");
  return 0;

输出

You have chosen Decimal to Binary and Octal Conversion!
Enter a decimal number:
100000000 in Decimal is 101111101011110000100000000 in Binary Form.

【讨论】:

很好的答案,但我同意,当然不应该要求初学者提出。

以上是关于我需要创建一个十进制到二进制程序,它可以接收多达 100,000,000 的输入并输出整个答案而不显示垃圾的主要内容,如果未能解决你的问题,请参考以下文章

如何转换从管道接收到的二进制数

WebSocketReceiveResult 强制接收二进制

arduino串口接收的问题

如何使用 C# 编辑二进制文件的十六进制值

我可以将多个BOOST单元测试链接到一个测试二进制文件中吗?

编程语言的分类及初识大蟒蛇