Anton Tarasenko
2 years ago
3 changed files with 392 additions and 5 deletions
@ -0,0 +1,280 @@
|
||||
/** |
||||
* A simply big integer implementation, mostly to allow Acedia's databases to |
||||
* store integers of arbitrary size. Should not be used in regular |
||||
* computations, designed to store player statistic values that are incremented |
||||
* from time to time. |
||||
* Copyright 2022 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class BigInt extends AcediaObject |
||||
dependson(MathAPI); |
||||
|
||||
var private bool negative; |
||||
// Digits array, from least to most significant: |
||||
// For example, for 13524: |
||||
// `digits[0] = 4` |
||||
// `digits[1] = 2` |
||||
// `digits[2] = 5` |
||||
// `digits[3] = 3` |
||||
// `digits[4] = 1` |
||||
// Valid `BigInt` should not have this array empty. |
||||
var private array<byte> digits; |
||||
|
||||
protected function Constructor() |
||||
{ |
||||
// Init with zero |
||||
digits[0] = 0; |
||||
} |
||||
|
||||
protected function Finalizer() |
||||
{ |
||||
negative = false; |
||||
digits.length = 0; |
||||
} |
||||
|
||||
// Minimal `int` value `-2,147,483,648` is slightly a pain to handle, so just |
||||
// use this pre-made constructor for it |
||||
private static function BigInt CreateMinimalNegative() |
||||
{ |
||||
local array<byte> newDigits; |
||||
local BigInt result; |
||||
newDigits[0] = 8; |
||||
newDigits[1] = 4; |
||||
newDigits[2] = 6; |
||||
newDigits[3] = 3; |
||||
newDigits[4] = 8; |
||||
newDigits[5] = 4; |
||||
newDigits[6] = 7; |
||||
newDigits[7] = 4; |
||||
newDigits[8] = 1; |
||||
newDigits[9] = 2; |
||||
result = BigInt(__().memory.Allocate(class'BigInt')); |
||||
result.digits = newDigits; |
||||
result.negative = true; |
||||
return result; |
||||
} |
||||
|
||||
// Removes unnecessary zeroes from leading digit positions `digits`. |
||||
// Does not change contained value. |
||||
private final static function TrimLeadingZeroes() |
||||
{ |
||||
local int i, zeroesToRemove; |
||||
|
||||
// Find how many leading zeroes there is. |
||||
// Since `digits` stores digits from least to most significant, we need |
||||
// to check from the end of `digits` array. |
||||
for (i = digits.length - 1; i >= 0; i -= 1) |
||||
{ |
||||
if (digits[i] != 0) { |
||||
break; |
||||
} |
||||
zeroesToRemove += 1; |
||||
} |
||||
// `digits` must not be empty, enforce `0` value in that case |
||||
if (zeroesToRemove >= digits.length) |
||||
{ |
||||
digits.length = 1; |
||||
digits[0] = 0; |
||||
negative = false; |
||||
} |
||||
else { |
||||
digits.length = digits.length - zeroesToRemove; |
||||
} |
||||
} |
||||
|
||||
public final static function BigInt FromInt(int value) |
||||
{ |
||||
local bool valueIsNegative; |
||||
local array<byte> newDigits; |
||||
local BigInt result; |
||||
local MathAPI.IntegerDivisionResult divisionResult; |
||||
|
||||
if (value < 0) |
||||
{ |
||||
// Treat special case of minimal `int` value `-2,147,483,648` that |
||||
// won't fit into positive `int` as special and use pre-made |
||||
// specialized constructor `CreateMinimalNegative()` |
||||
if (value < -MaxInt) { |
||||
return CreateMinimalNegative(); |
||||
} |
||||
else |
||||
{ |
||||
valueIsNegative = true; |
||||
value *= -1; |
||||
} |
||||
} |
||||
if (value == 0) { |
||||
newDigits[0] = 0; |
||||
} |
||||
else |
||||
{ |
||||
while (value > 0) |
||||
{ |
||||
divisionResult = __().math.IntegerDivision(value, 10); |
||||
value = divisionResult.quotient; |
||||
newDigits[newDigits.length] = divisionResult.remainder; |
||||
} |
||||
} |
||||
result = BigInt(__().memory.Allocate(class'BigInt')); |
||||
result.digits = newDigits; |
||||
result.negative = valueIsNegative; |
||||
result.TrimLeadingZeroes(); |
||||
return result; |
||||
} |
||||
|
||||
public final static function BigInt FromDecimal(BaseText value) |
||||
{ |
||||
local int i; |
||||
local bool valueIsNegative; |
||||
local byte nextDigit; |
||||
local array<byte> newDigits; |
||||
local Parser parser; |
||||
local BigInt result; |
||||
local Basetext.Character nextCharacter; |
||||
|
||||
if (value == none) { |
||||
return none; |
||||
} |
||||
parser = value.Parse(); |
||||
if (parser.Match(P("-")).Ok()) |
||||
{ |
||||
valueIsNegative = true; |
||||
parser.Confirm(); |
||||
} |
||||
parser.R(); |
||||
newDigits.length = parser.GetRemainingLength(); |
||||
i = newDigits.length - 1; |
||||
while (!parser.HasFinished()) |
||||
{ |
||||
// This should not happen, but just in case |
||||
if (i < 0) { |
||||
break; |
||||
} |
||||
parser.MCharacter(nextCharacter); |
||||
nextDigit = Clamp(__().text.CharacterToInt(nextCharacter), 0, 9); |
||||
newDigits[i] = nextDigit; |
||||
i -= 1; |
||||
} |
||||
result = BigInt(__().memory.Allocate(class'BigInt')); |
||||
result.digits = newDigits; |
||||
result.negative = valueIsNegative; |
||||
parser.FreeSelf(); |
||||
result.TrimLeadingZeroes(); |
||||
return result; |
||||
} |
||||
|
||||
public final static function BigInt FromDecimal_S(string value) |
||||
{ |
||||
local MutableText wrapper; |
||||
local BigInt result; |
||||
|
||||
wrapper = __().text.FromStringM(value); |
||||
result = FromDecimal(wrapper); |
||||
wrapper.FreeSelf(); |
||||
return result; |
||||
} |
||||
|
||||
private function _add(BigInt other) |
||||
{ |
||||
local int i; |
||||
local byte carry, digitSum; |
||||
local array<byte> otherDigits; |
||||
|
||||
if (other == none) { |
||||
return; |
||||
} |
||||
otherDigits = other.digits; |
||||
if (digits.length < otherDigits.length) { |
||||
digits.length = otherDigits.length; |
||||
} |
||||
carry = 0; |
||||
for (i = 0; i < digits.length; i += 1) |
||||
{ |
||||
digitSum = digits[i] + otherDigits[i] + carry; |
||||
digits[i] = _.math.Remainder(digitSum, 10); |
||||
carry = (digitSum - digits[i]) / 10; |
||||
} |
||||
if (carry > 0) { |
||||
digits[digits.length] = carry; |
||||
} |
||||
} |
||||
|
||||
public function BigInt Add(BigInt other) |
||||
{ |
||||
_add(other); |
||||
return self; |
||||
} |
||||
|
||||
public function BigInt AddInt(int other) |
||||
{ |
||||
local BigInt otherObject; |
||||
|
||||
otherObject = FromInt(other); |
||||
Add(otherObject); |
||||
_.memory.Free(otherObject); |
||||
return self; |
||||
} |
||||
|
||||
/*public function BigInt Multiply(BigInt other); |
||||
public function BigInt MultiplyInt(int other); |
||||
|
||||
public function bool IsNegative(); |
||||
public function (int other); |
||||
|
||||
public function int ToInt(); |
||||
|
||||
public function Text ToText(); |
||||
|
||||
public function Text ToText_M();*/ |
||||
|
||||
public function Text ToText() |
||||
{ |
||||
return ToText_M().IntoText(); |
||||
} |
||||
|
||||
public function MutableText ToText_M() |
||||
{ |
||||
local int i; |
||||
local MutableText result; |
||||
|
||||
result = _.text.Empty(); |
||||
if (negative) { |
||||
result.AppendCharacter(_.text.GetCharacter("-")); |
||||
} |
||||
for (i = digits.length - 1; i >= 0; i -= 1) { |
||||
result.AppendCharacter(_.text.CharacterFromCodePoint(digits[i] + 48)); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
public function string ToString() |
||||
{ |
||||
local int i; |
||||
local string result; |
||||
|
||||
if (negative) { |
||||
result = "-"; |
||||
} |
||||
for (i = digits.length - 1; i >= 0; i -= 1) { |
||||
result = result $ digits[i]; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
defaultproperties |
||||
{ |
||||
} |
@ -0,0 +1,106 @@
|
||||
/** |
||||
* Set of tests for `BigInt` class. |
||||
* Copyright 2022 Anton Tarasenko |
||||
*------------------------------------------------------------------------------ |
||||
* This file is part of Acedia. |
||||
* |
||||
* Acedia is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* Acedia is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||
*/ |
||||
class TEST_BigInt extends TestCase |
||||
abstract; |
||||
|
||||
protected static function TESTS() |
||||
{ |
||||
// Here we use `ToString()` method to check `BigInt` creation, |
||||
// therefore also testing it |
||||
Test_Creating(); |
||||
// So here we nee to test `ToText()` methods separately |
||||
Test_ToText(); |
||||
Test_LeadingZeroes(); |
||||
//Test_AddingValues(); |
||||
} |
||||
// TODO: leading zeroes |
||||
protected static function Test_Creating() |
||||
{ |
||||
Context("Testing creation of `BigInt`s."); |
||||
Issue("`ToString()` doesn't return value `BigInt` was initialized with" @ |
||||
"a positive `int`."); |
||||
TEST_ExpectTrue(class'BigInt'.static.FromInt(13524).ToString() == "13524"); |
||||
TEST_ExpectTrue( |
||||
class'BigInt'.static.FromInt(MaxInt).ToString() == "2147483647"); |
||||
|
||||
Issue("`ToString()` doesn't return value `BigInt` was initialized with" @ |
||||
"a positive integer inside `string`."); |
||||
TEST_ExpectTrue( |
||||
class'BigInt'.static.FromDecimal_S("2147483647").ToString() |
||||
== "2147483647"); |
||||
TEST_ExpectTrue( |
||||
class'BigInt'.static.FromDecimal_S("4238756872643464981264982128742389") |
||||
.ToString() == "4238756872643464981264982128742389"); |
||||
|
||||
Issue("`ToString()` doesn't return value `BigInt` was initialized with" @ |
||||
"a negative `int`."); |
||||
TEST_ExpectTrue(class'BigInt'.static.FromInt(-666).ToString() == "-666"); |
||||
TEST_ExpectTrue( |
||||
class'BigInt'.static.FromInt(-MaxInt).ToString() == "-2147483647"); |
||||
TEST_ExpectTrue( |
||||
class'BigInt'.static.FromInt(-MaxInt - 1).ToString() == "-2147483648"); |
||||
|
||||
Issue("`ToString()` doesn't return value `BigInt` was initialized with" @ |
||||
"a negative integer inside `string`."); |
||||
TEST_ExpectTrue( |
||||
class'BigInt'.static.FromDecimal_S("-2147483648").ToString() |
||||
== "-2147483648"); |
||||
TEST_ExpectTrue( |
||||
class'BigInt'.static.FromDecimal_S("-238473846327894632879097410348127") |
||||
.ToString() == "-238473846327894632879097410348127"); |
||||
} |
||||
|
||||
protected static function Test_ToText() |
||||
{ |
||||
Context("Testing `ToText()` method of `BigInt`s."); |
||||
Issue("`ToText()` doesn't return value `BigInt` was initialized with" @ |
||||
"a positive integer inside `string`."); |
||||
TEST_ExpectTrue(class'BigInt'.static |
||||
.FromDecimal_S("2147483647") |
||||
.ToText() |
||||
.ToString() == "2147483647"); |
||||
TEST_ExpectTrue(class'BigInt'.static |
||||
.FromDecimal_S("65784236592763459236597823645978236592378659110571388") |
||||
.ToText() |
||||
.ToString() == "65784236592763459236597823645978236592378659110571388"); |
||||
|
||||
Issue("`ToText()` doesn't return value `BigInt` was initialized with" @ |
||||
"a negative integer inside `string`."); |
||||
TEST_ExpectTrue(class'BigInt'.static |
||||
.FromDecimal_S("-2147483648") |
||||
.ToText() |
||||
.ToString() == "-2147483648"); |
||||
TEST_ExpectTrue(class'BigInt'.static |
||||
.FromDecimal_S("-9827657892365923510176386357863078603212901078175829") |
||||
.ToText() |
||||
.ToString() == "-9827657892365923510176386357863078603212901078175829"); |
||||
} |
||||
|
||||
/*protected static function Test_AddingValues() |
||||
{ |
||||
Context("Testing adding values to `BigInt`"); |
||||
Issue("`JSONPointer` is incorrectly extracted."); |
||||
}*/ |
||||
|
||||
defaultproperties |
||||
{ |
||||
caseGroup = "Database" |
||||
caseName = "BigInt" |
||||
} |
Loading…
Reference in new issue