1. 程式人生 > >【筆記】大數乘法之Karatsuba演算法 (Java BigInteger原始碼)

【筆記】大數乘法之Karatsuba演算法 (Java BigInteger原始碼)

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));
    }
}