注意:这比特定的C#问题更像是逻辑/数学问题.
我有自己的类调用Number
- 它只包含两个单独的字节数组,称为Whole
和Decimal
.这些字节数组每个都代表一个无限大的整数,但是,当它们放在一起时,它的想法是它们创建一个带小数部分的整数.
字节以小端格式存储,表示数字.我正在创建一个方法AddNumbers
,它将把这些中Number
的两个加在一起.
此方法依赖于另一个调用的方法PerformAdd
,它只将两个数组相加.它只需要一个指向最终字节数组的指针,一个指向要添加的数组的指针,以及一个指向要添加的第二个数组的指针 - 以及每个数组的长度.这两个数组只是命名为"更大"和"更小".以下是此方法的代码:
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;
}
Run Code Online (Sandbox Code Playgroud)
这个方法不是问题所在 - 问题在于我如何使用它.这AddNumbers
是使用它的方法.它的工作方式很好 - 它将两个独立的字节数组织成"更大"(更大的含义,具有更高的字节长度)和"更小".然后,它会创建的指针,它这两个Whole
和Decimal
分开.问题在于小数部分.
假设我们将数字1251
加2185
在一起,在这种情况下你会得到3436
- 这样就完美了!
再举一个例子:你有数字4.6
并添加1.2
- 再次,这工作正常,你得到5.8
.问题出在下一个例子中.
然而,我们有,15.673
并且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;
}
Run Code Online (Sandbox Code Playgroud)
如果您只需要BigDecimal
C#,我建议您找到并使用现有的实现。例如https://gist.github.com/nberardi/2667136(我不是作者,但看起来不错)。
如果您出于任何原因(学校等)必须实施它,那么我只会诉诸于使用BigInteger
.
如果您必须使用字节数组来实现它......您仍然可以从使用比例的想法中受益。显然,您必须在执行“PerformAdd”等操作后取出任何额外的数字,然后将它们转入主号码。
然而问题还不止于此。当您开始实现乘法时,您将遇到更多问题,并且您将不得不开始不可避免地混合小数和整数部分。
8.73*0.11 -> 0.9603
0.12*0.026 -> 0.00312
正如您所看到的,整数和小数部分混合在一起,然后小数部分增长为更长的序列
但是,如果您将它们表示为:
873|2 * 11|2 -> 873*11|4 -> 9603|4 -> 0.9603
12|2 & 26|3 -> 12*26|5 -> 312|5 -> 0.00312
这些问题就会消失。