【问题标题】:binary to decimal base shift二进制到十进制基移位
【发布时间】:2011-01-07 16:53:41
【问题描述】:

我需要一种将任意大小的无符号整数(以二进制格式存储)转换为十进制整数的算法。即使其易于阅读;)
我目前使用可能(或显然)有点幼稚的方法,通过除以十来连续计算模数和余数。
不幸的是,速度有点……蹩脚。

例如 我计算 2000^4000(使用我的 bignum 库)大约需要 1.5 秒(请不要燃烧 xD)。然而,包括必要的基本转换在内的打印需要大约 15 分钟,这很烦人。

我已经测试了 bc,它在不到一秒的时间内完成了这两个操作。
它是如何做到的? (不是带有 ffts 的乘法东西,以及任何只有基本转换的东西)

【问题讨论】:

  • 你的 bignum 究竟是如何存储的?或者,如果您不知道,如何允许您访问它?

标签: algorithm math bignum arbitrary-precision


【解决方案1】:

我目前使用可能(或显然)有点幼稚的方法,通过除以十来连续计算模数和余数。
那么你应该有O(n^2) 复杂度,这应该比 15 分钟好得多。

尽管如此,还是值得看看你是如何用10 进行除法的。

  1. 确保您应用的不是通用的长除法,而是更简单的长数除法算法。
  2. 确保重复使用内存。分配 10Kb 块 10000 次肯定会影响您的性能。

编辑
如何一次将长二进制数除以 10 并获得结果和提醒。没有额外的内存。
简单伪代码(a[0]是最高位)

int r = 0;
for (int i = 0; i < n; ++i) {
    r = r * 2 + a[i];
    a[i] = r / 10;
    r = r % 10;
}

我们举个例子,号码100111011 = 315。

第 0 步:r = 1, a[0] = 0
第 1 步:r = 2, a[1] = 0
第 2 步:r = 4, a[2] = 0
第三步:r = 9, a[3] = 0
第 4 步:r = 9, a[4] = 1
第 5 步:r = 9, a[5] = 1
第 6 步:r = 8, a[6] = 1
第 7 步:r = 7, a[7] = 1
第八步:r = 5, a[8] = 1

所以,提醒是5,结果是000011111 = 31。

【讨论】:

  • 什么是更简单的算法?
  • 内存不是问题。或者……至少我是这么认为的;)但第一似乎是关键。我实际上是在做一般的任意大小的划分。我想我可以优化很多。感谢 +1。
  • @iolo #2 与内存消耗无关:我相信过度分配内存也会影响速度。
  • 好兄弟!我写了一个新的除法算法,很快就到了 3.5 秒。看来你对内存分配也是正确的。谢谢! :)
【解决方案2】:

我认为 bc 使用 10^n 作为基数而不是 2。所以每个内部“数字”只是 n 个十进制数字,至少对于十进制输入/输出,问题变得微不足道。

【讨论】:

  • +1 好点,我忘了那个选项。 9 个十进制数字正好适合 32 位有符号整数。
  • @Nikita:曾几何时,BCD 被普遍使用(嗯,比现在更多,无论如何;例如,在 MOS 6502 的指令集中有 BCD 特定指令),并且典型的编码使用每个十进制数字 4 位。没有理论上尽可能紧凑,但更容易处理。
  • @ephemient:不只是 6502 :-) ...实际上许多处理器都有这种支持;例如你现在可能正在使用的那个(见siyobik.info/index.php?module=x86&id=69)
  • 我只是认为使用 6502 作为示例是给定您的用户名的合适的 :-) 知道有人仍在使用 x86 ISA 的这些部分吗?我没有。
  • @ephemient:实际上,即使在 6502 上,依赖它显然也不是那么普遍。在其中一个早期版本中编写我的 6502 仿真器(您可以在我死去的主页中找到它)时,在处理十进制模式时存在错误,但整个虚拟 Apple][ 包括 BASIC 解释器在内都可以正常工作,只是例外通过 print 或 input 语句处理浮点数的 IIRC - 无法记住两者中的哪一个 - 这就是我发现错误的方式。我认为 CPU 中存在十进制模式只是 COBOL 犯下的众多罪行之一。
【解决方案3】:

没有必要使用求幂:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

int main(){
    char a[] = "10011";
    unsigned long int res= 0;
    int i;

    for(i = 0; i < strlen(a); i++){
        res = (res<<1) + (a[i]-'0');
    }

    printf("%d",res);
    return 0;
}

第一次更新

现在长度应该不是问题...

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

char *doubles(char *);
char *sum(char *,int);

int main(){
    char a[] = "10011";
    char *res = calloc(2,sizeof(char));
    int i;

    res[0] = '0';
    for(i = 0; i < strlen(a); i++){
        res = sum(doubles(res),(a[i]-'0'));
    }

    printf("%s",res);
    return 0;
}

char *doubles(char *s){
    int i,len = strlen(s),t = 0;
    char *d;
    d = calloc(len+1,sizeof(char));
    for(i = 0; i < len; i++){
        t = ((s[len-i-1]-'0')<<1) + t;

        d[len-i] = ('0' + (t%10));
        t = t/10;
    }

    d[0] = t+'0';

    return (d[0] == '0')?d+1:d;
}

char *sum(char *s,int n){
    int i, len = strlen(s),t = n;
    char *d = calloc(len+1,sizeof(char));

    for(i = 0; i < len ; i++){
        t = (s[len-i-1]-'0') + t;
        d[len-i] = ('0' + (t%10));
        t = t/10;
    }
    d[0] = t+'0';

    return (d[0] == '0')?d+1:d;
}

【讨论】:

  • “res”在哪里是 2000^4000?很好。
  • 这只是一种算法,用于计算给定二进制数的十进制表示形式,而不使用幂运算。但可能我的问题错了......对不起(我仍然不明白他为什么要计算 2000^4000)
  • 求幂仅用于计算 2000^4000,因此对于这个问题并不重要。正如尼基塔所说,这个数字不适合标准数据类型,而这实际上是这个问题的根源。但这是 bignums 的普遍问题。另外这个库是自己写的,所以我不想使用第三方库。
  • 已更新,现在数据类型大小应该不是问题了。
猜你喜欢
  • 2012-06-11
  • 2017-05-22
  • 2018-08-26
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-18
相关资源
最近更新 更多