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源码)
/// <summary>
/// 非负大数Karatsuba乘法,数组第一个<see cref="uint"/>存放最高32位,最后一个<see cref="uint"/>存放最低32位。
/// </summary>
public static uint[] MultiplyKaratsubaNonegative(uint[] left, uint[] right)
{
var xlen = left.Length;
var ylen = right.Length;
// The number of ints in each half of the number.
var half = (Math.Max(xlen, ylen) + 1) >> 1;
// xl and yl are the lower halves of x and y respectively,
// xh and yh are the upper halves.
var xl = GetLower(left, half);
var xh = GetUpper(left, half);
var yl = GetLower(right, half);
var yh = GetUpper(right, half);
var p1 = MultiplyKaratsubaNonegative(xh, yh); // p1 = xh*yh
var p2 = MultiplyKaratsubaNonegative(xl, yl); // p2 = xl*yl
// p3 = (xl + xh)*(y1 + yh) - p1 - p2 = xh*yl + xl*yh
var t1 = MultiplyKaratsubaNonegative(xh, yl);
var t2 = MultiplyKaratsubaNonegative(xl, yh);
var p3 = AddNonegative(t1, t2);
// result = p1 * 2^(32*2*half) + p3 * 2^(32*half) + p2
Array.Resize(ref p1, (half << 1) + p1.Length);
Array.Resize(ref p3, half + p3.Length);
var result = AddNonegative(p1, p3);
return AddNonegative(result, p2);
}
private static uint[] GetLower(uint[] value, int n)
{
if (value.Length <= n)
{
return value;
}
return value.Skip(value.Length - n).Take(n).ToArray();
}
private static uint[] GetUpper(uint[] value, int n)
{
if (value.Length <= n)
{
return new uint[] { 0 };
}
return value.Take(value.Length - n).ToArray();
}
其中大数左移(32*half)位,因为移位数是32的倍数,而uint是32位,所以只需要将数组长度扩大half就可以了。测试
[TestMethod]
public void MultiplyKaratsubaNonegativeTest()
{
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 = MultiplyKaratsubaNonegative(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) MultiplyKaratsuba((uint[], bool) left, (uint[], bool) right)
{
if (IsZero(left))
return left;
if (IsZero(right))
return right;
if (IsAbsOne(left))
return (right.Item1, right.Item2 == left.Item2);
if (IsAbsOne(right))
return (left.Item1, left.Item2 == right.Item2);
return (MultiplyKaratsubaNonegative(left.Item1, right.Item1), left.Item2 == right.Item2);
}
private static bool IsZero((uint[], bool) value)
=> value.Item1.Length == 1 && value.Item1[0] == 0;
private static bool IsAbsOne((uint[], bool) value)
=> value.Item1.Length == 1 && value.Item1[0] == 1;
测试
[TestMethod]
public void MultiplyKaratsubaTest()
{
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 = MultiplyKaratsuba(ToTuple(left), ToTuple(right));
var expected = left * right;
Assert.AreEqual(expected, ValueOf(test));
}
}