Skip to content

Commit

Permalink
Implement conversion of biguint to octal.
Browse files Browse the repository at this point in the history
Carried bits in last word must be output at the end.

Add unittest for 3rd/4th word no-carry boundary.

Implement 'o' format specifier in BigInt.toString().
  • Loading branch information
H. S. Teoh committed Feb 25, 2016
1 parent 3ad0489 commit 6282dda
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 1 deletion.
8 changes: 7 additions & 1 deletion std/bigint.d
Expand Up @@ -786,6 +786,7 @@ public:
*
* $(TABLE Available output formats:,
* $(TR $(TD "d") $(TD Decimal))
* $(TR $(TD "o") $(TD Octal))
* $(TR $(TD "x") $(TD Hexadecimal, lower case))
* $(TR $(TD "X") $(TD Hexadecimal, upper case))
* $(TR $(TD "s") $(TD Default formatting (same as "d") ))
Expand All @@ -803,7 +804,7 @@ public:
void toString(scope void delegate(const(char)[]) sink, ref FormatSpec!char f) const
{
auto hex = (f.spec == 'x' || f.spec == 'X');
if (!(f.spec == 's' || f.spec == 'd' || hex))
if (!(f.spec == 's' || f.spec == 'd' || f.spec =='o' || hex))
throw new FormatException("Format specifier not understood: %" ~ f.spec);

char[] buff;
Expand All @@ -815,6 +816,10 @@ public:
{
buff = data.toHexString(0, '_', 0, f.flZero ? '0' : ' ', LetterCase.lower);
}
else if (f.spec == 'o')
{
buff = data.toOctalString();
}
else
{
buff = data.toDecimalString(0);
Expand Down Expand Up @@ -873,6 +878,7 @@ public:
assert(format("%d", x) == "12345000000");
assert(format("%x", x) == "2_dfd1c040");
assert(format("%X", x) == "2_DFD1C040");
assert(format("%o", x) == "133764340100");
}

// Implement toHash so that BigInt works properly as an AA key.
Expand Down
113 changes: 113 additions & 0 deletions std/internal/math/biguintcore.d
Expand Up @@ -344,6 +344,17 @@ public:
return buff[z-frontExtraBytes..$];
}

/**
* Convert to an octal string.
*/
char[] toOctalString() const
{
auto predictLength = 1 + data.length*BigDigitBits / 3;
char[] buff = new char[predictLength];
size_t firstNonZero = biguintToOctal(buff, data);
return buff[firstNonZero .. $];
}

// return false if invalid character found
bool fromHexString(const(char)[] s) pure nothrow @safe
{
Expand Down Expand Up @@ -1549,6 +1560,70 @@ char [] biguintToHex(char [] buff, const BigDigit [] data, char separator=0,
return buff;
}

/**
* Convert a big uint into an octal string.
*
* Params:
* buff = The destination buffer for the octal string. Must be large enough to
* store the result, including leading zeroes, which is
* ceil(data.length * BigDigitBits / 3) characters.
* The buffer is filled from back to front, starting from `buff[$-1]`.
* data = The biguint to be converted.
*
* Returns: The index of the leading non-zero digit in `buff`. Will be
* `buff.length - 1` if the entire big uint is zero.
*/
size_t biguintToOctal(char[] buff, const(BigDigit)[] data)
pure nothrow @safe @nogc
{
ubyte carry = 0;
int shift = 0;
size_t penPos = buff.length - 1;
size_t lastNonZero = buff.length - 1;

pragma(inline) void output(int digit) @nogc nothrow
{
if (digit != 0)
lastNonZero = penPos;
buff[penPos--] = cast(char)('0' + digit);
}

foreach (bigdigit; data)
{
if (shift < 0)
{
// Some bits were carried over from previous word.
assert(shift > -3);
output(((bigdigit << -shift) | carry) & 0b111);
shift += 3;
assert(shift > 0);
}

while (shift <= BigDigitBits - 3)
{
output((bigdigit >>> shift) & 0b111);
shift += 3;
}

if (shift < BigDigitBits)
{
// Some bits need to be carried forward.
carry = (bigdigit >>> shift) & 0b11;
}
shift -= BigDigitBits;
assert(shift >= -2 && shift <= 0);
}

if (shift < 0)
{
// Last word had bits that haven't been output yet.
assert(shift > -3);
output(carry);
}

return lastNonZero;
}

/** Convert a big uint into a decimal string.
*
* Params:
Expand Down Expand Up @@ -2429,3 +2504,41 @@ unittest
assert(r[] == r1[]);
assert(q[] == q1[]);
}

// biguintToOctal
unittest
{
enum bufSize = 5 * BigDigitBits / 3 + 1;
auto buf = new char[bufSize];
size_t i;
BigDigit[] data = [ 342391 ];

// Basic functionality with single word
i = biguintToOctal(buf, data);
assert(i == bufSize - 7 && buf[i .. $] == "1234567");

// Test carrying bits between words
data = [ 0x77053977, 0x39770539, 0x00000005 ];
i = biguintToOctal(buf, data);
assert(i == bufSize - 23 && buf[i .. $] == "12345670123456701234567");

// Test carried bits in the last word
data = [ 0x80000000 ];
i = biguintToOctal(buf, data);
assert(buf[i .. $] == "20000000000");

// Test boundary between 3rd and 4th word where the number of bits is
// divisible by 3 and no bits should be carried.
//
// The 0xC0000000's are for "poisoning" the carry to be non-zero when the
// rollover happens, so that if any bugs happen in wrongly adding the carry
// to the next word, non-zero bits will show up in the output.
data = [ 0xC0000000, 0xC0000000, 0xC0000000, 0x00000010 ];
i = biguintToOctal(buf, data);
assert(buf[i .. $] == "2060000000001400000000030000000000");

// Boundary case: 0
data = [ 0 ];
i = biguintToOctal(buf, data);
assert(buf[i .. $] == "0");
}

0 comments on commit 6282dda

Please sign in to comment.