Implement unary operation for multiple precision integers

This commit is contained in:
Eugen Wissner 2016-12-05 22:06:06 +01:00
parent 86c08e7af6
commit b3fdd6fd4a
2 changed files with 244 additions and 68 deletions

View File

@ -10,10 +10,13 @@
*/ */
module tanya.math.mp; module tanya.math.mp;
import std.algorithm.comparison; import std.algorithm.iteration;
import std.algorithm.searching; import std.algorithm.searching;
import std.algorithm.mutation; import std.algorithm.mutation;
import std.experimental.allocator; import std.experimental.allocator;
import std.math;
import std.range;
import std.traits;
import tanya.memory.allocator; import tanya.memory.allocator;
import tanya.memory.types; import tanya.memory.types;
@ -22,6 +25,11 @@ struct Integer
private RefCounted!(ubyte[]) rep; private RefCounted!(ubyte[]) rep;
private bool sign; private bool sign;
invariant
{
assert(rep.length || !sign, "0 should be positive.");
}
/** /**
* Creates a multiple precision integer. * Creates a multiple precision integer.
* *
@ -29,7 +37,8 @@ struct Integer
* value = Initial value. * value = Initial value.
* allocator = Allocator. * allocator = Allocator.
*/ */
this(in uint value, IAllocator allocator = theAllocator) this(T)(in T value, IAllocator allocator = theAllocator)
if (isIntegral!T)
in in
{ {
assert(allocator !is null); assert(allocator !is null);
@ -39,8 +48,8 @@ struct Integer
this(allocator); this(allocator);
immutable size = calculateSizeFromInt(value); immutable size = calculateSizeFromInt(value);
rep = allocator.makeArray!ubyte(size); allocator.resizeArray(rep, size);
assignInt(size, value); assignInt(value);
} }
/// ///
@ -61,8 +70,9 @@ struct Integer
{ {
this(allocator); this(allocator);
rep = allocator.makeArray!ubyte(value.length); allocator.resizeArray(rep, value.length);
value.rep.get.copy(rep.get); value.rep.get.copy(rep.get);
sign = value.sign;
} }
/// Ditto. /// Ditto.
@ -74,14 +84,14 @@ struct Integer
/* /*
* Figure out the minimum amount of space this value will take * Figure out the minimum amount of space this value will take
* up in bytes (leave at least one byte, though, if the value is 0). * up in bytes.
*/ */
pragma(inline, true) pragma(inline, true)
private ushort calculateSizeFromInt(in ref uint value) private ubyte calculateSizeFromInt(in ulong value)
const pure nothrow @safe @nogc const pure nothrow @safe @nogc
{ {
ushort size = 4; ubyte size = ulong.sizeof;
for (uint mask = 0xff000000; mask > 0x000000ff; mask >>= 8) for (ulong mask = 0xff00000000000000; mask >= 0xff; mask >>= 8)
{ {
if (value & mask) if (value & mask)
{ {
@ -98,26 +108,35 @@ struct Integer
* representation in big-endian format. * representation in big-endian format.
*/ */
pragma(inline, true) pragma(inline, true)
private void assignInt(in ref ushort size, in ref uint value) private void assignInt(T)(ref in T value)
pure nothrow @safe @nogc pure nothrow @safe @nogc
in
{ {
uint mask = 0x00000000ff, shift; static assert(isIntegral!T);
for (ushort i = size; i; --i) }
body
{
uint mask = 0xff, shift;
immutable absolute = abs(value);
sign = value < 0 ? true : false;
for (auto i = length; i; --i)
{ {
rep[i - 1] = cast(ubyte) ((value & mask) >> shift); rep[i - 1] = cast(ubyte) ((absolute & mask) >> shift);
mask <<= 8; mask <<= 8;
shift += 8; shift += 8;
} }
} }
ref Integer opAssign(in uint value) ref Integer opAssign(T)(in T value)
if (isIntegral!T)
{ {
ushort size = calculateSizeFromInt(value); immutable size = calculateSizeFromInt(value);
checkAllocator(); checkAllocator();
allocator.resizeArray(rep.get, size); allocator.resizeArray(rep.get, size);
assignInt(size, value); assignInt(value);
return this; return this;
} }
@ -125,9 +144,12 @@ struct Integer
ref Integer opAssign(in Integer value) ref Integer opAssign(in Integer value)
{ {
checkAllocator(); checkAllocator();
allocator.resizeArray(rep, value.length); allocator.resizeArray(rep, value.length);
value.rep.get.copy(rep.get); value.rep.get.copy(rep.get);
sign = value.sign;
return this; return this;
} }
@ -147,8 +169,7 @@ struct Integer
assert(h.rep[0] == 0b00000010 && h.rep[1] == 0b10110000); assert(h.rep[0] == 0b00000010 && h.rep[1] == 0b10110000);
h = 0; h = 0;
assert(h.length == 1); assert(h.length == 0);
assert(h.rep[0] == 0);
} }
/** /**
@ -196,13 +217,11 @@ struct Integer
{ {
return -1; return -1;
} }
// Otherwise, keep searching through the representational integers // Otherwise, keep searching through the representational integers
// until one is bigger than another - once we've found one, it's // until one is bigger than another - once we've found one, it's
// safe to stop, since the lower order bytes can't affect the // safe to stop, since the lower order bytes can't affect the
// comparison // comparison
int i = 0, j = 0; for (size_t i, j; i < length && j < h.length; ++i, ++j)
while (i < length && j < h.length)
{ {
if (rep[i] < h.rep[j]) if (rep[i] < h.rep[j])
{ {
@ -212,8 +231,6 @@ struct Integer
{ {
return 1; return 1;
} }
++i;
++j;
} }
// if we got all the way to the end without a comparison, the // if we got all the way to the end without a comparison, the
// two are equal // two are equal
@ -238,7 +255,8 @@ struct Integer
* Assignment operators with another $(D_PSYMBOL Integer). * Assignment operators with another $(D_PSYMBOL Integer).
* *
* Params: * Params:
* h = The second integer. * op = Operation.
* h = The second integer.
* *
* Returns: $(D_KEYWORD this). * Returns: $(D_KEYWORD this).
*/ */
@ -336,24 +354,23 @@ struct Integer
} }
while (i); while (i);
if (borrow && i) if (borrow && i && rep[i - 1])
{ {
if (!(rep[i - 1])) // Don't borrow i
{
throw new Exception("Error, subtraction result is negative\n");
}
--rep[i - 1]; --rep[i - 1];
} }
// Go through the representation array and see how many of the // Go through the representation array and see how many of the
// left-most bytes are unused. Remove them and resize the array. // left-most bytes are unused. Remove them and resize the array.
immutable offset = rep.countUntil!(a => a != 0); immutable offset = rep.get.countUntil!((const ref a) => a != 0);
if (offset > 0) if (offset > 0)
{ {
ubyte[] tmp; ubyte[] tmp = allocator.makeArray!ubyte(rep.length - offset);
allocator.resizeArray(tmp, rep.length - offset);
rep[offset .. $].copy(tmp); rep[offset .. $].copy(tmp);
rep = tmp; rep = tmp;
} }
else if (offset == -1)
{
allocator.resizeArray(rep, 0);
}
return this; return this;
} }
@ -371,6 +388,10 @@ struct Integer
h2 = 4294967294; h2 = 4294967294;
h1 -= h2; h1 -= h2;
assert(h1.rep == [0x80, 0x00, 0x00, 0x01]); assert(h1.rep == [0x80, 0x00, 0x00, 0x01]);
h2 = h1;
h1 -= h2;
assert(h1.length == 0);
} }
/// Ditto. /// Ditto.
@ -427,8 +448,7 @@ struct Integer
checkAllocator(); checkAllocator();
if (step >= rep.length) if (step >= rep.length)
{ {
allocator.resizeArray(rep, 1); allocator.resizeArray(rep, 0);
rep[0] = 0;
return this; return this;
} }
@ -450,7 +470,7 @@ struct Integer
rep[j] = (rep[i] >> bit | oldCarry); rep[j] = (rep[i] >> bit | oldCarry);
++j; ++j;
} }
rep.length = max(1, rep.length - n / 8 - (i == j ? 0 : 1)); allocator.resizeArray(rep, rep.length - n / 8 - (i == j ? 0 : 1));
return this; return this;
} }
@ -474,32 +494,32 @@ struct Integer
assert(h1.rep == [0x0f, 0xff]); assert(h1.rep == [0x0f, 0xff]);
h1 >>= 20; h1 >>= 20;
assert(h1.rep == [0x00]); assert(h1.length == 0);
h1 >>= 2; h1 >>= 2;
assert(h1.rep == [0x00]); assert(h1.length == 0);
h1 = 1431655765; h1 = 1431655765;
h1 >>= 16; h1 >>= 16;
assert(h1.rep == [0x55, 0x55]); assert(h1.rep == [0x55, 0x55]);
h1 >>= 16; h1 >>= 16;
assert(h1.rep == [0x00]); assert(h1.length == 0);
} }
/// Ditto. /// Ditto.
ref Integer opOpAssign(string op)(in Integer h) ref Integer opOpAssign(string op)(in Integer h)
if (op == "*") if (op == "*")
{ {
ubyte mask;
auto i = h.rep.length; auto i = h.rep.length;
auto temp = Integer(this); auto temp = Integer(this, allocator);
immutable sign = sign == h.sign ? false : true;
opAssign(0); opAssign(0);
do do
{ {
--i; --i;
for (mask = 0x01; mask; mask <<= 1) for (ubyte mask = 0x01; mask; mask <<= 1)
{ {
if (mask & h.rep[i]) if (mask & h.rep[i])
{ {
@ -509,6 +529,7 @@ struct Integer
} }
} }
while (i); while (i);
this.sign = sign;
return this; return this;
} }
@ -526,26 +547,24 @@ struct Integer
ref Integer opOpAssign(string op)(in Integer h) ref Integer opOpAssign(string op)(in Integer h)
if ((op == "/") || (op == "%")) if ((op == "/") || (op == "%"))
{ {
auto divisor = Integer(h);
// "bit_position" keeps track of which bit, of the quotient,
// is being set or cleared on the current operation.
size_t bit_size;
checkAllocator(); checkAllocator();
// First, left-shift divisor until it's >= than the divident auto divisor = Integer(h, allocator);
while (opCmp(divisor) > 0) size_t bitSize;
// First, left-shift divisor until it's >= than the dividend
for (; opCmp(divisor) > 0; ++bitSize)
{ {
divisor <<= 1; divisor <<= 1;
++bit_size;
} }
static if (op == "/") static if (op == "/")
{ {
auto quotient = allocator.makeArray!ubyte(bit_size / 8 + 1); auto quotient = allocator.makeArray!ubyte(bitSize / 8 + 1);
} }
auto bit_position = 8 - (bit_size % 8) - 1; // "bitPosition" keeps track of which bit, of the quotient,
// is being set or cleared on the current operation.
auto bitPosition = 8 - (bitSize % 8) - 1;
do do
{ {
if (opCmp(divisor) >= 0) if (opCmp(divisor) >= 0)
@ -553,21 +572,25 @@ struct Integer
opOpAssign!"-"(divisor); opOpAssign!"-"(divisor);
static if (op == "/") static if (op == "/")
{ {
quotient[bit_position / 8] |= (0x80 >> (bit_position % 8)); quotient[bitPosition / 8] |= (0x80 >> (bitPosition % 8));
} }
} }
if (bitSize)
if (bit_size)
{ {
divisor >>= 1; divisor >>= 1;
} }
++bit_position; else
{
break;
}
++bitPosition, --bitSize;
} }
while (bit_size--); while (true);
static if (op == "/") static if (op == "/")
{ {
rep = quotient; rep = quotient;
sign = sign == h.sign ? false : true;
} }
return this; return this;
} }
@ -582,7 +605,7 @@ struct Integer
h1 = 8; h1 = 8;
h1 %= h2; h1 %= h2;
assert(h1.rep == [0x00]); assert(h1.length == 0);
h1 = 7; h1 = 7;
h1 %= h2; h1 %= h2;
@ -591,7 +614,7 @@ struct Integer
h1 = 56088; h1 = 56088;
h2 = 456; h2 = 456;
h1 /= h2; h1 /= h2;
assert(h1.rep == [0x7b]); // 123 assert(h1.rep == [0x7b]);
} }
/// Ditto. /// Ditto.
@ -599,8 +622,8 @@ struct Integer
if (op == "^^") if (op == "^^")
{ {
auto i = exp.rep.length; auto i = exp.rep.length;
auto tmp1 = Integer(this); auto tmp1 = Integer(this, allocator);
Integer tmp2; auto tmp2 = Integer(allocator);
opAssign(1); opAssign(1);
@ -637,5 +660,150 @@ struct Integer
assert(h1.rep == [0x1b, 0x5c, 0xab, 0x9c, 0x31, 0x10]); assert(h1.rep == [0x1b, 0x5c, 0xab, 0x9c, 0x31, 0x10]);
} }
/**
* Unary operators.
*
* Params:
* op = Operation.
*
* Returns: New $(D_PSYMBOL Integer).
*/
Integer opUnary(string op)()
if ((op == "+") || (op == "-") || (op == "~"))
{
auto h = Integer(this, allocator);
static if (op == "-")
{
h.sign = !h.sign;
}
else static if (op == "~")
{
h.rep.get.each!((ref a) => a = ~a);
}
return h;
}
///
unittest
{
auto h1 = Integer(79);
Integer h2;
h2 = +h1;
assert(h2.length == 1);
assert(h2.rep[0] == 79);
assert(!h2.sign);
assert(h2 !is h1);
h2 = -h1;
assert(h2.length == 1);
assert(h2.rep[0] == 79);
assert(h2.sign);
h1 = -h2;
assert(h2.length == 1);
assert(h2.rep[0] == 79);
assert(h2.sign);
assert(h2 !is h1);
h1 = -h2;
assert(h1.length == 1);
assert(h1.rep[0] == 79);
assert(!h1.sign);
h2 = ~h1;
assert(h2.rep[0] == ~cast(ubyte) 79);
}
/**
* Increment/decrement.
*
* Params:
* op = Operation.
*
* Returns: $(D_KEYWORD this).
*/
ref Integer opUnary(string op)()
if ((op == "++") || (op == "--"))
{
checkAllocator();
if (op == "++" || sign || length == 0)
{
static if (op == "--")
{
sign = true;
}
auto size = rep
.get
.retro
.countUntil!((const ref a) => a != typeof(rep[0]).max);
if (size == -1)
{
size = length;
allocator.resizeArray(rep.get, rep.length + 1);
rep[0] = 1;
}
else
{
++rep[$ - size - 1];
}
rep[$ - size .. $] = 0;
}
else
{
immutable size = rep.get.retro.countUntil!((const ref a) => a != 0);
if (rep[0] == 1)
{
allocator.resizeArray(rep, rep.length - 1);
rep[0 .. $] = typeof(rep[0]).max;
}
else
{
--rep[$ - size - 1];
rep[$ - size .. $] = typeof(rep[0]).max;
}
if (rep.length == 0)
{
sign = false;
}
}
return this;
}
///
unittest
{
Integer h;
++h;
assert(h.rep == [0x01]);
assert(h.length == 1);
--h;
assert(h.length == 0);
h = 511;
++h;
assert(h.rep == [0x02, 0x00]);
--h;
assert(h.rep == [0x01, 0xff]);
h = 79;
++h;
assert(h.rep == [0x50]);
--h;
assert(h.rep == [0x4f]);
h = 65535;
++h;
assert(h.rep == [0x01, 0x00, 0x00]);
--h;
assert(h.rep == [0xff, 0xff]);
}
mixin StructAllocator; mixin StructAllocator;
} }

View File

@ -10,6 +10,7 @@
*/ */
module tanya.memory.allocator; module tanya.memory.allocator;
import std.algorithm.mutation;
import std.experimental.allocator; import std.experimental.allocator;
import std.typecons; import std.typecons;
@ -136,26 +137,33 @@ abstract class Allocator : IAllocator
/** /**
* Params: * Params:
* T = Element type of the array being created. * T = Element type of the array being created.
* allocator = The allocator used for getting memory. * allocator = The allocator used for getting memory.
* array = A reference to the array being changed. * array = A reference to the array being changed.
* length = New array length. * length = New array length.
* init = The value to fill the new part of the array with if it becomes
* larger.
* *
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could * Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
* not be reallocated. In the latter * not be reallocated. In the latter
*/ */
bool resizeArray(T)(IAllocator allocator, bool resizeArray(T)(IAllocator allocator,
ref T[] array, ref T[] array,
in size_t length) in size_t length,
T init = T.init)
{ {
void[] buf = array; void[] buf = array;
immutable oldLength = array.length;
if (!allocator.reallocate(buf, length * T.sizeof)) if (!allocator.reallocate(buf, length * T.sizeof))
{ {
return false; return false;
} }
array = cast(T[]) buf; array = cast(T[]) buf;
if (oldLength < length)
{
array[oldLength .. $].uninitializedFill(init);
}
return true; return true;
} }
@ -194,7 +202,7 @@ mixin template StructAllocator()
{ {
private IAllocator allocator; private IAllocator allocator;
this(IAllocator allocator) this(IAllocator allocator) pure nothrow @safe @nogc
in in
{ {
assert(allocator !is null); assert(allocator !is null);