【问题标题】:Algorithm for converting large hex numbers into decimal form (base 10 form)将大的十六进制数转换为十进制形式的算法(以 10 为基数)
【发布时间】:2017-12-28 22:39:57
【问题描述】:

我有一个字节数组和该数组的长度。目标是输出包含以 10 为底数的数字的字符串。

我的数组是小端的。这意味着第一个 (arr[0]) 字节是最低有效字节。这是一个例子:

#include <iostream>
using namespace std;

typedef unsigned char Byte;

int main(){
  int len = 5;
  Byte *arr = new Byte[5];
  int i = 0;

  arr[i++] = 0x12;
  arr[i++] = 0x34;
  arr[i++] = 0x56;
  arr[i++] = 0x78;
  arr[i++] = 0x9A;

  cout << hexToDec(arr, len) << endl;
}

该数组由[0x12, 0x34, 0x56, 0x78, 0x9A] 组成。我要实现的函数hexToDec 应该返回663443878930,这是十进制的数字。

但是,问题是因为我的机器是32位,所以它输出2018915346(注意这个数字是从整数溢出获得的)。所以,问题是因为我使用的是幼稚的方式(迭代数组并将其乘以256 到数组中位置的幂,然后乘以该位置的字节,最后添加到总和)。这当然会产生整数溢出。

我也试过long long int,但在某些时候,当然会发生整数溢出。

我想表示为十进制数的数组可能很长(超过 1000 个字节),这肯定需要比我天真的算法更聪明的算法。

问题

实现这一目标的好算法是什么?另外,我必须问的另一个问题是该算法的最佳复杂度是多少?可以在线性复杂度O(n) 中完成,其中n 是数组的长度?我真的想不出一个好主意。实施不是问题,是我缺乏想法。

建议或想法如何做到这一点就足够了。但是,如果使用一些代码更容易解​​释,请随意用 C++ 编写。

【问题讨论】:

  • 您可能会在使用 bigint 库/类时找到帮助,使用您最喜欢的搜索引擎。
  • @Yunnosch。我的目标是从头开始实现它,而不是使用内置库或类。 C++ 只是一个示例语言,我对算法很感兴趣。
  • 艰难,我希望是为了学习,而不是为了解决更大的问题。我认为“像在学校用铅笔和纸教的那样划分”将是“一种”算法。可能不是最好的......即根据数组大小的知识(即 16 的幂),找到高于您要查找的值的 10 的最低幂,然后得到除以下一个较低的 10 幂的余数。重复。
  • @Yunnosch。 “找到 10 的最低幂,它高于您要查找的值” - 这不会导致整数溢出吗?
  • 如果您使用指数,则不会。对于其他所有内容,您需要保持在 byte 的数据类型数组中。 IE。对值使用字节数组的查找表(或在确定指数后即时生成它们)。

标签: c++ arrays algorithm memory hex


【解决方案1】:

您发布的号码0x9a78563412,正如您以小端格式表示的那样,可以使用以下代码转换为正确的uint64_t

#include <iostream>
#include <stdint.h>

int main()
{
    uint64_t my_number = 0;
    const int base = 0x100; /* base 256 */
    uint8_t array[] = { 0x12, 0x34, 0x56, 0x78, 0x9a };

    /* go from right to left, as it is little endian */
    for (int i = sizeof array; i > 0;) {
        my_number *= base;
        my_number += array[--i];
    }
    std::cout << my_number << std::endl; /* conversion uses 10 base by default */
}

样本运行给出:

$ num
663443878930

因为我们的底数是 2 的精确幂,所以我们可以使用优化代码

my_number <<= 8; /* left shift by 8 */
my_number |= array[--i]; /* bit or */

由于这些操作比整数乘法和求和更简单,因此预计这样做会提高一些(但不是很多)效率。将其保留在第一个示例中应该更具表现力,因为它更代表任意基础转换。

【讨论】:

    【解决方案2】:

    您可以也不能在O(n) 中实现这一点。一切都取决于您号码的内部表示。

    1. 对于真正的二进制形式(2 基数的幂,例如 256)

      这在O(n) 中无法解决吗?此类数字的十六进制打印在O(n) 中,但是您可以将 HEX 字符串转换为十进制并返回如下:

      因为创建十六进制字符串不需要 bignum 数学。因此,您只需在 HEX 中将数组从 MSW 打印到 LSW。这是O(n),但不是转换为DEC

      要在 decadic 中打印 bigint,您需要将其连续修改/除以 10,获得从 LSDMSD 的数字,直到子结果为零。然后以相反的顺序打印它们......除法和模数可以一次完成,因为它们是相同的操作。因此,如果您的号码有N 十进制数字,那么您需要N bigint 部门。例如,每个 bigint 除法都可以通过二进制除法来完成,因此我们需要 log2(n) 位移和减法,它们都是 O(n),因此本机 bigint 打印的复杂性是:

      O(N.n.log2(n))
      

      我们可以通过对数从n 计算N,因此对于BYTEs:

      N = log10(base^n)
        = log10(2^(8.n))
        = log2(2^(8.n))/log2(10)
        = 8.n/log2(10)
        = 8.n*0.30102999
        = 2.40824.n
      

      所以复杂度是:

      O(2.40824.n.n.log2(n)) = O(n^2.log2(n))
      

      对于非常大的数字来说,这太疯狂了。

    2. 10 基二进制形式的幂

      要在O(n) 中执行此操作,您需要稍微更改号码的基数。它仍然以二进制形式表示,但基数是 10 的幂。

      例如,如果您的号码将由16bit WORDs 表示,您可以使用仍然适合它的最高基数10000(最大值为16536)。现在您可以轻松地以 decadic 打印,只需打印数组中从 MSWLSW 的每个单词。

      例子:

      让我们将大数字1234567890 存储为BYTEs,基数为100,其中MSW 排在第一位。所以号码会按如下方式存储

      BYTE x[] = { 12, 34, 56, 78, 90 }
      

      但是正如您在使用BYTEs 和基本100 时所看到的那样,我们正在浪费空间,因为在整个BYTE 范围内只使用了100*100/256=~39%。对这些数字的操作与原始二进制形式略有不同,因为我们需要以不同的方式处理上溢/下溢和携带标志。

    3. BCD(二进制编码的十进制)

      还有另一个选项是使用 BCD(二进制编码的十进制),它与前面的选项几乎相同,但基数 10 用于单个数字......每个 nibel ( 4 位)只包含一位数字。处理器通常具有用于此数字表示的指令集。用法类似于二进制编码的数字,但在每个算术运算之后是 BCD 恢复指令,称为 DAA,它使用进位和辅助进位标志状态来恢复 BCD 编码结果。要在 decadic 中以 BCD 打印值,只需将值打印为 HEX。我们之前示例中的数字将像这样以 BCD 编码:

      BYTE x[] = { 0x12, 0x34, 0x56, 0x78, 0x90 }
      

    当然,#2,#3 都会使您的号码在 O(n) 中的 HEX 打印变得不可能。

    【讨论】:

    • 您提供的链接中的脚本输出 098083 十六进制数 9A78563412
    • @M__6littleeggs 你用过str_hex2dec("9A78563412h"); 吗?脚本经过测试。你在 Borland 运行它吗?如果不是,也许你错过了AnsiString 的端口,它从1 而不是0 开始索引。我现在去查一下你的号码……
    • @M__6littleeggs 它输出是正确的663443878930
    • 看来你是对的。问题是因为我刚刚将AnsiString 替换为std::stringLengthlength,我不知道索引已转移到1。它有效,顺便说一句,它很好地解释了。您的代码很容易理解。
    • 很奇怪。我想知道为什么这个答案有 0 票。这是 1) 正确答案,2) 解释清楚,代码看起来清晰直观,3) 您的解决方案完全回答了 OP 的问题。因此,缺乏支持确实让我感到困惑。
    【解决方案3】:

    您需要提高您的小学技能并实施long division

    我认为您最好在基数 16 中实现长除法(每次迭代将数字除以 0x0A)。提醒每个部门 - 这些是您的十进制数字(第一个是最低有效数字)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-25
      • 2010-10-31
      • 2018-02-17
      • 2015-01-26
      • 1970-01-01
      • 2013-05-31
      • 2018-07-04
      相关资源
      最近更新 更多