首页 > 解决方案 > 如何将数字从十进制传送到整个字节数组

问题描述

注意:这更像是一个逻辑/数学问题,而不是特定的 C# 问题。

我有我自己的类Number- 它非常简单地包含两个单独的字节数组,称为WholeDecimal。这些字节数组每个都代表一个无限大的整数,但是,当放在一起时,他们的想法是它们创建一个带有小数部分的整数。

字节以小端格式存储,代表一个数字。我正在创建一个名为的方法AddNumbers,它将其中两个加Number在一起。

此方法依赖于另一个名为 的方法PerformAdd,它只是将两个数组相加。它只接受一个指向最终字节数组的指针、一个指向要添加的数组的指针和一个指向要添加的第二个数组的指针——以及每个数组的长度。这两个数组只是命名为 "larger" 和 "smaller"。这是此方法的代码:

    private static unsafe void PerformAdd(byte* finalPointer, byte* largerPointer, byte* smallerPointer, int largerLength, int smallerLength)
    {
        int carry = 0;

        // Go through all the items that can be added, and work them out.
        for (int i = 0; i < smallerLength; i++)
        {
            var add = *largerPointer-- + *smallerPointer-- + carry;

            // Stick the result of this addition in the "final" array.
            *finalPointer-- = (byte)(add & 0xFF);

            // Now, set a carry from this.
            carry = add >> 8;
        }

        // Now, go through all the remaining items (which don't need to be added), and add them to the "final" - still working with the carry.
        for (int i = smallerLength; i < largerLength; i++)
        {
            var wcarry = *largerPointer-- + carry;

            // Stick the result of this addition in the "final" array.
            *finalPointer-- = (byte)(wcarry & 0xFF);

            // Now, set a carry from this.
            carry = wcarry >> 8;
        }

        // Now, if we have anything still left to carry, carry it into a new byte.
        if (carry > 0)
            *finalPointer-- = (byte)carry;
    }

这种方法不是问题所在——问题在于我如何使用它。这是使用它的AddNumbers方法。它的工作方式很好 - 它将两个单独的字节数组组织成“更大”(更大意味着具有更高字节长度)和“更小”。然后它创建指针,它为WholeDecimal单独地执行此操作。问题在于小数部分。

假设我们将数字相加12512185在这种情况下你会得到3436- 这样就完美了!

再举一个例子:你有数字4.6并加1.2- 再一次,这很好用,你得到5.8. 问题出现在下一个示例中。

我们有15.673and 1.783,你会期望17.456,然而,实际上,这会返回: 16.1456,原因是它不带“1”。

所以,这是我的问题:我将如何实现一种知道何时以及如何执行此操作的方法?这是我的AddNumbers方法的代码:

    public static unsafe Number AddNumbers(Number num1, Number num2)
    {
        // Store the final result.
        Number final = new Number(new byte[num1.Whole.Length + num2.Whole.Length], new byte[num1.Decimal.Length + num2.Decimal.Length]);

        // We're going to figure out which number (num1 or num2) has more bytes, and then we'll create pointers to smallest and largest.
        fixed (byte* num1FixedWholePointer = num1.Whole, num1FixedDecPointer = num1.Decimal, num2FixedWholePointer = num2.Whole, num2FixedDecPointer = num2.Decimal,
            finalFixedWholePointer = final.Whole, finalFixedDecimalPointer = final.Decimal)
        {
            // Create a pointer and figure out which whole number has the most bytes.
            var finalWholePointer = finalFixedWholePointer + (final.Whole.Length - 1);
            var num1WholeLarger = num1.Whole.Length > num2.Whole.Length ? true : false;

            // Store the larger/smaller whole number lengths.
            var largerLength = num1WholeLarger ? num1.Whole.Length : num2.Whole.Length;
            var smallerLength = num1WholeLarger ? num2.Whole.Length : num1.Whole.Length;

            // Create pointers to the whole numbers (the largest amount of bytes and smallest amount of bytes).
            var largerWholePointer = num1WholeLarger ? num1FixedWholePointer + (num1.Whole.Length - 1) : num2FixedWholePointer + (num2.Whole.Length - 1);
            var smallerWholePointer = num1WholeLarger ? num2FixedWholePointer + (num2.Whole.Length - 1) : num1FixedWholePointer + (num1.Whole.Length - 1);

            // Handle decimal numbers.
            if (num1.Decimal.Length > 0 || num2.Decimal.Length > 0)
            {
                // Create a pointer and figure out which decimal has the most bytes.
                var finalDecPointer = finalFixedDecimalPointer + (final.Decimal.Length - 1);
                var num1DecLarger = num1.Decimal.Length > num2.Decimal.Length ? true : false;

                // Store the larger/smaller whole number lengths.
                var largerDecLength = num1DecLarger ? num1.Decimal.Length : num2.Decimal.Length;
                var smallerDecLength = num1DecLarger ? num2.Whole.Length : num1.Decimal.Length;

                // Store pointers for decimals as well.
                var largerDecPointer = num1DecLarger ? num1FixedDecPointer + (num1.Decimal.Length - 1) : num2FixedDecPointer + (num2.Decimal.Length - 1);
                var smallerDecPointer = num1DecLarger ? num2FixedDecPointer + (num2.Decimal.Length - 1) : num1FixedDecPointer + (num1.Decimal.Length - 1);

                // Add the decimals first.
                PerformAdd(finalDecPointer, largerDecPointer, smallerDecPointer, largerDecLength, smallerDecLength);
            }

            // Add the whole number now.
            PerformAdd(finalWholePointer, largerWholePointer, smallerWholePointer, largerLength, smallerLength);
        }

        return final;
    }

标签: c#mathbinary

解决方案


您选择的格式基本上很难使用,我不知道有人在此任务中使用相同的格式。例如,这种格式的乘法或除法必须很难实现。

实际上,我认为您存储的信息不足以首先唯一地恢复值。在您的格式中存储的表示形式与0.1和有何不同0.01?我认为您无法区分这两个值。

您面临的问题是同一问题的较小副作用:您存储十进制值的二进制表示,并期望能够暗示十进制表示的唯一大小(位数)。你不能这样做,因为当发生十进制溢出时,你不能保证你的基于 256 的存储值也会溢出。实际上,更常见的情况是不同时发生。

除了显式存储与小数点后位数相等的内容外,我认为您无法以任何其他方式解决此问题。而且,如果您仍然要这样做,为什么不切换到更简单的 single 格式BigInteger(是的,它是标准库的一部分,尽管没有类似的东西BigDecimal)和scale? 这是许多类似库使用的格式。在该格式123.45中,存储为一对12345and -2(用于小数位),而1.2345存储为一对12345and -4。这种格式的乘法几乎是一项微不足道的任务(鉴于BigInteger已经实现了乘法,所以你只需要能够在最后截断零)。加法和减法并不那么简单,但是您需要首先使用乘以 10 来匹配两个数字的比例,然后使用标准加法BigInteger,然后再归一化(最后删除零)。除法仍然很困难,您必须决定要支持哪些舍入策略,因为不能保证两个数字的除法适合固定精度的数字。


推荐阅读