将字节数组转换为一个十进制字符串

10
我将尝试编写一个函数,将任意大的字节数组(大于64位)转换为c#中表示为字符串的十进制数。但我无法弄清如何实现。
例如以下代码...
Console.WriteLine(ConvertToString(
  new byte[]
  { 
    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 
    0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
  }));

..应该打印出来

22774453838368691933757882222884355840

我不想仅仅使用像biginteger这样的额外库,因为我希望它简单易懂并且能够理解其工作原理。


2
我建议继续使用BigInteger库,并使用Reflector查看其工作原理。重新发明此代码非常不必要。 - Codesleuth
显然他想学习其中的方法。 - Skurmedel
5个回答

6
一些指导方针:
  1. 您需要将数字的每个数字保存在矩阵中,每个数字一个空间。该矩阵为空。
  2. 然后,您需要一个矩阵来保存矩阵的乘积。它也是空的。
  3. 现在对于每个字节:
    1. 首先将当前数字矩阵的每个数字乘以256,将10模数保存在相应的临时数字中,并将数字除以10加到下一个数字的乘积中。
    2. 将临时乘法矩阵分配给当前数字矩阵。
    3. 然后将字节添加到第一个数字中。
    4. 更正当前矩阵,在每个索引中仅保存10模数并将值除以10传递到下一个索引。
  4. 然后您需要将每个数字串联成一个字符串。
  5. 返回字符串。

不要忘记根据需要扩展每个矩阵,或者从传递的字节数确定所需的最大大小。

编辑,以下是第三步的示例:

Values = [0xAA, 0xBB] Initial Current = [] Initial Temp = []

使用0xAA

  1. 没有什么可以乘。
  2. 分配时没有变化。
  3. 我们将0xAA添加到当前值的第一个值中:Current = [170]
  4. 我们更正当前值以仅保存模数,将除以十的值传递给下一个值:
    1. 第1位数字:Current = [0] pass 17。
    2. 第2位数字:Current = [0, 7] pass 1。
    3. 第3位数字:Current = [0, 7, 1]没有值可以传递,因此处理结束。

现在使用0xBB

  1. 对每一个数字进行以下操作,乘以256并保存在temp中:
    1. 第一个数字:Temp=[0],用于保存到下一位。
    2. 第二个数字:在校正之前,Temp=[0, 1792],校正后,Temp=[0, 2],传递179。
    3. 第三个数字:在校正之前,Temp=[0, 2, 1*256+179=435],校正后,Temp=[0, 2, 5],传递43。
    4. 第四个数字:在校正之前,Temp=[0, 2, 5, 43],校正后,Temp=[0, 2, 5, 3],传递3。
    5. 第五个数字:在校正之前和之后,Temp=[0, 2, 5, 3, 4],没有数字需要保存,因此乘法结束。
  2. 将temp分配给当前的数字:Current=[0, 2, 5, 3, 4];Temp=[]
  3. 将当前值添加到第一个数字:Current=[187, 2, 5, 3, 4]
  4. 校正值:
    1. 第一个数字:Current=[7, 2, 5, 3, 4],通过18。
    2. 第二个数字:Current=[7, 0, 5, 3, 4],通过2。
    3. 第三个数字:Current=[7, 0, 7, 3, 4],没有数字需要通过,因此加法结束。

现在我们只需要将结果连接起来得到43707。


@Wilhelm:我不确定我理解你的描述(但听起来好像可行)... 你能给我一个例子,用两个字节(例如0xAA和0xBB)吗? - Martin
“matrix”对我来说有错误的联想,我只会称它们为数组或列表。” - Simon Buchan
n 字节的最大数字位数为 ceil(8 n log_10(2))。使用 16.16 固定点数表示:(n * 0x00026882 + 0xFFFF) >> 16 - Simon Buchan

4

根据@Wilheim的回答:

static string BytesToString(byte[] data) {
    // Minimum length 1.
    if (data.Length == 0) return "0";

    // length <= digits.Length.
    var digits = new byte[(data.Length * 0x00026882/* (int)(Math.Log(2, 10) * 0x80000) */ + 0xFFFF) >> 16];
    int length = 1;

    // For each byte:
    for (int j = 0; j != data.Length; ++j) {
        // digits = digits * 256 + data[j].
        int i, carry = data[j];
        for (i = 0; i < length || carry != 0; ++i) {
            int value = digits[i] * 256 + carry;
            carry = Math.DivRem(value, 10, out value);
            digits[i] = (byte)value;
        }
        // digits got longer.
        if (i > length) length = i;
    }

    // Return string.
    var result = new StringBuilder(length);
    while (0 != length) result.Append((char)('0' + digits[--length]));
    return result.ToString();
}

提供的代码中再多加一些注释会更好,这将有助于理解实际发生了什么。 - ABPerson
正如所述,这是Wilheim伪代码的一个实现,因此更一般地描述了该算法 - 这是为那些想要在代码中看到它是什么样子的读者而设计的。如果我今天回答这个问题,它将会有很大不同(可能会使用JS)。 - Simon Buchan

1

如果你想了解其工作原理,可以看看C# BigInteger Class @ CodeProject,这是一个超酷的资源。

此外,我已经将该类剥离到最基本的内容以回答这个问题。它还可以进一步优化。 :)

尝试复制并粘贴以下代码,它可以正常运行!

using System;

public class BigInteger
{
    // maximum length of the BigInteger in uint (4 bytes)
    // change this to suit the required level of precision.

    private const int maxLength = 70;


    private uint[] data = null;             // stores bytes from the Big Integer
    public int dataLength;                 // number of actual chars used


    public BigInteger()
    {
        data = new uint[maxLength];
        dataLength = 1;
    }

    public BigInteger(long value)
    {
        data = new uint[maxLength];
        long tempVal = value;

        dataLength = 0;
        while (value != 0 && dataLength < maxLength)
        {
            data[dataLength] = (uint)(value & 0xFFFFFFFF);
            value >>= 32;
            dataLength++;
        }

        if (tempVal > 0)         // overflow check for +ve value
        {
            if (value != 0 || (data[maxLength - 1] & 0x80000000) != 0)
                throw (new ArithmeticException("Positive overflow in constructor."));
        }
        else if (tempVal < 0)    // underflow check for -ve value
        {
            if (value != -1 || (data[dataLength - 1] & 0x80000000) == 0)
                throw (new ArithmeticException("Negative underflow in constructor."));
        }

        if (dataLength == 0)
            dataLength = 1;
    }

    public BigInteger(ulong value)
    {
        data = new uint[maxLength];

        // copy bytes from ulong to BigInteger without any assumption of
        // the length of the ulong datatype

        dataLength = 0;
        while (value != 0 && dataLength < maxLength)
        {
            data[dataLength] = (uint)(value & 0xFFFFFFFF);
            value >>= 32;
            dataLength++;
        }

        if (value != 0 || (data[maxLength - 1] & 0x80000000) != 0)
            throw (new ArithmeticException("Positive overflow in constructor."));

        if (dataLength == 0)
            dataLength = 1;
    }

    public BigInteger(BigInteger bi)
    {
        data = new uint[maxLength];

        dataLength = bi.dataLength;

        for (int i = 0; i < dataLength; i++)
            data[i] = bi.data[i];
    }

    public BigInteger(byte[] inData)
    {
        dataLength = inData.Length >> 2;

        int leftOver = inData.Length & 0x3;
        if (leftOver != 0)         // length not multiples of 4
            dataLength++;


        if (dataLength > maxLength)
            throw (new ArithmeticException("Byte overflow in constructor."));

        data = new uint[maxLength];

        for (int i = inData.Length - 1, j = 0; i >= 3; i -= 4, j++)
        {
            data[j] = (uint)((inData[i - 3] << 24) + (inData[i - 2] << 16) +
                             (inData[i - 1] << 8) + inData[i]);
        }

        if (leftOver == 1)
            data[dataLength - 1] = (uint)inData[0];
        else if (leftOver == 2)
            data[dataLength - 1] = (uint)((inData[0] << 8) + inData[1]);
        else if (leftOver == 3)
            data[dataLength - 1] = (uint)((inData[0] << 16) + (inData[1] << 8) + inData[2]);


        while (dataLength > 1 && data[dataLength - 1] == 0)
            dataLength--;

        //Console.WriteLine("Len = " + dataLength);
    }

    public override string ToString()
    {
        return ToString(10);
    }

    public string ToString(int radix)
    {

        string charSet = "ABCDEF";
        string result = "";

        BigInteger a = this;

        BigInteger quotient = new BigInteger();
        BigInteger remainder = new BigInteger();
        BigInteger biRadix = new BigInteger(radix);

        if (a.dataLength == 1 && a.data[0] == 0)
            result = "0";
        else
        {
            while (a.dataLength > 1 || (a.dataLength == 1 && a.data[0] != 0))
            {
                singleByteDivide(a, biRadix, quotient, remainder);

                if (remainder.data[0] < 10)
                    result = remainder.data[0] + result;
                else
                    result = charSet[(int)remainder.data[0] - 10] + result;

                a = quotient;
            }
        }

        return result;
    }

    private static void singleByteDivide(BigInteger bi1, BigInteger bi2,
                                         BigInteger outQuotient, BigInteger outRemainder)
    {
        uint[] result = new uint[maxLength];
        int resultPos = 0;

        // copy dividend to reminder
        for (int i = 0; i < maxLength; i++)
            outRemainder.data[i] = bi1.data[i];
        outRemainder.dataLength = bi1.dataLength;

        while (outRemainder.dataLength > 1 && outRemainder.data[outRemainder.dataLength - 1] == 0)
            outRemainder.dataLength--;

        ulong divisor = (ulong)bi2.data[0];
        int pos = outRemainder.dataLength - 1;
        ulong dividend = (ulong)outRemainder.data[pos];

        if (dividend >= divisor)
        {
            ulong quotient = dividend / divisor;
            result[resultPos++] = (uint)quotient;

            outRemainder.data[pos] = (uint)(dividend % divisor);
        }
        pos--;

        while (pos >= 0)
        {
            dividend = ((ulong)outRemainder.data[pos + 1] << 32) + (ulong)outRemainder.data[pos];
            ulong quotient = dividend / divisor;
            result[resultPos++] = (uint)quotient;

            outRemainder.data[pos + 1] = 0;
            outRemainder.data[pos--] = (uint)(dividend % divisor);
        }

        outQuotient.dataLength = resultPos;
        int j = 0;
        for (int i = outQuotient.dataLength - 1; i >= 0; i--, j++)
            outQuotient.data[j] = result[i];
        for (; j < maxLength; j++)
            outQuotient.data[j] = 0;

        while (outQuotient.dataLength > 1 && outQuotient.data[outQuotient.dataLength - 1] == 0)
            outQuotient.dataLength--;

        if (outQuotient.dataLength == 0)
            outQuotient.dataLength = 1;

        while (outRemainder.dataLength > 1 && outRemainder.data[outRemainder.dataLength - 1] == 0)
            outRemainder.dataLength--;
    }



    public static void Main(string[] args)
    {

        BigInteger big = new BigInteger(    new byte[]
                                  { 
                                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 
                                    0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
                                  });

        Console.WriteLine(big);

    }

}

0

不,MSDN说:“十进制值类型表示从正79,228,162,514,264,337,593,543,950,335到负79,228,162,514,264,337,593,543,950,335的十进制数。” - Martin

0
这是我将字节数组转换为大十进制数的代码。
public static string ToDecimal(byte[] bytes) {
    // convert bytes array to ints array
    var ints = ToIntArray(bytes).Reverse();

    const uint limitUnit = 1_000_000_000;
    // array of decimal numbers, only 9 digits
    var intBuffer = new List<ulong>();

    // convert array of integer to array of decimal numbers
    foreach (var t in ints) {
        PushNewInt(intBuffer, t);
    }

    if (intBuffer.Count == 0) return "0";
    // convert array of decimal numbers to string
    var sb = new StringBuilder(intBuffer[^1].ToString());
    for (var i = intBuffer.Count - 2; i >= 0; --i) {
        sb.Append($"{intBuffer[i]:D9}");
    }

    return sb.ToString();
}

/* convert bytes array to ints array,
   example:
     input: [0x1, 0x2, 0x3, 0x4, 0x10, 0x11, 0x16, 0x32],
     return [0x04030201, 0x32161110]
*/
public static int[] ToIntArray(byte[] bytes) {
    // trim zero byte
    var length = bytes.Length;
    while (length > 0 && bytes[length - 1] == 0) {
        --length;
    }

    var integer = 0;
    var intArray = new int[length / 4 + ((length & 3) == 0 ? 0 : 1)];

    for (var i = 0; i < length; ++i) {
        integer = bytes[i] << (8 * i) | integer;
        if ((i & 3) != 3) continue;
        // group 4
        intArray[i / 4] = integer;
        integer = 0;
    }

    if (integer > 0) intArray[length / 4] = integer;

    return intArray;
}

/* add a integer 32 bit to decimal numbers that stored in buffer.
   ex: add 0xABCDEF01 to number 1234567890123456789012,
   means to do math: 1234567890123456789012 x 2^32 + 0xABCDEF01,
   buffer = [456789012, 567890123, 1234]
*/
void PushNewInt(IList<ulong> buffer, int newInt) {
    var added = (ulong)(uint)newInt;
    for (var i = 0; i < buffer.Count; ++i) {
        var upValue = buffer[i] << 32 | added;
        buffer[i] = upValue % limitUnit;
        added = upValue / limitUnit;
    }

    if (added == 0) return;

    buffer.Add(added % limitUnit);
    added /= limitUnit;
    if (added > 0) buffer.Add(added);
}

单元测试:
public static IEnumerable<object[]> TestByteArray => new List<object[]> {
    new object[] { new byte[] { 0x1, 0x2, 0x3, 0x4, 0x10, 0x11 }, new[] { 0x04030201, 0x1110 } },
    new object[] { new byte[] { 0xAA, 0x0, 0x00, 0x0, 0x00, 0x00 }, new[] { 0xAA } },
    new object[] {
        Convert.FromHexString("B3D5482DCA7FD3956343D8BFE3CEB4"),
        new[] { 0x2D48D5B3, -1781301302, -1076346013, 0xB4CEE3 }
    }
};

[Theory]
[MemberData(nameof(TestByteArray))]
public void ToIntArray(byte[] bytes, int[] expected) {
    var actual = Radix.ToIntArray(bytes);
    // var integerArray = MemoryMarshal.Cast<byte, int>(bytes.AsSpan());
    // var expected = integerArray.ToArray();
    Assert.Equal(expected, actual);
}


private static IEnumerable<byte[]> _toDecimalTest = new List<byte[]> {
    new byte[] { 0xAA, 0x0, 0x00, 0x0, 0x00, 0x00 },
    Convert.FromHexString("B3D5482DCA7FD3956343D8BFE3CEB4"),
    RandomBytes(219),
    new byte[]
    { 
        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 
        0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
    }
};

private static byte[] RandomBytes(int count) {
    var bytes = new byte[count];
    Random.Shared.NextBytes(bytes);
    return bytes;
}

public static IEnumerable<object[]> ToDecimalTestData => _toDecimalTest.Select(
    bytes => new object[] {
        bytes, new BigInteger(bytes, isUnsigned: true).ToString()
    }
);

[Theory]
[MemberData(nameof(ToDecimalTestData))]
public void ToDecimal(byte[] bytes, string expected) {
    var actual = Radix.ToDecimal(bytes);
    Assert.Equal(expected, actual);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接