Browse Source

Add comments and convenience methods to `BigInt`

pull/8/head
Anton Tarasenko 2 years ago
parent
commit
1cd890743a
  1. 417
      sources/BaseRealm/API/Math/BigInt.uc
  2. 38
      sources/BaseRealm/API/Math/MathAPI.uc

417
sources/BaseRealm/API/Math/BigInt.uc

@ -1,8 +1,6 @@
/** /**
* A simply big integer implementation, mostly to allow Acedia's databases to * A simple big integer implementation, mostly to allow Acedia's databases to
* store integers of arbitrary size. Should not be used in regular * store integers of arbitrary size.
* computations, designed to store player statistic values that are incremented
* from time to time.
* Copyright 2022 Anton Tarasenko * Copyright 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
@ -23,6 +21,39 @@
class BigInt extends AcediaObject class BigInt extends AcediaObject
dependson(MathAPI); dependson(MathAPI);
/**
* # `BigInt`
*
* A simple big integer implementation, mostly to allow Acedia's databases to
* store integers of arbitrary size. It can be used for long arithmetic
* computations, but it was mainly meant as a players' statistics counter and,
* therefore, not optimized for performing large amount of operations.
*
* ## Usage
*
* `BigInt` can be created from both `int` and decimal `BaseText`/`string`
* representation, preferably by `MathAPI` (`_.math.`) methods
* `ToBigInt()`/`MakeBigInt()`.
* Then it can be combined either directly with other `BigInt` or with
* `int`/`BaseText`/`string` through available arithmetic operations.
* To make use of stored value one can convert it back into either `int` or
* decimal `BaseText`/`string` representation.
* Newly allocated `BigInt` is guaranteed to hold `0` as value.
*/
/**
* `BigInt` data as a `struct` - meant to be used to store `BigInt`'s values
* inside the local databases.
*/
struct BigIntData
{
var bool negative;
var array<byte> digits;
};
/**
* Used to represent a result of comparison for `BigInt`s with each other.
*/
enum BigIntCompareResult enum BigIntCompareResult
{ {
BICR_Less, BICR_Less,
@ -30,24 +61,38 @@ enum BigIntCompareResult
BICR_Greater BICR_Greater
}; };
// Does stored `BigInt` has negative sign?
var private bool negative; var private bool negative;
// Digits array, from least to most significant: // Digits array, from least to most significant. For example, for 13524:
// For example, for 13524:
// `digits[0] = 4` // `digits[0] = 4`
// `digits[1] = 2` // `digits[1] = 2`
// `digits[2] = 5` // `digits[2] = 5`
// `digits[3] = 3` // `digits[3] = 3`
// `digits[4] = 1` // `digits[4] = 1`
// Valid `BigInt` should not have this array empty. // Valid `BigInt` should not have this array empty: zero should be
// represented by an array with a single `0`-element.
// This isn't a most efficient representation for `BigInt`, but it's easy
// to convert to and from decimal representation.
// INVARIANT: this array must not have leading (in the sense of significance)
// zeroes. That is, last element of the array should not be a `0`. The only
// exception if if stored value is `0`, then `digits` must consist of a single
// `0` element.
var private array<byte> digits; var private array<byte> digits;
const ALMOST_MAX_INT = 147483647; // Constants useful for converting `BigInt` back to `int`, while avoiding
// overflow.
// We can add less digits than that without any fear of overflow
const DIGITS_IN_MAX_INT = 10; const DIGITS_IN_MAX_INT = 10;
// Maximum `int` value is `2147483647`, so in case most significant digit
// is 10th and is `2` (so number has a form of "2xxxxxxxxx"), to check for
// overflow we only need to compare combination of the rest of the digits with
// this constant.
const ALMOST_MAX_INT = 147483647;
// To add last digit we add/subtract that digit multiplied by this value.
const LAST_DIGIT_ORDER = 1000000000; const LAST_DIGIT_ORDER = 1000000000;
protected function Constructor() protected function Constructor()
{ {
// Init with zero
SetZero(); SetZero();
} }
@ -57,7 +102,7 @@ protected function Finalizer()
digits.length = 0; digits.length = 0;
} }
// ??? // Auxiliary method to set current value to zero
private function BigInt SetZero() private function BigInt SetZero()
{ {
negative = false; negative = false;
@ -66,8 +111,8 @@ private function BigInt SetZero()
return self; return self;
} }
// Minimal `int` value `-2,147,483,648` is slightly a pain to handle, so just // Minimal `int` value `-2,147,483,648` is somewhat of a pain to handle, so
// use this pre-made constructor for it // just use this auxiliary pre-made constructor for it
private function BigInt SetMinimalNegative() private function BigInt SetMinimalNegative()
{ {
negative = true; negative = true;
@ -110,6 +155,32 @@ private final function TrimLeadingZeroes()
} }
} }
/**
* Changes current value of `BigInt` to given `BigInt` value.
*
* @param value New value of the caller `BigInt`. If `none` is given,
* method does nothing.
* @return Self-reference to allow for method chaining.
*/
public final function BigInt Set(BigInt value)
{
if (value == none) {
return self;
}
value.TrimLeadingZeroes();
digits = value.digits;
negative = value.negative;
return self;
}
/**
* Changes current value of `BigInt` to given `int` value `value`.
*
* Cannot fail.
*
* @param value New value of the caller `BigInt`.
* @return Self-reference to allow for method chaining.
*/
public final function BigInt SetInt(int value) public final function BigInt SetInt(int value)
{ {
local MathAPI.IntegerDivisionResult divisionResult; local MathAPI.IntegerDivisionResult divisionResult;
@ -146,7 +217,18 @@ public final function BigInt SetInt(int value)
return self; return self;
} }
public final function BigInt Set(BaseText value) /**
* Changes current value of `BigInt` to the value, given by decimal
* representation inside `value` argument.
*
* If invalid decimal representation (digits only, possibly with leading sign)
* is given - behavior is undefined. Otherwise cannot fail.
*
* @param value New value of the caller `BigInt`, given by decimal
* its representation. If `none` is given, method does nothing.
* @return Self-reference to allow for method chaining.
*/
public final function BigInt SetDecimal(BaseText value)
{ {
local int i; local int i;
local byte nextDigit; local byte nextDigit;
@ -158,14 +240,16 @@ public final function BigInt Set(BaseText value)
} }
parser = value.Parse(); parser = value.Parse();
negative = parser.Match(P("-")).Ok(); negative = parser.Match(P("-")).Ok();
if (!parser.Ok()) {
parser.R().Match(P("+")).Ok();
}
// Reset to valid state whether sign was consumed or not
parser.Confirm(); parser.Confirm();
parser.R(); parser.R();
// Reset current value
digits.length = 0;
digits.length = parser.GetRemainingLength(); digits.length = parser.GetRemainingLength();
/*if (digits.length <= 0) // Parse new one
{
parser.FreeSelf();
return SetZero();
}*/
i = digits.length - 1; i = digits.length - 1;
while (!parser.HasFinished()) while (!parser.HasFinished())
{ {
@ -183,17 +267,29 @@ public final function BigInt Set(BaseText value)
return self; return self;
} }
public final function BigInt Set_S(string value) /**
* Changes current value of `BigInt` to the value, given by decimal
* representation inside `value` argument.
*
* If invalid decimal representation (digits only, possibly with leading sign)
* is given - behavior is undefined. Otherwise cannot fail.
*
* @param value New value of the caller `BigInt`, given by decimal
* its representation.
* @return Self-reference to allow for method chaining.
*/
public final function BigInt SetDecimal_S(string value)
{ {
local MutableText wrapper; local MutableText wrapper;
wrapper = __().text.FromStringM(value); wrapper = __().text.FromStringM(value);
Set(wrapper); SetDecimal(wrapper);
wrapper.FreeSelf(); wrapper.FreeSelf();
return self; return self;
} }
public function BigIntCompareResult _compareModulus(BigInt other) // Auxiliary method for comparing two `BigInt`s by their absolute value.
private function BigIntCompareResult _compareAbsolute(BigInt other)
{ {
local int i; local int i;
local array<byte> otherDigits; local array<byte> otherDigits;
@ -218,17 +314,30 @@ public function BigIntCompareResult _compareModulus(BigInt other)
return BICR_Greater; return BICR_Greater;
} }
/**
* Compares caller `BigInt` to `other`.
*
* @param other Value to compare the caller `BigInt`.
* If given reference is `none` - behavior is undefined.
* @return `BigIntCompareResult` representing the result of comparison.
* Returned value describes how caller `BigInt` relates to the `other`,
* e.g. if `BICR_Less` was returned - it means that caller `BigInt` is
* smaller that `other`.
*/
public function BigIntCompareResult Compare(BigInt other) public function BigIntCompareResult Compare(BigInt other)
{ {
local BigIntCompareResult resultForModulus; local BigIntCompareResult resultForModulus;
if (other == none) {
return BICR_Less;
}
if (negative && !other.negative) { if (negative && !other.negative) {
return BICR_Less; return BICR_Less;
} }
if (!negative && other.negative) { if (!negative && other.negative) {
return BICR_Greater; return BICR_Greater;
} }
resultForModulus = _compareModulus(other); resultForModulus = _compareAbsolute(other);
if (resultForModulus == BICR_Equal) { if (resultForModulus == BICR_Equal) {
return BICR_Equal; return BICR_Equal;
} }
@ -240,6 +349,70 @@ public function BigIntCompareResult Compare(BigInt other)
return BICR_Greater; return BICR_Greater;
} }
/**
* Compares caller `BigInt` to `other`.
*
* @param other Value to compare the caller `BigInt`.
* @return `BigIntCompareResult` representing the result of comparison.
* Returned value describes how caller `BigInt` relates to the `other`,
* e.g. if `BICR_Less` was returned - it means that caller `BigInt` is
* smaller that `other`.
*/
public function BigIntCompareResult CompareInt(int other)
{
local BigInt wrapper;
local BigIntCompareResult result;
wrapper = _.math.ToBigInt(other);
result = Compare(wrapper);
wrapper.FreeSelf();
return result;
}
/**
* Compares caller `BigInt` to `other`.
*
* @param other Value to compare the caller `BigInt`.
* If given reference is `none` - behavior is undefined.
* @return `BigIntCompareResult` representing the result of comparison.
* Returned value describes how caller `BigInt` relates to the `other`,
* e.g. if `BICR_Less` was returned - it means that caller `BigInt` is
* smaller that `other`.
*/
public function BigIntCompareResult CompareDecimal(BaseText other)
{
local BigInt wrapper;
local BigIntCompareResult result;
wrapper = _.math.MakeBigInt(other);
result = Compare(wrapper);
wrapper.FreeSelf();
return result;
}
/**
* Compares caller `BigInt` to `other`.
*
* @param other Value to compare the caller `BigInt`.
* If given value contains invalid decimal value - behavior is undefined.
* @return `BigIntCompareResult` representing the result of comparison.
* Returned value describes how caller `BigInt` relates to the `other`,
* e.g. if `BICR_Less` was returned - it means that caller `BigInt` is
* smaller that `other`.
*/
public function BigIntCompareResult CompareDecimal_S(string other)
{
local BigInt wrapper;
local BigIntCompareResult result;
wrapper = _.math.MakeBigInt_S(other);
result = Compare(wrapper);
wrapper.FreeSelf();
return result;
}
// Adds absolute values of caller `BigInt` and `other` with no changes to
// the sign
private function _add(BigInt other) private function _add(BigInt other)
{ {
local int i; local int i;
@ -269,6 +442,8 @@ private function _add(BigInt other)
// No leading zeroes can be created here, so no need to trim // No leading zeroes can be created here, so no need to trim
} }
// Subtracts absolute value of `other` from the caller `BigInt`, flipping
// caller's sign in case `other`'s absolute value is bigger.
private function _sub(BigInt other) private function _sub(BigInt other)
{ {
local int i; local int i;
@ -279,7 +454,7 @@ private function _sub(BigInt other)
if (other == none) { if (other == none) {
return; return;
} }
resultForModulus = _compareModulus(other); resultForModulus = _compareAbsolute(other);
if (resultForModulus == BICR_Equal) if (resultForModulus == BICR_Equal)
{ {
SetZero(); SetZero();
@ -315,8 +490,17 @@ private function _sub(BigInt other)
TrimLeadingZeroes(); TrimLeadingZeroes();
} }
/**
* Adds `other` value to the caller `BigInt`.
*
* @param other Value to add. If `none` is given method does nothing.
* @return Self-reference to allow for method chaining.
*/
public function BigInt Add(BigInt other) public function BigInt Add(BigInt other)
{ {
if (other == none) {
return self;
}
if (negative == other.negative) { if (negative == other.negative) {
_add(other); _add(other);
} }
@ -326,6 +510,14 @@ public function BigInt Add(BigInt other)
return self; return self;
} }
/**
* Adds `other` value to the caller `BigInt`.
*
* Cannot fail.
*
* @param other Value to add.
* @return Self-reference to allow for method chaining.
*/
public function BigInt AddInt(int other) public function BigInt AddInt(int other)
{ {
local BigInt otherObject; local BigInt otherObject;
@ -336,6 +528,53 @@ public function BigInt AddInt(int other)
return self; return self;
} }
/**
* Adds `other` value to the caller `BigInt`.
*
* If invalid decimal representation (digits only, possibly with leading sign)
* is given - behavior is undefined. Otherwise cannot fail.
*
* @param other Value to add. If `none` is given, method does nothing.
* @return Self-reference to allow for method chaining.
*/
public function BigInt AddDecimal(BaseText other)
{
local BigInt otherObject;
if (other == none) {
return self;
}
otherObject = _.math.MakeBigInt(other);
Add(otherObject);
_.memory.Free(otherObject);
return self;
}
/**
* Adds `other` value to the caller `BigInt`.
*
* If invalid decimal representation (digits only, possibly with leading sign)
* is given - behavior is undefined. Otherwise cannot fail.
*
* @param other Value to add.
* @return Self-reference to allow for method chaining.
*/
public function BigInt AddDecimal_S(string other)
{
local BigInt otherObject;
otherObject = _.math.MakeBigInt_S(other);
Add(otherObject);
_.memory.Free(otherObject);
return self;
}
/**
* Subtracts `other` value to the caller `BigInt`.
*
* @param other Value to subtract. If `none` is given method does nothing.
* @return Self-reference to allow for method chaining.
*/
public function BigInt Subtract(BigInt other) public function BigInt Subtract(BigInt other)
{ {
if (negative != other.negative) { if (negative != other.negative) {
@ -347,21 +586,92 @@ public function BigInt Subtract(BigInt other)
return self; return self;
} }
/**
* Subtracts `other` value to the caller `BigInt`.
*
* Cannot fail.
*
* @param other Value to subtract.
* @return Self-reference to allow for method chaining.
*/
public function BigInt SubtractInt(int other) public function BigInt SubtractInt(int other)
{ {
local BigInt otherObject; local BigInt otherObject;
otherObject = _.math.ToBigInt(other); otherObject = _.math.ToBigInt(other);
Add(otherObject); Subtract(otherObject);
_.memory.Free(otherObject);
return self;
}
/**
* Subtracts `other` value to the caller `BigInt`.
*
* If invalid decimal representation (digits only, possibly with leading sign)
* is given - behavior is undefined. Otherwise cannot fail.
*
* @param other Value to subtract. If `none`, method does nothing.
* @return Self-reference to allow for method chaining.
*/
public function BigInt SubtractDecimal(BaseText other)
{
local BigInt otherObject;
if (other == none) {
return self;
}
otherObject = _.math.MakeBigInt(other);
Subtract(otherObject);
_.memory.Free(otherObject);
return self;
}
/**
* Subtracts `other` value to the caller `BigInt`.
*
* If invalid decimal representation (digits only, possibly with leading sign)
* is given - behavior is undefined. Otherwise cannot fail.
*
* @param other Value to subtract.
* @return Self-reference to allow for method chaining.
*/
public function BigInt SubtractDecimal_S(string other)
{
local BigInt otherObject;
otherObject = _.math.MakeBigInt_S(other);
Subtract(otherObject);
_.memory.Free(otherObject); _.memory.Free(otherObject);
return self; return self;
} }
/**
* Checks if caller `BigInt` is negative. Zero is not considered negative
* number.
*
* @return `true` if stored value is negative (`< 0`) and `false` otherwise
* (`>= 0`).
*/
public function bool IsNegative() public function bool IsNegative()
{ {
// Handle special case of zero first (it ignores `negative` flag)
if (digits.length == 1 && digits[0] == 0) {
return false;
}
return negative; return negative;
} }
/**
* Converts caller `BigInt` into `int` representation.
*
* In case stored value is outside `int`'s value range
* (`[-MaxInt-1, MaxInt] == [-2147483648; 2147483647]`),
* method returns either maximal or minimal possible value, depending on
* the `BigInt`'s sign.
*
* @return `int` representation of the caller `BigInt`, clamped into available
* `int` value range.
*/
public function int ToInt() public function int ToInt()
{ {
local int i; local int i;
@ -394,7 +704,10 @@ public function int ToInt()
return accumulator; return accumulator;
} }
// Assumes `digits.length <= DIGITS_IN_MAX_INT` // Adding `DIGITS_IN_MAX_INT - 1` will never lead to an overflow, but
// adding the next digit can, so we need to handle it differently and more
// carefully.
// Assumes `digits.length <= DIGITS_IN_MAX_INT`.
private function int AddUnsafeDigitToInt(int accumulator) private function int AddUnsafeDigitToInt(int accumulator)
{ {
local int unsafeDigit; local int unsafeDigit;
@ -433,11 +746,21 @@ private function int AddUnsafeDigitToInt(int accumulator)
return MaxInt; return MaxInt;
} }
/**
* Converts caller `BigInt` into `Text` representation.
*
* @return `Text` representation of the caller `BigInt`.
*/
public function Text ToText() public function Text ToText()
{ {
return ToText_M().IntoText(); return ToText_M().IntoText();
} }
/**
* Converts caller `BigInt` into `MutableText` representation.
*
* @return `MutableText` representation of the caller `BigInt`.
*/
public function MutableText ToText_M() public function MutableText ToText_M()
{ {
local int i; local int i;
@ -453,6 +776,11 @@ public function MutableText ToText_M()
return result; return result;
} }
/**
* Converts caller `BigInt` into `string` representation.
*
* @return `string` representation of the caller `BigInt`.
*/
public function string ToString() public function string ToString()
{ {
local int i; local int i;
@ -467,6 +795,45 @@ public function string ToString()
return result; return result;
} }
/**
* Restores `BigInt` from the `BigIntData` value.
*
* This method is created to make an efficient way to store `BigInt` inside
* local databases.
*
* @param data Data to read new caller `BigInt`'s value from.
*/
public function FromData(BigIntData data)
{
local int i;
negative = data.negative;
digits = data.digits;
// Deal with possibly erroneous data
for (i = 0; i < digits.length; i += 1) {
if (digits[i] > 9) {
digits[i] = 9;
}
}
}
/**
* Converts caller `BigInt`'s value into `BigIntData`.
*
* This method is created to make an efficient way to store `BigInt` inside
* local databases.
*
* @return Value of the caller `BigInt` in the `struct` form.
*/
public function BigIntData ToData()
{
local BigIntData result;
result.negative = negative;
result.digits = digits;
return result;
}
defaultproperties defaultproperties
{ {
} }

38
sources/BaseRealm/API/Math/MathAPI.uc

@ -31,6 +31,13 @@ struct IntegerDivisionResult
var int remainder; var int remainder;
}; };
/**
* Changes current value of `BigInt` to given `BigInt` value.
*
* @param value New value of the caller `BigInt`. If `none` is given,
* method does nothing.
* @return Self-reference to allow for method chaining.
*/
public function BigInt ToBigInt(int value) public function BigInt ToBigInt(int value)
{ {
local BigInt result; local BigInt result;
@ -39,20 +46,45 @@ public function BigInt ToBigInt(int value)
return result.SetInt(value); return result.SetInt(value);
} }
public function BigInt MakeBigInt(Text value) /**
* Creates new `BigInt` value, base on the decimal representation given by
* `value`.
*
* If invalid decimal representation (digits only, possibly with leading sign)
* is given - contents of returned value are undefined. Otherwise cannot fail.
*
* @param value New value of the caller `BigInt`, given by decimal
* its representation. If `none` is given, method returns `BigInt`
* containing `0` as value.
* @return Created `BigInt`, containing value, given by its the decimal
* representation `value`.
*/
public function BigInt MakeBigInt(BaseText value)
{ {
local BigInt result; local BigInt result;
result = BigInt(_.memory.Allocate(class'BigInt')); result = BigInt(_.memory.Allocate(class'BigInt'));
return result.Set(value); return result.SetDecimal(value);
} }
/**
* Creates new `BigInt` value, base on the decimal representation given by
* `value`.
*
* If invalid decimal representation (digits only, possibly with leading sign)
* is given - contents of returned value are undefined. Otherwise cannot fail.
*
* @param value New value of the caller `BigInt`, given by decimal
* its representation.
* @return Created `BigInt`, containing value, given by its the decimal
* representation `value`.
*/
public function BigInt MakeBigInt_S(string value) public function BigInt MakeBigInt_S(string value)
{ {
local BigInt result; local BigInt result;
result = BigInt(_.memory.Allocate(class'BigInt')); result = BigInt(_.memory.Allocate(class'BigInt'));
return result.Set_S(value); return result.SetDecimal_S(value);
} }
/** /**

Loading…
Cancel
Save