phobos/std/bigint.d
Don Clugston 9ecf947290 5568 A problem with BigInt modulus
This is quite subtle. The recursive division algorithm has a possibility of one
bit of overflow in the second recursive call, in cases where the top quarter of
the dividend is identical to the top half of the divisor.
The description of the recursive division algorithm in "Modern Computer
Arithmetic" 0.5.9 is rather vague about this (which is why I missed it
originally, though the test case does cause assert failure in biguint).
Unfortunately it requires fairly substantial changes to the function, which
makes the algorithm a lot less elegant. But I have managed to implement it
without any change to the basic division algorithm (which is very fast).
2011-02-23 21:20:48 +01:00

517 lines
15 KiB
D

/** Arbitrary-precision ('bignum') arithmetic
*
* Performance is optimized for numbers below ~1000 decimal digits.
* For X86 machines, highly optimised assembly routines are used.
*
* The following algorithms are currently implemented:
* $(UL
* $(LI Karatsuba multiplication)
* $(LI Squaring is optimized independently of multiplication)
* $(LI Divide-and-conquer division)
* $(LI Binary exponentiation)
* )
*
* For very large numbers, consider using the $(WEB gmplib.org, GMP library) instead.
*
* License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
* Authors: Don Clugston
* Source: $(PHOBOSSRC std/_bigint.d)
*/
/* Copyright Don Clugston 2008 - 2010.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
module std.bigint;
private import std.internal.math.biguintcore;
/** A struct representing an arbitrary precision integer
*
* All arithmetic operations are supported, except
* unsigned shift right (>>>). Logical operations are not currently supported.
*
* BigInt implements value semantics using copy-on-write. This means that
* assignment is cheap, but operations such as x++ will cause heap
* allocation. (But note that for most bigint operations, heap allocation is
* inevitable anyway).
Example:
----------------------------------------------------
BigInt a = "9588669891916142";
BigInt b = "7452469135154800";
auto c = a * b;
assert(c == "71459266416693160362545788781600");
auto d = b * a;
assert(d == "71459266416693160362545788781600");
assert(d == c);
d = c * "794628672112";
assert(d == "56783581982794522489042432639320434378739200");
auto e = c + d;
assert(e == "56783581982865981755459125799682980167520800");
auto f = d + c;
assert(f == e);
auto g = f - c;
assert(g == d);
g = f - d;
assert(g == c);
e = 12345678;
g = c + e;
auto h = g / b;
auto i = g % b;
assert(h == a);
assert(i == e);
BigInt j = "-0x9A56_57f4_7B83_AB78";
j ^^= 11;
----------------------------------------------------
*
*/
struct BigInt
{
private:
BigUint data; // BigInt adds signed arithmetic to BigUint.
bool sign = false;
public:
/// Construct a BigInt from a decimal or hexadecimal string.
/// The number must be in the form of a D decimal or hex literal:
/// It may have a leading + or - sign; followed by "0x" if hexadecimal.
/// Underscores are permitted.
/// BUG: Should throw a IllegalArgumentException/ConvError if invalid character found
this(T:string)(T s)
{
bool neg = false;
if (s[0] == '-') {
neg = true;
s = s[1..$];
} else if (s[0] == '+') {
s = s[1..$];
}
data = 0UL;
auto q = 0X3;
bool ok;
assert(isZero());
if (s.length > 2 && (s[0..2] == "0x" || s[0..2] == "0X"))
{
ok = data.fromHexString(s[2..$]);
} else {
ok = data.fromDecimalString(s);
}
assert(ok);
if (isZero())
neg = false;
sign = neg;
}
///
this(T: long) (T x)
{
data = data.init; // @@@: Workaround for compiler bug
opAssign(x);
}
///
void opAssign(T: long)(T x)
{
data = cast(ulong)((x < 0) ? -x : x);
sign = (x < 0);
}
///
void opAssign(T:BigInt)(T x)
{
data = x.data;
sign = x.sign;
}
// BigInt op= integer
BigInt opOpAssign(string op, T)(T y)
if ((op=="+" || op=="-" || op=="*" || op=="/" || op=="%"
|| op==">>" || op=="<<" || op=="^^") && is (T: long))
{
ulong u = cast(ulong)(y < 0 ? -y : y);
static if (op=="+")
{
data = BigUint.addOrSubInt(data, u, sign != (y<0), sign);
}
else static if (op=="-")
{
data = BigUint.addOrSubInt(data, u, sign == (y<0), sign);
}
else static if (op=="*")
{
if (y == 0) {
sign = false;
data = 0UL;
} else {
sign = ( sign != (y<0) );
data = BigUint.mulInt(data, u);
}
}
else static if (op=="/")
{
assert(y!=0, "Division by zero");
static assert(!is(T == long) && !is(T == ulong));
data = BigUint.divInt(data, cast(uint)u);
sign = data.isZero() ? false : sign ^ (y < 0);
}
else static if (op=="%")
{
assert(y!=0, "Division by zero");
static assert(!is(T==long) && !is(T==ulong));
data = cast(ulong)BigUint.modInt(data, cast(uint)u);
// x%y always has the same sign as x.
// This is not the same as mathematical mod.
}
else static if (op==">>" || op=="<<")
{
// Do a left shift if y>0 and <<, or
// if y<0 and >>; else do a right shift.
if (y == 0)
return this;
else if ((y > 0) == (op=="<<"))
{
// Sign never changes during left shift
data = data.opShl(u);
} else
{
data = data.opShr(u);
if (data.isZero())
sign = false;
}
}
else static if (op=="^^")
{
sign = (y & 1) ? sign : false;
data = BigUint.pow(data, u);
}
else static assert(0, "BigInt " ~ op[0..$-1] ~ "= " ~ T.stringof ~ " is not supported");
return this;
}
// BigInt op= BigInt
BigInt opOpAssign(string op, T)(T y)
if ((op=="+" || op== "-" || op=="*" || op=="/" || op=="%")
&& is (T: BigInt))
{
static if (op == "+")
{
data = BigUint.addOrSub(data, y.data, sign != y.sign, &sign);
}
else static if (op == "-")
{
data = BigUint.addOrSub(data, y.data, sign == y.sign, &sign);
}
else static if (op == "*")
{
data = BigUint.mul(data, y.data);
sign = isZero() ? false : sign ^ y.sign;
}
else static if (op == "/")
{
y.checkDivByZero();
if (!isZero())
{
sign ^= y.sign;
data = BigUint.div(data, y.data);
}
}
else static if (op == "%")
{
y.checkDivByZero();
if (!isZero())
{
data = BigUint.mod(data, y.data);
// x%y always has the same sign as x.
if (isZero())
sign = false;
}
}
else static assert(0, "BigInt " ~ op[0..$-1] ~ "= " ~ T.stringof ~ " is not supported");
return this;
}
// BigInt op BigInt
BigInt opBinary(string op, T)(T y)
if ((op=="+" || op == "*" || op=="-" || op=="/" || op=="%") && is (T: BigInt))
{
BigInt r = this;
return r.opOpAssign!(op)(y);
}
// BigInt op integer
BigInt opBinary(string op, T)(T y)
if ((op=="+" || op == "*" || op=="-" || op=="/"
|| op==">>" || op=="<<" || op=="^^") && is (T: long))
{
BigInt r = this;
return r.opOpAssign!(op)(y);
}
//
int opBinary(string op, T : int)(T y)
if (op == "%")
{
assert(y!=0);
uint u = y < 0 ? -y : y;
int rem = BigUint.modInt(data, u);
// x%y always has the same sign as x.
// This is not the same as mathematical mod.
return sign ? -rem : rem;
}
// Commutative operators
BigInt opBinaryRight(string op, T)(T y)
if ((op=="+" || op=="*") && !is(T: BigInt))
{
return opBinary!(op)(y);
}
// BigInt = integer op BigInt
BigInt opBinaryRight(string op, T)(T y)
if (op == "-" && is(T: long))
{
ulong u = cast(ulong)(y < 0 ? -y : y);
BigInt r;
static if (op == "-")
{
r.sign = sign;
r.data = BigUint.addOrSubInt(data, u, sign == (y<0), r.sign);
r.negate();
}
return r;
}
// integer = integer op BigInt
T opBinaryRight(string op, T)(T y)
if ((op=="%" || op=="/") && is(T: long))
{
static if (op == "%")
{
checkDivByZero();
// y%x always has the same sign as y.
if (data.ulongLength() > 1)
return y;
return cast(T)(y % data.peekUlong(0));
}
else static if (op == "/")
{
checkDivByZero();
if (data.ulongLength() > 1)
return 0;
return cast(T)(y / data.peekUlong(0));
}
}
// const unary operations
BigInt opUnary(string op)() /*const*/ if (op=="+" || op=="-")
{
static if (op=="-")
{
BigInt r = this;
r.negate();
return r;
}
else static if (op=="+")
return this;
}
// non-const unary operations
BigInt opUnary(string op)() if (op=="++" || op=="--")
{
static if (op=="++")
{
data = BigUint.addOrSubInt(data, 1UL, false, sign);
return this;
}
else static if (op=="--")
{
data = BigUint.addOrSubInt(data, 1UL, true, sign);
return this;
}
}
///
bool opEquals(Tdummy=void)(ref const BigInt y) const
{
return sign == y.sign && y.data == data;
}
///
bool opEquals(T: int)(T y) const
{
if (sign != (y<0))
return 0;
return data.opEquals(cast(ulong)( y>=0 ? y : -y));
}
///
int opCmp(T:long)(T y)
{
if (sign != (y<0) )
return sign ? -1 : 1;
int cmp = data.opCmp(cast(ulong)(y >= 0 ? y : -y));
return sign? -cmp: cmp;
}
///
int opCmp(T:BigInt)(T y)
{
if (sign!=y.sign)
return sign ? -1 : 1;
int cmp = data.opCmp(y.data);
return sign? -cmp: cmp;
}
/// Returns the value of this BigInt as a long,
/// or +- long.max if outside the representable range.
long toLong() pure const
{
return (sign ? -1 : 1) *
(data.ulongLength() == 1 && (data.peekUlong(0) <= cast(ulong)(long.max))
? cast(long)(data.peekUlong(0))
: long.max);
}
/// Returns the value of this BigInt as an int,
/// or +- int.max if outside the representable range.
long toInt() pure const
{
return (sign ? -1 : 1) *
(data.uintLength() == 1 && (data.peekUint(0) <= cast(uint)(int.max))
? cast(int)(data.peekUint(0))
: int.max);
}
/// Number of significant uints which are used in storing this number.
/// The absolute value of this BigInt is always < 2^^(32*uintLength)
@property size_t uintLength() pure const
{
return data.uintLength();
}
/// Number of significant ulongs which are used in storing this number.
/// The absolute value of this BigInt is always < 2^^(64*ulongLength)
@property size_t ulongLength() pure const
{
return data.ulongLength();
}
/** Convert the BigInt to string, passing it to 'sink'.
*
* $(TABLE The output format is controlled via formatString:
* $(TR $(TD "d") $(TD Decimal))
* $(TR $(TD "x") $(TD Hexadecimal, lower case))
* $(TR $(TD "X") $(TD Hexadecimal, upper case))
* $(TR $(TD "s") $(TD Default formatting (same as "d") ))
* $(TR $(TD null) $(TD Default formatting (same as "d") ))
* )
*/
void toString(void delegate(const (char)[]) sink, string formatString) const
{
if (isNegative())
sink("-");
if (formatString.length>0 && formatString[$-1]=='x' || formatString[$-1]=='X')
{
char[] buff = data.toHexString(0, '_');
sink(buff);
}
else
{
char [] buff = data.toDecimalString(0);
sink(buff);
}
}
/+
private:
/// Convert to a hexadecimal string, with an underscore every
/// 8 characters.
string toHex()
{
string buff = data.toHexString(1, '_');
if (isNegative())
buff[0] = '-';
else
buff = buff[1..$];
return buff;
}
+/
private:
void negate()
{
if (!data.isZero())
sign = !sign;
}
bool isZero() pure const
{
return data.isZero();
}
bool isNegative() pure const
{
return sign;
}
// Generate a runtime error if division by zero occurs
void checkDivByZero() pure const
{
assert(!isZero(), "BigInt division by zero");
if (isZero())
auto x = 1/toInt(); // generate a div by zero error
}
}
string toDecimalString(BigInt x)
{
string outbuff="";
void sink(const(char)[] s) { outbuff ~= s; }
x.toString(&sink, "d");
return outbuff;
}
string toHex(BigInt x)
{
string outbuff="";
void sink(const(char)[] s) { outbuff ~= s; }
x.toString(&sink, "x");
return outbuff;
}
unittest {
// Radix conversion
assert( toDecimalString(BigInt("-1_234_567_890_123_456_789"))
== "-1234567890123456789");
assert( toHex(BigInt("0x1234567890123456789")) == "123_45678901_23456789");
assert( toHex(BigInt("0x00000000000000000000000000000000000A234567890123456789"))
== "A23_45678901_23456789");
assert( toHex(BigInt("0x000_00_000000_000_000_000000000000_000000_")) == "0");
assert(BigInt(-0x12345678).toInt() == -0x12345678);
assert(BigInt(-0x12345678).toLong() == -0x12345678);
assert(BigInt(0x1234_5678_9ABC_5A5AL).ulongLength == 1);
assert(BigInt(0x1234_5678_9ABC_5A5AL).toLong() == 0x1234_5678_9ABC_5A5AL);
assert(BigInt(-0x1234_5678_9ABC_5A5AL).toLong() == -0x1234_5678_9ABC_5A5AL);
assert(BigInt(0xF234_5678_9ABC_5A5AL).toLong() == long.max);
assert(BigInt(-0x123456789ABCL).toInt() == -int.max);
assert((BigInt(-2) + BigInt(1)) == BigInt(-1));
BigInt a = ulong.max - 5;
auto b = -long.max % a;
assert( b == -long.max % (ulong.max - 5));
b = long.max / a;
assert( b == long.max /(ulong.max - 5));
assert(BigInt(1) - 1 == 0);
}
unittest // Recursive division, bug 5568
{
enum Z = 4843;
BigInt m = (BigInt(1) << (Z*8) ) - 1;
m -= (BigInt(1) << (Z*6)) - 1;
BigInt oldm = m;
BigInt a = (BigInt(1) << (Z*4) )-1;
BigInt b = m % a;
m /= a;
m *= a;
assert( m + b == oldm);
m = (BigInt(1) << (4846 + 4843) ) - 1;
a = (BigInt(1) << 4846 ) - 1;
b = (BigInt(1) << (4846*2 + 4843)) - 1;
BigInt c = (BigInt(1) << (4846*2 + 4843*2)) - 1;
BigInt w = c - b + a;
assert(w % m == 0);
}