Skip to content

Commit

Permalink
Fix Issue 15229: Refactored BigInt code to work with character ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
JackStouffer committed Apr 28, 2016
1 parent f3e6c43 commit dd0d139
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 66 deletions.
95 changes: 72 additions & 23 deletions std/bigint.d
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import std.conv : ConvException;
private import std.internal.math.biguintcore;
private import std.format : FormatSpec, FormatException;
private import std.traits;
private import std.range.primitives;

/** A struct representing an arbitrary precision integer.
*
Expand All @@ -49,44 +50,89 @@ private:
bool sign = false;
public:
/**
* Construct a BigInt from a decimal or hexadecimal string.
* Construct a BigInt from a decimal or hexadecimal string. The number must
* be in the form of a decimal or hex literal. It may have a leading `+`
* or `-` sign, followed by `0x` or `0X` if hexadecimal. Underscores are
* permitted in any location after the `0x` and/or the sign of the number.
*
* 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. An empty string is treated as "0".
* Params:
* s = a finite bidirectional range of any character type
*
* Throws: ConvException if invalid character found, or string is empty.
* Throws:
* $(D ConvException) if the string doesn't represent a valid number
*/
this(T : const(char)[])(T s, string file = __FILE__, size_t line = __LINE__)
pure
this(Range)(Range s) if (
isBidirectionalRange!Range &&
isSomeChar!(ElementType!Range) &&
!isInfinite!Range)
{
if (s.length == 0)
throw new ConvException("Can't initialize BigInt with "~
"empty string", file, line);
import std.algorithm.iteration : filterBidirectional;
import std.algorithm.searching : startsWith;
import std.exception : enforce;
import std.conv : ConvException;

enforce!ConvException(!s.empty, "Can't initialize BigInt with an empty range");

bool neg = false;
if (s[0] == '-') {
bool ok;

data = 0UL;

// check for signs and if the string is a hex value
if (s.front == '+')
{
s.popFront(); // skip '+'
}
else if (s.front == '-')
{
neg = true;
s = s[1..$];
} else if (s[0] == '+') {
s = s[1..$];
s.popFront();
}
data = 0UL;
bool ok;
assert(isZero());
if (s.length > 2 && (s[0..2] == "0x" || s[0..2] == "0X"))

if (s.save.startsWith("0x") || s.save.startsWith("0X"))
{
s.popFront;
s.popFront;

if (!s.empty)
ok = data.fromHexString(s.filterBidirectional!(a => a != '_'));
else
ok = false;
}
else
{
ok = data.fromHexString(s[2..$]);
} else {
ok = data.fromDecimalString(s);
ok = data.fromDecimalString(s.filterBidirectional!(a => a != '_'));
}
if (!ok)
throw new ConvException("Invalid digit string", file, line);

enforce!ConvException(ok, "Not a valid numerical string");

if (isZero())
neg = false;

sign = neg;
}

unittest
{
import std.internal.test.dummyrange;
import std.exception : assertThrown;

auto r1 = new ReferenceBidirectionalRange!dchar("101");
auto big1 = BigInt(r1);
assert(big1 == BigInt(101));

auto r2 = new ReferenceBidirectionalRange!dchar("1_000");
auto big2 = BigInt(r2);
assert(big2 == BigInt(1000));

auto r3 = new ReferenceBidirectionalRange!dchar("0x0");
auto big3 = BigInt(r3);
assert(big3 == BigInt(0));

auto r4 = new ReferenceBidirectionalRange!dchar("0x");
assertThrown!ConvException(BigInt(r4));
}

/// Construct a BigInt from a built-in integral type.
this(T)(T x) pure nothrow if (isIntegral!T)
{
Expand Down Expand Up @@ -1106,6 +1152,9 @@ unittest {
assert(BigInt(-4) % BigInt(5) == -4);
assert(BigInt(2)/BigInt(-3) == BigInt(0)); // bug 8022
assert(BigInt("-1") > long.min); // bug 9548

assert(toDecimalString(BigInt("0000000000000000000000000000000000000000001234567"))
== "1234567");
}

unittest // Minimum signed value bug tests.
Expand Down
111 changes: 68 additions & 43 deletions std/internal/math/biguintcore.d
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ alias multibyteSub = multibyteAddSub!('-');


private import core.cpuid;
private import std.traits : Unqual;
private import std.traits;
private import std.range.primitives;
public import std.ascii : LetterCase;

shared static this()
Expand Down Expand Up @@ -345,35 +346,53 @@ public:
}

// return false if invalid character found
bool fromHexString(const(char)[] s) pure nothrow @safe
bool fromHexString(Range)(Range s) if (
isBidirectionalRange!Range && isSomeChar!(ElementType!Range))
{
import std.range : walkLength;

//Strip leading zeros
int firstNonZero = 0;
while ((firstNonZero < s.length - 1) &&
(s[firstNonZero]=='0' || s[firstNonZero]=='_'))
{
++firstNonZero;
}
auto len = (s.length - firstNonZero + 15)/4;
auto tmp = new BigDigit[len+1];
uint part = 0;
uint sofar = 0;
uint partcount = 0;
assert(s.length>0);
for (ptrdiff_t i = s.length - 1; i>=firstNonZero; --i)
{
assert(i>=0);
char c = s[i];
if (s[i]=='_') continue;
uint x = (c>='0' && c<='9') ? c - '0'
: (c>='A' && c<='F') ? c - 'A' + 10
: (c>='a' && c<='f') ? c - 'a' + 10
: 100;
if (x==100) return false;
while (!s.empty && s.front == '0')
s.popFront;

if (s.empty)
{
data = ZERO;
return true;
}

auto len = (s.save.walkLength + 15) / 4;
auto tmp = new BigDigit[len + 1];
uint part, sofar, partcount;

foreach_reverse (character; s)
{
if (character == '_')
continue;

uint x;
if (character >= '0' && character <= '9')
{
x = character - '0';
}
else if (character >= 'A' && character <= 'F')
{
x = character - 'A' + 10;
}
else if (character >= 'a' && character <= 'f')
{
x = character - 'a' + 10;
}
else
{
return false;
}

part >>= 4;
part |= (x<<(32-4));
part |= (x << (32 - 4));
++partcount;
if (partcount==8)

if (partcount == 8)
{
tmp[sofar] = part;
++sofar;
Expand All @@ -396,25 +415,26 @@ public:
}

// return true if OK; false if erroneous characters found
// FIXME: actually throws `ConvException` on error.
bool fromDecimalString(const(char)[] s) pure @trusted
bool fromDecimalString(Range)(Range s) if (
isForwardRange!Range && isSomeChar!(ElementType!Range))
{
//Strip leading zeros
int firstNonZero = 0;
while ((firstNonZero < s.length) &&
(s[firstNonZero]=='0' || s[firstNonZero]=='_'))
import std.range : walkLength;

while (!s.empty && s.front == '0')
{
++firstNonZero;
s.popFront;
}
if (firstNonZero == s.length && s.length >= 1)

if (s.empty)
{
data = ZERO;
return true;
}
auto predictlength = (18*2 + 2*(s.length-firstNonZero)) / 19;
auto tmp = new BigDigit[predictlength];

uint hi = biguintFromDecimal(tmp, s[firstNonZero..$]);
auto predict_length = (18 * 2 + 2 * s.save.walkLength) / 19;
auto tmp = new BigDigit[predict_length];

uint hi = biguintFromDecimal(tmp, s);
tmp.length = hi;

data = trustedAssumeUnique(tmp);
Expand Down Expand Up @@ -1598,10 +1618,14 @@ size_t biguintToDecimal(char [] buff, BigDigit [] data) pure nothrow
* Returns:
* the highest index of data which was used.
*/
int biguintFromDecimal(BigDigit [] data, const(char)[] s) pure
int biguintFromDecimal(Range)(BigDigit[] data, Range s) if (
isInputRange!Range &&
isSomeChar!(ElementType!Range) &&
!isInfinite!Range)
in
{
assert((data.length >= 2) || (data.length == 1 && s.length == 1));
static if (hasLength!Range)
assert((data.length >= 2) || (data.length == 1 && s.length == 1));
}
body
{
Expand All @@ -1624,14 +1648,15 @@ body
if (data.length > 1)
data[1] = 0;

for (int i= (s[0]=='-' || s[0]=='+')? 1 : 0; i<s.length; ++i)
foreach (character; s)
{
if (s[i] == '_')
if (character == '_')
continue;
if (s[i] < '0' || s[i] > '9')

if (character < '0' || character > '9')
throw new ConvException("invalid digit");
x *= 10;
x += s[i] - '0';
x += character - '0';
++lo;
if (lo == 9)
{
Expand Down

0 comments on commit dd0d139

Please sign in to comment.