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));
}
}
(uint[], bool)左移
/// <summary>
/// <paramref name="value"/> << <paramref name="k"/> (<paramref name="k"/> > 0)
/// </summary>
public static (uint[], bool) ShiftLeft((uint[], bool) value, int k)
{
var nInts = k >> 5;
var nBits = k & 0x1F;
var len = value.Item1.Length;
uint[] result;
if (nBits == 0)//k = 0 (mod 32)
{
result = value.Item1.ToArray();
Array.Resize(ref result, len + nInts);
}
else
{
var i = 0;
var nBits2 = 32 - nBits;
var highBits = value.Item1[0] >> nBits2;
if (highBits != 0)
{
result = new uint[len + nInts + 1];
result[i++] = highBits;
}
else
{
result = new uint[len + nInts];
}
var j = 0;
while (j < len - 1)
result[i++] = (value.Item1[j++] << nBits) | (value.Item1[j] >> nBits2);
result[i] = value.Item1[j] << nBits;
}
return (result, value.Item2);
}
(uint[], bool)右移
/// <summary>
/// <paramref name="value"/> >> <paramref name="k"/> (<paramref name="k"/> > 0)
/// </summary>
public static (uint[], bool) ShiftRight((uint[], bool) value, int k)
{
var nInts = k >> 5;
var nBits = k & 0x1F;
var len = value.Item1.Length;
uint[] result;
// Special case: entire contents shifted off the end
if (nInts >= len)
return (new uint[] { 0 }, true);
if (nBits == 0)// k = 0 (mod 32)
{
var newMagLen = len - nInts;
result = value.Item1.Take(newMagLen).ToArray();
}
else
{
var i = 0;
var highBits = value.Item1[0] >> nBits;
if (highBits != 0)
{
result = new uint[len - nInts];
result[i++] = highBits;
}
else
{
result = new uint[len - nInts - 1];
}
int nBits2 = 32 - nBits;
int j = 0;
while (j < len - nInts - 1)
result[i++] = (value.Item1[j++] << nBits2) | (value.Item1[j] >> nBits);
}
if (!value.Item2)
{
// Find out whether any one-bits were shifted off the end.
var onesLost = false;
for (int i = len - 1, j = len - nInts; i >= j && !onesLost; i--)
onesLost = (value.Item1[i] != 0);
if (!onesLost && nBits != 0)
onesLost = (value.Item1[len - nInts - 1] << (32 - nBits) != 0);
if (onesLost)
result = Increment(result);
}
return (result, value.Item2);
}
private static uint[] Increment(uint[] value)
{
var lastSum = 0u;
for (var i = value.Length - 1; i >= 0 && lastSum == 0; i--)
lastSum = (value[i] += 1);
if (lastSum == 0)
{
value = new uint[value.Length + 1];
value[0] = 1;
}
return value;
}
测试
[TestMethod]
public void ShiftTest()
{
var bytes = new byte[32];
for (var i = 0; i < 100; ++i)
{
ran.NextBytes(bytes);
var value = new BigInteger(bytes);
var k = ran.Next(1, 10);
var tuple = ToTuple(value);
var test = ShiftLeft(tuple, k);
var expected = value << k;
Assert.AreEqual(expected, ValueOf(test));
ran.NextBytes(bytes);
value = new BigInteger(bytes);
tuple = ToTuple(value);
test = ShiftRight(tuple, k);
expected = value >> k;
Assert.AreEqual(expected, ValueOf(test));
}
}
Toom-Cook-3 Multiplication
/// <summary>
/// ToomCook3乘法,数组第一个<see cref="uint"/>存放最高32位,最后一个<see cref="uint"/>存放最低32位。
/// </summary>
public static (uint[], bool) MultiplyToomCook3((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);
var alen = left.Item1.Length;
var blen = right.Item1.Length;
var largest = Math.Max(alen, blen);
// k is the size (in ints) of the lower-order slices.
var k = (largest + 2) / 3; // Equal to ceil(largest/3)
// r is the size (in ints) of the highest-order slice.
var r = largest - (k << 1);
// Obtain slices of the numbers. a2 and b2 are the most significant
// bits of the numbers a and b, and a0 and b0 the least significant.
var a2 = GetToomSlice(left, k, r, 0, largest);
var a1 = GetToomSlice(left, k, r, 1, largest);
var a0 = GetToomSlice(left, k, r, 2, largest);
var b2 = GetToomSlice(right, k, r, 0, largest);
var b1 = GetToomSlice(right, k, r, 1, largest);
var b0 = GetToomSlice(right, k, r, 2, largest);
var v0 = MultiplyToomCook3(a0, b0);
var da1 = Add(a2, a0);
var db1 = Add(b2, b0);
var vm1 = MultiplyToomCook3(Subtract(da1, a1), Subtract(db1, b1));
da1 = Add(da1, a1);
db1 = Add(db1, b1);
var v1 = MultiplyToomCook3(da1, db1);
var tmp = Add(db1, b2);
tmp = ShiftLeft(tmp, 1);
tmp = Subtract(tmp, b0);
var v2 = Add(da1, a2);
v2 = ShiftLeft(v2, 1);
v2 = Subtract(v2, a0);
v2 = Multiply(v2, tmp);
var vinf = MultiplyToomCook3(a2, b2);
// The algorithm requires two divisions by 2 and one by 3.
// All divisions are known to be exact, that is, they do not produce
// remainders, and all results are positive. The divisions by 2 are
// implemented as right shifts which are relatively efficient, leaving
// only an exact division by 3, which is done by a specialized
// linear-time algorithm.
var t2 = Subtract(v2, vm1); t2 = ExactDivideBy3(t2);
var tm1 = Subtract(v1, vm1); tm1 = ShiftRight(tm1, 1);
var t1 = Subtract(v1, v0);
t2 = Subtract(t2, t1); t2 = ShiftRight(t2, 1);
t1 = Subtract(t1, tm1); t1 = Subtract(t1, vinf);
t2 = Subtract(t2, ShiftLeft(vinf, 1));
tm1 = Subtract(tm1, t2);
// Number of bits to shift left.
var ss = k << 5;
var result = ShiftLeft(vinf, ss); result = Add(result, t2);
result = ShiftLeft(result, ss); result = Add(result, t1);
result = ShiftLeft(result, ss); result = Add(result, tm1);
result = ShiftLeft(result, ss); result = Add(result, v0);
return left.Item2 == right.Item2
? result
: (result.Item1, !result.Item2);
}
private static (uint[], bool) GetToomSlice((uint[], bool) value, int lowerSize, int upperSize, int slice, int fullsize)
{
var start = 0; var end = 0;
var len = value.Item1.Length;
var offset = fullsize - len;
if (slice == 0)
{
start = 0 - offset;
end = upperSize - 1 - offset;
}
else
{
start = upperSize + (slice - 1) * lowerSize - offset;
end = start + lowerSize - 1;
}
if (start < 0)
{
start = 0;
}
if (end < 0)
{
return (new uint[] { 0 }, true);
}
var sliceSize = (end - start) + 1;
if (sliceSize <= 0)
{
return (new uint[] { 0 }, true);
}
// While performing Toom-Cook, all slices are positive and
// the sign is adjusted when the final number is composed.
if (start == 0 && sliceSize >= len)
{
return (value.Item1, true);
}
//var intSlice = new uint[sliceSize];
//Array.Copy(value, start, intSlice, 0, sliceSize);
return (value.Item1.Skip(start).Take(sliceSize).ToArray(), true);
}
private static (uint[], bool) ExactDivideBy3((uint[], bool) value)
{
var len = value.Item1.Length;
var result = new uint[len];
long x, w, q;
var borrow = 0L;
for (var i = len - 1; i >= 0; i--)
{
x = (value.Item1[i] & LONG_MASK);
w = x - borrow;
if (borrow > x)
{ // Did we make the number go negative?
borrow = 1L;
}
else
{
borrow = 0L;
}
// 0xAAAAAAAB is the modular inverse of 3 (mod 2^32). Thus,
// the effect of this is to divide by 3 (mod 2^32).
// This is much faster than division on most architectures.
q = (w * 0xAAAA_AAABL) & LONG_MASK;
result[i] = (uint)q;
// Now check the borrow. The second check can of course be
// eliminated if the first fails.
if (q >= 0x5555_5556L)
{
borrow++;
if (q >= 0xAAAA_AAABL)
borrow++;
}
}
return (result, value.Item2);
}
测试
[TestMethod]
public void MultiplyToomCook3Test()
{
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 = MultiplyToomCook3(ToTuple(left), ToTuple(right));
var expected = left * right;
Assert.AreEqual(expected, ValueOf(test));
}
}