【筆記】大數加減法 (Java BigInteger原始碼)
阿新 • • 發佈:2018-11-10
BigInteger與uint[]
用uint[]來表示非負大數,其中陣列開頭是大數的最高32位,陣列結尾是大數最低32位。其與BigInteger的轉換方法
/// <summary> /// <see cref="uint"/>陣列轉為非負大整數 /// </summary> private static BigInteger ValueOf(uint[] value) { var result = BigInteger.Zero; foreach (var num in value) { result <<= 32; result |= (num & 0xFFFF_FFFF); } return result; } /// <summary> /// 非負大整數轉為<see cref="uint"/>陣列 /// </summary> private static uint[] ToIntArray(BigInteger value) { var byteCount = value.GetByteCount(); var len = (int)Math.Ceiling(byteCount / 4d); var result = new uint[len]; for (var i = len - 1; i >= 0; --i) { result[i] = (uint)(value & 0xFFFF_FFFF); value >>= 32; } return result; }
測試
[TestMethod] public void ConvertTest() { var bytes = new byte[32]; Random ran = new Random(); for (var i = 0; i < 100; ++i) { ran.NextBytes(bytes); var value = BigInteger.Abs(new BigInteger(bytes)); var test = ToIntArray(value); Assert.AreEqual(value, ValueOf(test)); } }
Unsigned加法(Java BigInteger原始碼)
private static readonly long LONG_MASK = 0xFFFF_FFFFL; /// <summary> /// 大數加法,陣列第一個int存放最高32位,最後一個int存放最低32位。 /// </summary> public static uint[] AddNonegative(uint[] left, uint[] right) { // If left is shorter, swap the two arrays if (left.Length < right.Length) { var tmp = left; left = right; right = tmp; } var xIndex = left.Length; var yIndex = right.Length; var result = new uint[xIndex]; var sum = 0L; if (yIndex == 1) { sum = (left[--xIndex] & LONG_MASK) + (right[0] & LONG_MASK); result[xIndex] = (uint)sum; } else { // Add common parts of both numbers while (yIndex > 0) { sum = (left[--xIndex] & LONG_MASK) + (right[--yIndex] & LONG_MASK) + (long)((ulong)sum >> 32); result[xIndex] = (uint)sum; } } // Copy remainder of longer number while carry propagation is required var carry = ((ulong)sum >> 32) != 0; while (xIndex > 0 && carry) carry = ((result[--xIndex] = left[xIndex] + 1) == 0); // Copy remainder of longer number while (xIndex > 0) result[--xIndex] = left[xIndex]; // Grow result if necessary if (carry) { Array.Resize(ref result, result.Length + 1); for (var i = result.Length - 1; i > 0; --i) result[i] = result[i - 1]; result[0] = 0x01; } return result; }
測試
[TestMethod]
public void AddNonegativeTest()
{
var bytes = new byte[32];
Random ran = new Random();
for(var i = 0; i < 100; ++i)
{
ran.NextBytes(bytes);
var left = BigInteger.Abs(new BigInteger(bytes));
ran.NextBytes(bytes);
var right = BigInteger.Abs(new BigInteger(bytes));
var test = AddNonegative(ToIntArray(left), ToIntArray(right));
var expected = left + right;
Assert.AreEqual(expected, ValueOf(test));
}
}
Unsigned減法(Java BigInteger原始碼)
/// <summary>
/// 大數減法,<paramref name="left"/> 大於<paramref name="right"/>。陣列第一個int存放最高32位,最後一個int存放最低32位。
/// </summary>
public static uint[] SubtractNonegative(uint[] left, uint[] right)
{
var bigIndex = left.Length;
var result = new uint[bigIndex];
var littleIndex = right.Length;
var difference = 0L;
// Subtract common parts of both numbers
while (littleIndex > 0)
{
difference = (left[--bigIndex] & LONG_MASK) -
(right[--littleIndex] & LONG_MASK) +
(difference >> 32);
result[bigIndex] = (uint)difference;
}
// Subtract remainder of longer number while borrow propagates
var borrow = (difference >> 32) != 0;
while (bigIndex > 0 && borrow)
borrow = ((result[--bigIndex] = left[bigIndex] - 1) == uint.MaxValue);
// Copy remainder of longer number
while (bigIndex > 0)
result[--bigIndex] = left[bigIndex];
return result;
}
測試
[TestMethod]
public void SubtractNonegativeTest()
{
var bytes = new byte[32];
Random ran = new Random();
for (var i = 0; i < 100; ++i)
{
ran.NextBytes(bytes);
var left = BigInteger.Abs(new BigInteger(bytes));
ran.NextBytes(bytes);
var right = BigInteger.Abs(new BigInteger(bytes));
while (right > left) right >>= 1;
var test = SubtractNonegative(ToIntArray(left), ToIntArray(right));
var expected = left - right;
Assert.AreEqual(expected, ValueOf(test));
}
}
BigInteger與(uint[], bool)
上面的減法有太多限制,加法也不能計算負數,接下來推廣到通用加減法。
用(uint[], bool)來表示有符號大數,其中uint[]是大數的絕對值,bool為false時是負數。
其與BigInteger之間轉換方法
/// <summary>
/// (<see cref="uint"/>[], <see cref="bool"/>) to <see cref="BigInteger"/>
/// </summary>
private BigInteger ValueOf((uint[], bool) value)
{
var result = BigInteger.Zero;
foreach (var num in value.Item1)
{
result <<= 32;
result |= (num & 0xFFFF_FFFF);
}
return value.Item2 ? result : -result;
}
/// <summary>
/// <see cref="BigInteger"/> to (<see cref="uint"/>[], <see cref="bool"/>)
/// </summary>
private (uint[], bool) ToTuple(BigInteger value)
{
var positive = BigInteger.Abs(value);
var byteCount = positive.GetByteCount();
var len = (int)Math.Ceiling(byteCount / 4d);
var result = new uint[len];
for (var i = len - 1; i >= 0; --i)
{
result[i] = (uint)(positive & 0xFFFF_FFFF);
positive >>= 32;
}
return (result, value >= 0);
}
測試
[TestMethod]
public void ConvertTest()
{
var bytes = new byte[32];
Random ran = new Random();
for (var i = 0; i < 100; ++i)
{
ran.NextBytes(bytes);
var value = new BigInteger(bytes);
var test = ToTuple(value);
Assert.AreEqual(value, ValueOf(test));
}
}
Signed加法
/// <summary>
/// 大數加法,陣列第一個<see cref="uint"/>存放最高32位,最後一個<see cref="uint"/>存放最低32位。
/// </summary>
public static (uint[], bool) Add((uint[], bool) left, (uint[], bool) right)
{
if (IsZero(left))
return right;
if (IsZero(right))
return left;
if (left.Item2 == right.Item2)//正負相同
return (AddNonegative(left.Item1, right.Item1), left.Item2);
var cmp = Compare(left.Item1, right.Item1);
if (cmp < 0)//abs(left) < abs(right)
return (SubtractNonegative(right.Item1, left.Item1), right.Item2);
else
return (SubtractNonegative(left.Item1, right.Item1), left.Item2);
}
private static int Compare(uint[] left, uint[] right)
{
var len1 = left.Length;
var len2 = right.Length;
if (len1 < len2)
return -1;
if (len1 > len2)
return 1;
for (var i = 0; i < len1; i++)
{
var a = left[i];
var b = right[i];
if (a != b)
return ((a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1;
}
return 0;
}
private static bool IsZero((uint[], bool) value)
=> value.Item1.Length == 1 && value.Item1[0] == 0;
測試
[TestMethod]
public void AddTest()
{
var bytes = new byte[32];
Random ran = new Random();
for (var i = 0; i < 100; ++i)
{
ran.NextBytes(bytes);
var left = new BigInteger(bytes);
ran.NextBytes(bytes);
var right = new BigInteger(bytes);
var test = Add(ToTuple(left), ToTuple(right));
var expected = left + right;
Assert.AreEqual(expected, ValueOf(test));
}
}
Signed減法
/// <summary>
/// 大數減法,陣列第一個<see cref="uint"/>存放最高32位,最後一個<see cref="uint"/>存放最低32位。
/// </summary>
public static (uint[], bool) Subtract((uint[], bool) left, (uint[], bool) right)
{
if (IsZero(left))
return (right.Item1, !right.Item2);
if (IsZero(right))
return left;
if (left.Item2 != right.Item2)//正負相異
return (AddNonegative(left.Item1, right.Item1), left.Item2);
var cmp = Compare(left.Item1, right.Item1);
if (cmp == 0)
return (new uint[] { 0 }, true);
else if (cmp < 0)//abs(left) < abs(right)
return (SubtractNonegative(right.Item1, left.Item1), !right.Item2);
else
return (SubtractNonegative(left.Item1, right.Item1), left.Item2);
}
測試
[TestMethod]
public void SubtractTest()
{
var bytes = new byte[32];
Random ran = new Random();
for (var i = 0; i < 100; ++i)
{
ran.NextBytes(bytes);
var left = new BigInteger(bytes);
ran.NextBytes(bytes);
var right = new BigInteger(bytes);
var test = Subtract(ToTuple(left), ToTuple(right));
var expected = left - right;
Assert.AreEqual(expected, ValueOf(test));
}
}