Browse Source

Fix style for `BigInt`

core_refactor
Anton Tarasenko 2 years ago
parent
commit
15a49d8ddc
  1. 679
      sources/BaseRealm/API/Math/BigInt.uc
  2. 29
      sources/BaseRealm/API/Math/MathAPI.uc
  3. 60
      sources/BaseRealm/API/Math/Tests/TEST_BigInt.uc

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

@ -1,7 +1,8 @@
/** /**
* A simple big integer implementation, mostly to allow Acedia's databases to * Author: dkanus
* store integers of arbitrary size. * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* Copyright 2022 Anton Tarasenko * License: GPL
* Copyright 2022-2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -19,41 +20,22 @@
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class BigInt extends AcediaObject class BigInt extends AcediaObject
dependson(MathAPI); dependson(MathApi);
/** /// A simple big integer implementation.
* # `BigInt` ///
* /// [`BigInt`]'s main purpose is to allow Acedia's databases to store integers of arbitrary size.
* A simple big integer implementation, mostly to allow Acedia's databases to /// It can be used for long arithmetic computations, but it was mainly meant as a players'
* store integers of arbitrary size. It can be used for long arithmetic /// statistics counter and, therefore, not optimized for performing large amount of operations.
* 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
* `BigInt` data as a `struct` - meant to be used to store `BigInt`'s values /// the local databases.
* inside the local databases. struct BigIntData {
*/
struct BigIntData
{
var bool negative; var bool negative;
var array<byte> digits; var array<byte> digits;
}; };
/** /// Result of comparison for [`BigInt`]s with each other.
* Used to represent a result of comparison for `BigInt`s with each other.
*/
enum BigIntCompareResult enum BigIntCompareResult
{ {
BICR_Less, BICR_Less,
@ -61,60 +43,60 @@ enum BigIntCompareResult
BICR_Greater BICR_Greater
}; };
// Does stored `BigInt` has negative sign? /// Does stored [`BigInt`] have a negative sign?
var private bool negative; var private bool negative;
// Digits array, from least to most significant. For example, for 13524: /// Digits array, from least to most significant. For example, for 13524:
// `digits[0] = 4` ///
// `digits[1] = 2` /// ```
// `digits[2] = 5` /// `digits[0] = 4`
// `digits[3] = 3` /// `digits[1] = 2`
// `digits[4] = 1` /// `digits[2] = 5`
// Valid `BigInt` should not have this array empty: zero should be /// `digits[3] = 3`
// represented by an array with a single `0`-element. /// `digits[4] = 1`
// 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) /// Valid [`BigInt`] should not have this array empty: zero should be represented by an array with
// zeroes. That is, last element of the array should not be a `0`. The only /// a single `0`-element.
// exception if if stored value is `0`, then `digits` must consist of a single /// This isn't a most efficient representation for [`BigInt`], but it's easy to convert to and from
// `0` element. /// decimal representation.
///
/// # Invariants
///
/// 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;
// Constants useful for converting `BigInt` back to `int`, while avoiding /// Constants useful for converting [`BigInt`] back to [`int`], while avoiding overflow.
// overflow. /// We can add less digits than that without any fear of 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 /// Maximum [`int`] value is `2147483647`, so in case most significant digit is 10th and is `2`
// is 10th and is `2` (so number has a form of "2xxxxxxxxx"), to check for /// (so number has a form of `2xxxxxxxxx`), to check for overflow we only need to compare
// overflow we only need to compare combination of the rest of the digits with /// combination of the rest of the digits with this constant.
// this constant.
const ALMOST_MAX_INT = 147483647; const ALMOST_MAX_INT = 147483647;
// To add last digit we add/subtract that digit multiplied by this value. /// 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() {
{
SetZero(); SetZero();
} }
protected function Finalizer() protected function Finalizer() {
{
negative = false; negative = false;
digits.length = 0; digits.length = 0;
} }
// Auxiliary method to set current value to zero // Auxiliary method to set current value to zero
private function BigInt SetZero() private function SetZero() {
{
negative = false; negative = false;
digits.length = 1; digits.length = 1;
digits[0] = 0; digits[0] = 0;
return self;
} }
// Minimal `int` value `-2,147,483,648` is somewhat of a pain to handle, so // Minimal [`int`] value `-2,147,483,648` is somewhat of a pain to handle, so just use this
// just use this auxiliary pre-made constructor for it // auxiliary pre-made constructor for it
private function BigInt SetMinimalNegative() private function SetMinimalNegative() {
{
negative = true; negative = true;
digits.length = 10; digits.length = 10;
digits[0] = 8; digits[0] = 8;
@ -127,20 +109,17 @@ private function BigInt SetMinimalNegative()
digits[7] = 4; digits[7] = 4;
digits[8] = 1; digits[8] = 1;
digits[9] = 2; digits[9] = 2;
return self;
} }
// Removes unnecessary zeroes from leading digit positions `digits`. // Removes unnecessary zeroes from leading digit positions `digits`.
// Does not change contained value. // Does not change contained value.
private final function TrimLeadingZeroes() private final function TrimLeadingZeroes() {
{
local int i, zeroesToRemove; local int i, zeroesToRemove;
// Find how many leading zeroes there is. // Finds how many leading zeroes there is.
// Since `digits` stores digits from least to most significant, we need // Since `digits` stores digits from least to most significant, we need to check from the end of
// to check from the end of `digits` array. // `digits` array.
for (i = digits.length - 1; i >= 0; i -= 1) for (i = digits.length - 1; i >= 0; i -= 1) {
{
if (digits[i] != 0) { if (digits[i] != 0) {
break; break;
} }
@ -155,142 +134,123 @@ private final function TrimLeadingZeroes()
} }
} }
/** /// Changes current value of [`BigInt`] to given value.
* Changes current value of `BigInt` to given `BigInt` value. public final function Set(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) { if (value != none) {
return self;
}
value.TrimLeadingZeroes(); value.TrimLeadingZeroes();
digits = value.digits; digits = value.digits;
negative = value.negative; negative = value.negative;
return self; }
} }
/** /// Changes current value of [`BigInt`] to given value.
* Changes current value of `BigInt` to given `int` value `value`. public final function SetInt(int value) {
* local MathApi.IntegerDivisionResult divisionResult;
* 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)
{
local MathAPI.IntegerDivisionResult divisionResult;
negative = false; negative = false;
digits.length = 0; digits.length = 0;
if (value < 0) if (value < 0) {
{ // Treat special case of minimal [`int`] value `-2,147,483,648` that
// Treat special case of minimal `int` value `-2,147,483,648` that // won't fit into positive [`int`] as special and use pre-made
// won't fit into positive `int` as special and use pre-made
// specialized constructor `CreateMinimalNegative()` // specialized constructor `CreateMinimalNegative()`
if (value < -MaxInt) { if (value < -maxInt) {
return SetMinimalNegative(); SetMinimalNegative();
} return;
else } else {
{
negative = true; negative = true;
value *= -1; value *= -1;
} }
} }
if (value == 0) { if (value == 0) {
digits[0] = 0; digits[0] = 0;
} } else {
else while (value > 0) {
{
while (value > 0)
{
divisionResult = __().math.IntegerDivision(value, 10); divisionResult = __().math.IntegerDivision(value, 10);
value = divisionResult.quotient; value = divisionResult.quotient;
digits[digits.length] = divisionResult.remainder; digits[digits.length] = divisionResult.remainder;
} }
} }
TrimLeadingZeroes(); TrimLeadingZeroes();
return self;
} }
/** /// Changes current value of [`BigInt`] to the value, given by decimal representation.
* Changes current value of `BigInt` to the value, given by decimal ///
* representation inside `value` argument. /// If `none` or invalid decimal representation (digits only, possibly with leading sign) is given
* /// as an argument, caller's value won't change.
* If invalid decimal representation (digits only, possibly with leading sign) /// Returns `true` in case of success and `false` otherwise.
* is given - behavior is undefined. Otherwise cannot fail. public final function bool SetDecimal(BaseText value) {
*
* @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;
local bool newNegative;
local array<byte> newDigits;
local Parser parser; local Parser parser;
local Basetext.Character nextCharacter; local Basetext.Character nextCharacter;
if (value == none) { if (value == none) {
return none; return false;
} }
parser = value.Parse(); parser = value.Parse();
negative = parser.Match(P("-")).Ok(); newNegative = ParseSign(parser);
if (!parser.Ok()) {
parser.R().Match(P("+")).Ok();
}
// Reset to valid state whether sign was consumed or not // Reset to valid state whether sign was consumed or not
parser.Confirm(); parser.Confirm();
parser.R(); parser.R();
// Reset current value newDigits.length = parser.GetRemainingLength();
digits.length = 0;
digits.length = parser.GetRemainingLength();
// Parse new one // Parse new one
i = digits.length - 1; i = newDigits.length - 1;
while (!parser.HasFinished()) while (!parser.HasFinished()){
{
// This should not happen, but just in case // This should not happen, but just in case
if (i < 0) { if (i < 0) {
break; break;
} }
parser.MCharacter(nextCharacter); parser.MCharacter(nextCharacter);
nextDigit = Clamp(__().text.CharacterToInt(nextCharacter), 0, 9); nextDigit = __().text.CharacterToInt(nextCharacter, 10);
digits[i] = nextDigit; if (nextDigit < 0) {
return false;
}
newDigits[i] = nextDigit;
i -= 1; i -= 1;
} }
parser.FreeSelf(); parser.FreeSelf();
digits = newDigits;
negative = newNegative;
TrimLeadingZeroes(); TrimLeadingZeroes();
return self; return true;
} }
/** // Tries to parse either `+` or `-` and returns `true` iff it parsed `-`.
* Changes current value of `BigInt` to the value, given by decimal // If neither got parsed, `parser` will enter failed state.
* representation inside `value` argument. // Assumes `parser` isn't `none`.
* private final function bool ParseSign(Parser parser) {
* If invalid decimal representation (digits only, possibly with leading sign) parser.Match(P("-"));
* is given - behavior is undefined. Otherwise cannot fail. negative = parser.Ok();
* if (parser.Ok()) {
* @param value New value of the caller `BigInt`, given by decimal negative = true;
* its representation. }
* @return Self-reference to allow for method chaining. else {
*/ parser.R();
public final function BigInt SetDecimal_S(string value) parser.Match(P("+"));
{ }
return negative;
}
/// Changes current value of [`BigInt`] to the value, given by decimal representation.
///
/// If invalid decimal representation (digits only, possibly with leading sign) is given as
/// an argument, caller's value won't change.
/// Returns `true` in case of success and `false` otherwise.
public final function bool SetDecimal_S(string value) {
local bool result;
local MutableText wrapper; local MutableText wrapper;
wrapper = __().text.FromStringM(value); wrapper = __().text.FromStringM(value);
SetDecimal(wrapper); result = SetDecimal(wrapper);
wrapper.FreeSelf(); wrapper.FreeSelf();
return self; return result;
} }
// Auxiliary method for comparing two `BigInt`s by their absolute value. // Auxiliary method for comparing two [`BigInt`]s by their absolute value.
private function BigIntCompareResult _compareAbsolute(BigInt other) private function BigIntCompareResult _compareAbsolute(BigInt other) {
{
local int i; local int i;
local array<byte> otherDigits; local array<byte> otherDigits;
@ -314,52 +274,32 @@ private function BigIntCompareResult _compareAbsolute(BigInt other)
return BICR_Greater; return BICR_Greater;
} }
/** /// Compares caller [`BigInt`] to [`other`].
* Compares caller `BigInt` to `other`. ///
* /// [`BigIntCompareResult`] representing the result of comparison is returned as a result.
* @param other Value to compare the caller `BigInt`. /// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then
* If given reference is `none` - behavior is undefined. /// it means that caller [`BigInt`] is smaller that `other`.
* @return `BigIntCompareResult` representing the result of comparison. /// If argument is `none`, then it is considered to be less ([`BICR_Less`]) than caller [`BigInt`].
* Returned value describes how caller `BigInt` relates to the `other`, public function BigIntCompareResult Compare(BigInt other) {
* e.g. if `BICR_Less` was returned - it means that caller `BigInt` is
* smaller that `other`.
*/
public function BigIntCompareResult Compare(BigInt other)
{
local BigIntCompareResult resultForModulus; local BigIntCompareResult resultForModulus;
if (other == none) { if (other == none) return BICR_Less;
return BICR_Less; if (negative && !other.negative) return BICR_Less;
} if (!negative && other.negative) return BICR_Greater;
if (negative && !other.negative) {
return BICR_Less;
}
if (!negative && other.negative) {
return BICR_Greater;
}
resultForModulus = _compareAbsolute(other); resultForModulus = _compareAbsolute(other);
if (resultForModulus == BICR_Equal) { if (resultForModulus == BICR_Equal) return BICR_Equal;
return BICR_Equal; if (negative && (resultForModulus == BICR_Greater)) return BICR_Less;
} if (!negative && (resultForModulus == BICR_Less)) return BICR_Less;
if ( (negative && (resultForModulus == BICR_Greater))
|| (!negative && (resultForModulus == BICR_Less)) )
{
return BICR_Less;
}
return BICR_Greater; return BICR_Greater;
} }
/** /// Compares caller [`BigInt`] to [`other`].
* Compares caller `BigInt` to `other`. ///
* /// [`BigIntCompareResult`] representing the result of comparison is returned as a result.
* @param other Value to compare the caller `BigInt`. /// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then
* @return `BigIntCompareResult` representing the result of comparison. /// it means that caller [`BigInt`] is smaller that `other`.
* Returned value describes how caller `BigInt` relates to the `other`, public function BigIntCompareResult CompareInt(int 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 BigInt wrapper;
local BigIntCompareResult result; local BigIntCompareResult result;
@ -369,39 +309,31 @@ public function BigIntCompareResult CompareInt(int other)
return result; return result;
} }
/** /// Compares caller [`BigInt`] to a decimal representation of a number.
* Compares caller `BigInt` to `other`. ///
* /// [`BigIntCompareResult`] representing the result of comparison is returned as a result.
* @param other Value to compare the caller `BigInt`. /// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then
* If given reference is `none` - behavior is undefined. /// it means that caller [`BigInt`] is smaller that `other`.
* @return `BigIntCompareResult` representing the result of comparison. /// If argument is `none` or is an invalid decimal representation (digits only, possibly with
* Returned value describes how caller `BigInt` relates to the `other`, /// leading sign, then it is considered to be less ([`BICR_Less`]) than caller [`BigInt`].
* e.g. if `BICR_Less` was returned - it means that caller `BigInt` is public function BigIntCompareResult CompareDecimal(BaseText other) {
* smaller that `other`.
*/
public function BigIntCompareResult CompareDecimal(BaseText other)
{
local BigInt wrapper; local BigInt wrapper;
local BigIntCompareResult result; local BigIntCompareResult result;
wrapper = _.math.MakeBigInt(other); wrapper = _.math.MakeBigInt(other);
result = Compare(wrapper); result = Compare(wrapper);
wrapper.FreeSelf(); _.memory.Free(wrapper);
return result; return result;
} }
/** /// Compares caller [`BigInt`] to a decimal representation of a number.
* Compares caller `BigInt` to `other`. ///
* /// [`BigIntCompareResult`] representing the result of comparison is returned as a result.
* @param other Value to compare the caller `BigInt`. /// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then
* If given value contains invalid decimal value - behavior is undefined. /// it means that caller [`BigInt`] is smaller that `other`.
* @return `BigIntCompareResult` representing the result of comparison. /// If argument is an invalid decimal representation (digits only, possibly with leading sign, then
* Returned value describes how caller `BigInt` relates to the `other`, /// it is considered to be less ([`BICR_Less`]) than caller [`BigInt`].
* e.g. if `BICR_Less` was returned - it means that caller `BigInt` is public function BigIntCompareResult CompareDecimal_S(string other) {
* smaller that `other`.
*/
public function BigIntCompareResult CompareDecimal_S(string other)
{
local BigInt wrapper; local BigInt wrapper;
local BigIntCompareResult result; local BigIntCompareResult result;
@ -411,10 +343,8 @@ public function BigIntCompareResult CompareDecimal_S(string other)
return result; return result;
} }
// Adds absolute values of caller `BigInt` and `other` with no changes to // Adds absolute values of caller [`BigInt`] and [`other`] with no changes to the sign.
// the sign private function _add(BigInt other) {
private function _add(BigInt other)
{
local int i; local int i;
local byte carry, digitSum; local byte carry, digitSum;
local array<byte> otherDigits; local array<byte> otherDigits;
@ -425,13 +355,11 @@ private function _add(BigInt other)
otherDigits = other.digits; otherDigits = other.digits;
if (digits.length < otherDigits.length) { if (digits.length < otherDigits.length) {
digits.length = otherDigits.length; digits.length = otherDigits.length;
} } else {
else {
otherDigits.length = digits.length; otherDigits.length = digits.length;
} }
carry = 0; carry = 0;
for (i = 0; i < digits.length; i += 1) for (i = 0; i < digits.length; i += 1) {
{
digitSum = digits[i] + otherDigits[i] + carry; digitSum = digits[i] + otherDigits[i] + carry;
digits[i] = _.math.Remainder(digitSum, 10); digits[i] = _.math.Remainder(digitSum, 10);
carry = (digitSum - digits[i]) / 10; carry = (digitSum - digits[i]) / 10;
@ -442,10 +370,9 @@ 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 // Subtracts absolute value of [`other`] from the caller [`BigInt`], flipping caller's sign in case
// caller's sign in case `other`'s absolute value is bigger. // `other`'s absolute value is bigger.
private function _sub(BigInt other) private function _sub(BigInt other) {
{
local int i; local int i;
local int carry, nextDigit; local int carry, nextDigit;
local array<byte> minuendDigits, subtrahendDigits; local array<byte> minuendDigits, subtrahendDigits;
@ -455,34 +382,27 @@ private function _sub(BigInt other)
return; return;
} }
resultForModulus = _compareAbsolute(other); resultForModulus = _compareAbsolute(other);
if (resultForModulus == BICR_Equal) if (resultForModulus == BICR_Equal) {
{
SetZero(); SetZero();
return; return;
} }
if (resultForModulus == BICR_Less) if (resultForModulus == BICR_Less) {
{
negative = !negative; negative = !negative;
minuendDigits = other.digits; minuendDigits = other.digits;
subtrahendDigits = digits; subtrahendDigits = digits;
} } else {
else
{
minuendDigits = digits; minuendDigits = digits;
subtrahendDigits = other.digits; subtrahendDigits = other.digits;
} }
digits.length = minuendDigits.length; digits.length = minuendDigits.length;
subtrahendDigits.length = minuendDigits.length; subtrahendDigits.length = minuendDigits.length;
carry = 0; carry = 0;
for (i = 0; i < digits.length; i += 1) for (i = 0; i < digits.length; i += 1) {
{
nextDigit = int(minuendDigits[i]) - int(subtrahendDigits[i]) + carry; nextDigit = int(minuendDigits[i]) - int(subtrahendDigits[i]) + carry;
if (nextDigit < 0) if (nextDigit < 0) {
{
nextDigit += 10; nextDigit += 10;
carry = -1; carry = -1;
} } else {
else {
carry = 0; carry = 0;
} }
digits[i] = nextDigit; digits[i] = nextDigit;
@ -490,170 +410,108 @@ private function _sub(BigInt other)
TrimLeadingZeroes(); TrimLeadingZeroes();
} }
/** /// Adds another value to the caller [`BigInt`].
* Adds `other` value to the caller `BigInt`. ///
* /// If argument is `none`, then given method does nothing.
* @param other Value to add. If `none` is given method does nothing. public function Add(BigInt other) {
* @return Self-reference to allow for method chaining.
*/
public function BigInt Add(BigInt other)
{
if (other == none) { if (other == none) {
return self; return;
} }
if (negative == other.negative) { if (negative == other.negative) {
_add(other); _add(other);
} } else {
else {
_sub(other); _sub(other);
} }
return self;
} }
/** /// Adds another value to the caller [`BigInt`].
* Adds `other` value to the caller `BigInt`. public function AddInt(int other) {
*
* Cannot fail.
*
* @param other Value to add.
* @return Self-reference to allow for method chaining.
*/
public function BigInt AddInt(int other)
{
local BigInt otherObject; local BigInt otherObject;
otherObject = _.math.ToBigInt(other); otherObject = _.math.ToBigInt(other);
Add(otherObject); Add(otherObject);
_.memory.Free(otherObject); _.memory.Free(otherObject);
return self;
} }
/** /// Adds decimal representation of the number to the caller [`BigInt`].
* Adds `other` value to the caller `BigInt`. ///
* /// If `none` or invalid decimal representation (digits only, possibly with leading sign) is given -
* If invalid decimal representation (digits only, possibly with leading sign) /// does nothing.
* is given - behavior is undefined. Otherwise cannot fail. public function AddDecimal(BaseText other) {
*
* @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; local BigInt otherObject;
if (other == none) { if (other == none) {
return self; return;
} }
otherObject = _.math.MakeBigInt(other); otherObject = _.math.MakeBigInt(other);
Add(otherObject); Add(otherObject);
_.memory.Free(otherObject); _.memory.Free(otherObject);
return self;
} }
/** /// Adds decimal representation of the number to the caller [`BigInt`].
* Adds `other` value to the caller `BigInt`. ///
* /// If invalid decimal representation (digits only, possibly with leading sign) is given -
* If invalid decimal representation (digits only, possibly with leading sign) /// does nothing.
* is given - behavior is undefined. Otherwise cannot fail. public function AddDecimal_S(string other) {
*
* @param other Value to add.
* @return Self-reference to allow for method chaining.
*/
public function BigInt AddDecimal_S(string other)
{
local BigInt otherObject; local BigInt otherObject;
otherObject = _.math.MakeBigInt_S(other); otherObject = _.math.MakeBigInt_S(other);
Add(otherObject); Add(otherObject);
_.memory.Free(otherObject); _.memory.Free(otherObject);
return self;
} }
/** /// Subtracts another value to the caller [`BigInt`].
* Subtracts `other` value to the caller `BigInt`. ///
* /// If argument is `none`, then given method does nothing.
* @param other Value to subtract. If `none` is given method does nothing. public function Subtract(BigInt other) {
* @return Self-reference to allow for method chaining.
*/
public function BigInt Subtract(BigInt other)
{
if (negative != other.negative) { if (negative != other.negative) {
_add(other); _add(other);
} } else {
else {
_sub(other); _sub(other);
} }
return self;
} }
/** /// Adds another value to the caller [`BigInt`].
* Subtracts `other` value to the caller `BigInt`. public function SubtractInt(int other) {
*
* Cannot fail.
*
* @param other Value to subtract.
* @return Self-reference to allow for method chaining.
*/
public function BigInt SubtractInt(int other)
{
local BigInt otherObject; local BigInt otherObject;
otherObject = _.math.ToBigInt(other); otherObject = _.math.ToBigInt(other);
Subtract(otherObject); Subtract(otherObject);
_.memory.Free(otherObject); _.memory.Free(otherObject);
return self;
} }
/** /// Subtracts decimal representation of the number to the caller [`BigInt`].
* Subtracts `other` value to the caller `BigInt`. ///
* /// If `none` or invalid decimal representation (digits only, possibly with leading sign) is given -
* If invalid decimal representation (digits only, possibly with leading sign) /// does nothing.
* is given - behavior is undefined. Otherwise cannot fail. public function SubtractDecimal(BaseText other) {
*
* @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; local BigInt otherObject;
if (other == none) { if (other == none) {
return self; return;
} }
otherObject = _.math.MakeBigInt(other); otherObject = _.math.MakeBigInt(other);
Subtract(otherObject); Subtract(otherObject);
_.memory.Free(otherObject); _.memory.Free(otherObject);
return self;
} }
/** /// Adds decimal representation of the number to the caller [`BigInt`].
* Subtracts `other` value to the caller `BigInt`. ///
* /// If invalid decimal representation (digits only, possibly with leading sign) is given -
* If invalid decimal representation (digits only, possibly with leading sign) /// does nothing.
* is given - behavior is undefined. Otherwise cannot fail. public function SubtractDecimal_S(string other) {
*
* @param other Value to subtract.
* @return Self-reference to allow for method chaining.
*/
public function BigInt SubtractDecimal_S(string other)
{
local BigInt otherObject; local BigInt otherObject;
otherObject = _.math.MakeBigInt_S(other); otherObject = _.math.MakeBigInt_S(other);
Subtract(otherObject); Subtract(otherObject);
_.memory.Free(otherObject); _.memory.Free(otherObject);
return self;
} }
/** /// Checks if caller [`BigInt`] is negative.
* Checks if caller `BigInt` is negative. Zero is not considered negative ///
* number. /// Returns if stored value is negative and `false` otherwise.
* /// Zero is not considered negative number.
* @return `true` if stored value is negative (`< 0`) and `false` otherwise public function bool IsNegative() {
* (`>= 0`).
*/
public function bool IsNegative()
{
// Handle special case of zero first (it ignores `negative` flag) // Handle special case of zero first (it ignores `negative` flag)
if (digits.length == 1 && digits[0] == 0) { if (digits.length == 1 && digits[0] == 0) {
return false; return false;
@ -661,19 +519,12 @@ public function bool IsNegative()
return negative; return negative;
} }
/** /// Converts caller [`BigInt`] into [`int`] representation.
* Converts caller `BigInt` into `int` representation. ///
* /// In case stored value is outside `int`'s value range
* In case stored value is outside `int`'s value range /// (`[-maxInt-1, maxInt] == [-2147483648; 2147483647]`), method returns either maximal or minimal
* (`[-MaxInt-1, MaxInt] == [-2147483648; 2147483647]`), // possible value, depending on the [`BigInt`]'s sign.
* method returns either maximal or minimal possible value, depending on public function int ToInt() {
* the `BigInt`'s sign.
*
* @return `int` representation of the caller `BigInt`, clamped into available
* `int` value range.
*/
public function int ToInt()
{
local int i; local int i;
local int accumulator; local int accumulator;
local int safeDigitsAmount; local int safeDigitsAmount;
@ -681,19 +532,16 @@ public function int ToInt()
if (digits.length <= 0) { if (digits.length <= 0) {
return 0; return 0;
} }
if (digits.length > DIGITS_IN_MAX_INT) if (digits.length > DIGITS_IN_MAX_INT) {
{
if (negative) { if (negative) {
return (-MaxInt - 1); return (-maxInt - 1);
} } else {
else { return maxInt;
return MaxInt;
} }
} }
// At most `DIGITS_IN_MAX_INT - 1` iterations // At most `DIGITS_IN_MAX_INT - 1` iterations
safeDigitsAmount = Min(DIGITS_IN_MAX_INT - 1, digits.length); safeDigitsAmount = Min(DIGITS_IN_MAX_INT - 1, digits.length);
for (i = safeDigitsAmount - 1; i >= 0; i -= 1) for (i = safeDigitsAmount - 1; i >= 0; i -= 1) {
{
accumulator *= 10; accumulator *= 10;
accumulator += digits[i]; accumulator += digits[i];
} }
@ -704,12 +552,10 @@ public function int ToInt()
return accumulator; return accumulator;
} }
// Adding `DIGITS_IN_MAX_INT - 1` will never lead to an overflow, but /// Adding `DIGITS_IN_MAX_INT - 1` will never lead to an overflow, but adding the next digit can,
// adding the next digit can, so we need to handle it differently and more /// so we need to handle it differently and more carefully.
// carefully. /// Assumes `digits.length <= DIGITS_IN_MAX_INT`.
// 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;
local bool noOverflow; local bool noOverflow;
@ -717,20 +563,17 @@ private function int AddUnsafeDigitToInt(int accumulator)
return accumulator; return accumulator;
} }
unsafeDigit = digits[DIGITS_IN_MAX_INT - 1]; unsafeDigit = digits[DIGITS_IN_MAX_INT - 1];
// `MaxInt` stats with `2`, so if last/unsafe digit is either `0` or `1`, // `maxInt` stats with `2`, so if last/unsafe digit is either `0` or `1`, there is no overflow,
// there is no overflow, otherwise - check rest of the digits // otherwise - check rest of the digits
noOverflow = (unsafeDigit < 2); noOverflow = (unsafeDigit < 2);
if (unsafeDigit == 2) if (unsafeDigit == 2) {
{ // Include `maxInt` and `-maxInt-1` (minimal possible value) into an overflow too - this way
// Include `MaxInt` and `-MaxInt-1` (minimal possible value) into // we still give a correct result, but do not have to worry about `int`-arithmetic error
// an overflow too - this way we still give a correct result, but do
// not have to worry about `int`-arithmetic error
noOverflow = noOverflow noOverflow = noOverflow
|| (negative && (accumulator > -ALMOST_MAX_INT - 1)) || (negative && (accumulator > -ALMOST_MAX_INT - 1))
|| (!negative && (accumulator < ALMOST_MAX_INT)); || (!negative && (accumulator < ALMOST_MAX_INT));
} }
if (noOverflow) if (noOverflow) {
{
if (negative) { if (negative) {
accumulator -= unsafeDigit * LAST_DIGIT_ORDER; accumulator -= unsafeDigit * LAST_DIGIT_ORDER;
} }
@ -741,28 +584,18 @@ private function int AddUnsafeDigitToInt(int accumulator)
} }
// Handle overflow // Handle overflow
if (negative) { if (negative) {
return (-MaxInt - 1); return (-maxInt - 1);
} }
return MaxInt; return maxInt;
} }
/** /// Converts caller [`BigInt`] into [`Text`] representation.
* Converts caller `BigInt` into `Text` representation. public function Text ToText() {
*
* @return `Text` representation of the caller `BigInt`.
*/
public function Text ToText()
{
return ToText_M().IntoText(); return ToText_M().IntoText();
} }
/** /// Converts caller [`BigInt`] into [`MutableText`] representation.
* Converts caller `BigInt` into `MutableText` representation. public function MutableText ToText_M() {
*
* @return `MutableText` representation of the caller `BigInt`.
*/
public function MutableText ToText_M()
{
local int i; local int i;
local MutableText result; local MutableText result;
@ -776,13 +609,8 @@ public function MutableText ToText_M()
return result; return result;
} }
/** /// Converts caller [`BigInt`] into [`string`] representation.
* Converts caller `BigInt` into `string` representation. public function string ToString() {
*
* @return `string` representation of the caller `BigInt`.
*/
public function string ToString()
{
local int i; local int i;
local string result; local string result;
@ -795,16 +623,10 @@ public function string ToString()
return result; return result;
} }
/** /// Restores [`BigInt`] from the [`BigIntData`] value.
* Restores `BigInt` from the `BigIntData` value. ///
* /// This method is created to make an efficient way to store [`BigInt`].
* This method is created to make an efficient way to store `BigInt` inside public function FromData(BigIntData data) {
* local databases.
*
* @param data Data to read new caller `BigInt`'s value from.
*/
public function FromData(BigIntData data)
{
local int i; local int i;
negative = data.negative; negative = data.negative;
@ -817,16 +639,10 @@ public function FromData(BigIntData data)
} }
} }
/** /// Converts caller [`BigInt`]'s value into [`BigIntData`].
* Converts caller `BigInt`'s value into `BigIntData`. ///
* /// This method is created to make an efficient way to store [`BigInt`].
* This method is created to make an efficient way to store `BigInt` inside public function BigIntData ToData() {
* local databases.
*
* @return Value of the caller `BigInt` in the `struct` form.
*/
public function BigIntData ToData()
{
local BigIntData result; local BigIntData result;
result.negative = negative; result.negative = negative;
@ -834,6 +650,5 @@ public function BigIntData ToData()
return result; return result;
} }
defaultproperties defaultproperties {
{
} }

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

@ -37,36 +37,41 @@ public function BigInt ToBigInt(int value)
local BigInt result; local BigInt result;
result = BigInt(_.memory.Allocate(class'BigInt')); result = BigInt(_.memory.Allocate(class'BigInt'));
return result.SetInt(value); result.SetInt(value);
return result;
} }
/// Creates new `BigInt` value, based on the decimal number representation. /// Creates new `BigInt` value, based on the decimal number representation.
/// ///
/// Expects valid decimal representation as input (digits only, possibly with leading sign), /// If (and only if) `none` or invalid decimal representation (digits only, possibly with
/// otherwise contents of returned value are undefined. /// leading sign) is given as an argument, method will return `none`.
/// If invalid decimal representation is given - contents of returned value are undefined.
/// Otherwise cannot fail and is guaranteed to return non-`none` value.
public function BigInt MakeBigInt(BaseText 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.SetDecimal(value); if (result.SetDecimal(value)) {
return result;
}
result.FreeSelf();
return none;
} }
/// Creates new `BigInt` value, based on the decimal number representation. /// Creates new `BigInt` value, based on the decimal number representation.
/// ///
/// Expects valid decimal representation as input (digits only, possibly with leading sign), /// If (and only if) invalid decimal representation (digits only, possibly with leading sign) is
/// otherwise contents of returned value are undefined. /// given as an argument, method will return `none`.
/// If invalid decimal representation is given - contents of returned value are undefined.
/// Otherwise cannot fail and is guaranteed to return non-`none` 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.SetDecimal_S(value); if (result.SetDecimal_S(value)) {
return result;
}
result.FreeSelf();
return none;
} }
/// Computes remainder of the integer division of [`number`] by [`divisor`]. /// Computes remainder of the integer division of [`number`] by [`divisor`].
@ -79,7 +84,7 @@ public function int Remainder(int number, int divisor)
local int quotient; local int quotient;
quotient = number / divisor; quotient = number / divisor;
return (number - quotient/// divisor); return (number - quotient * divisor);
} }
/// Computes quotient and remainder of the integer division of [`number`] by [`divisor`]. /// Computes quotient and remainder of the integer division of [`number`] by [`divisor`].

60
sources/BaseRealm/API/Math/Tests/TEST_BigInt.uc

@ -1,6 +1,8 @@
/** /**
* Set of tests for `BigInt` class. * Author: dkanus
* Copyright 2022 Anton Tarasenko * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* License: GPL
* Copyright 2022-2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -20,8 +22,7 @@
class TEST_BigInt extends TestCase class TEST_BigInt extends TestCase
abstract; abstract;
protected static function TESTS() protected static function TESTS() {
{
// Here we use `ToString()` method to check `BigInt` creation, // Here we use `ToString()` method to check `BigInt` creation,
// therefore also testing it // therefore also testing it
Context("Testing creation of `BigInt`s."); Context("Testing creation of `BigInt`s.");
@ -36,43 +37,33 @@ protected static function TESTS()
Test_SubtractingValues(); Test_SubtractingValues();
} }
protected static function Test_Creating() protected static function Test_Creating() {
{
Issue("`ToString()` doesn't return value `BigInt` was initialized with" @ Issue("`ToString()` doesn't return value `BigInt` was initialized with" @
"a positive `int`."); "a positive `int`.");
TEST_ExpectTrue(__().math.ToBigInt(13524).ToString() == "13524"); TEST_ExpectTrue(__().math.ToBigInt(13524).ToString() == "13524");
TEST_ExpectTrue( TEST_ExpectTrue(__().math.ToBigInt(MaxInt).ToString() == "2147483647");
__().math.ToBigInt(MaxInt).ToString() == "2147483647");
Issue("`ToString()` doesn't return value `BigInt` was initialized with" @ Issue("`ToString()` doesn't return value `BigInt` was initialized with" @
"a positive integer inside `string`."); "a positive integer inside `string`.");
TEST_ExpectTrue( TEST_ExpectTrue(__().math.MakeBigInt_S("2147483647").ToString() == "2147483647");
__().math.MakeBigInt_S("2147483647").ToString()
== "2147483647");
TEST_ExpectTrue( TEST_ExpectTrue(
__().math.MakeBigInt_S("4238756872643464981264982128742389") __().math.MakeBigInt_S("4238756872643464981264982128742389")
.ToString() == "4238756872643464981264982128742389"); .ToString() == "4238756872643464981264982128742389");
Issue("`ToString()` doesn't return value `BigInt` was initialized with" @ Issue("`ToString()` doesn't return value `BigInt` was initialized with a negative `int`.");
"a negative `int`.");
TEST_ExpectTrue(__().math.ToBigInt(-666).ToString() == "-666"); TEST_ExpectTrue(__().math.ToBigInt(-666).ToString() == "-666");
TEST_ExpectTrue( TEST_ExpectTrue(__().math.ToBigInt(-MaxInt).ToString() == "-2147483647");
__().math.ToBigInt(-MaxInt).ToString() == "-2147483647"); TEST_ExpectTrue(__().math.ToBigInt(-MaxInt - 1).ToString() == "-2147483648");
TEST_ExpectTrue(
__().math.ToBigInt(-MaxInt - 1).ToString() == "-2147483648");
Issue("`ToString()` doesn't return value `BigInt` was initialized with" @ Issue("`ToString()` doesn't return value `BigInt` was initialized with" @
"a negative integer inside `string`."); "a negative integer inside `string`.");
TEST_ExpectTrue( TEST_ExpectTrue(__().math.MakeBigInt_S("-2147483648").ToString() == "-2147483648");
__().math.MakeBigInt_S("-2147483648").ToString()
== "-2147483648");
TEST_ExpectTrue( TEST_ExpectTrue(
__().math.MakeBigInt_S("-238473846327894632879097410348127") __().math.MakeBigInt_S("-238473846327894632879097410348127")
.ToString() == "-238473846327894632879097410348127"); .ToString() == "-238473846327894632879097410348127");
} }
protected static function Test_ToText() protected static function Test_ToText() {
{
Issue("`ToText()` doesn't return value `BigInt` was initialized with" @ Issue("`ToText()` doesn't return value `BigInt` was initialized with" @
"a positive integer inside `string`."); "a positive integer inside `string`.");
TEST_ExpectTrue(__().math TEST_ExpectTrue(__().math
@ -96,20 +87,19 @@ protected static function Test_ToText()
.ToString() == "-9827657892365923510176386357863078603212901078175829"); .ToString() == "-9827657892365923510176386357863078603212901078175829");
} }
protected static function Test_AddingValues() protected static function Test_AddingValues() {
{
SubTest_AddingSameSignValues(); SubTest_AddingSameSignValues();
SubTest_AddingDifferentSignValues(); SubTest_AddingDifferentSignValues();
} }
protected static function SubTest_AddingSameSignValues() protected static function SubTest_AddingSameSignValues() {
{
local BigInt main, addition; local BigInt main, addition;
Issue("Two positive `BigInt`s are incorrectly added."); Issue("Two positive `BigInt`s are incorrectly added.");
main = __().math.MakeBigInt_S("927641962323462271784269213864"); main = __().math.MakeBigInt_S("927641962323462271784269213864");
addition = __().math.MakeBigInt_S("16324234842947239847239239"); addition = __().math.MakeBigInt_S("16324234842947239847239239");
main.Add(addition); main.Add(addition);
Log("UMBRA" @ main.ToString());
TEST_ExpectTrue(main.ToString() == "927658286558305219024116453103"); TEST_ExpectTrue(main.ToString() == "927658286558305219024116453103");
main = __().math.MakeBigInt_S("16324234842947239847239239"); main = __().math.MakeBigInt_S("16324234842947239847239239");
addition = __().math.MakeBigInt_S("927641962323462271784269213864"); addition = __().math.MakeBigInt_S("927641962323462271784269213864");
@ -135,8 +125,7 @@ protected static function SubTest_AddingSameSignValues()
TEST_ExpectTrue(main.ToString() == "-1457931745873178552"); TEST_ExpectTrue(main.ToString() == "-1457931745873178552");
} }
protected static function SubTest_AddingDifferentSignValues() protected static function SubTest_AddingDifferentSignValues() {
{
local BigInt main, addition; local BigInt main, addition;
Issue("Negative `BigInt`s is incorrectly added to positive one."); Issue("Negative `BigInt`s is incorrectly added to positive one.");
@ -168,14 +157,12 @@ protected static function SubTest_AddingDifferentSignValues()
TEST_ExpectTrue(main.ToString() == "0"); TEST_ExpectTrue(main.ToString() == "0");
} }
protected static function Test_SubtractingValues() protected static function Test_SubtractingValues() {
{
SubTest_SubtractingSameSignValues(); SubTest_SubtractingSameSignValues();
SubTest_SubtractingDifferentSignValues(); SubTest_SubtractingDifferentSignValues();
} }
protected static function SubTest_SubtractingSameSignValues() protected static function SubTest_SubtractingSameSignValues() {
{
local BigInt main, sub; local BigInt main, sub;
Issue("Two positive `BigInt`s are incorrectly subtracted."); Issue("Two positive `BigInt`s are incorrectly subtracted.");
@ -207,8 +194,7 @@ protected static function SubTest_SubtractingSameSignValues()
TEST_ExpectTrue(main.ToString() == "0"); TEST_ExpectTrue(main.ToString() == "0");
} }
protected static function SubTest_SubtractingDifferentSignValues() protected static function SubTest_SubtractingDifferentSignValues() {
{
local BigInt main, sub; local BigInt main, sub;
Issue("Negative `BigInt`s is incorrectly subtracted from positive one."); Issue("Negative `BigInt`s is incorrectly subtracted from positive one.");
@ -240,8 +226,7 @@ protected static function SubTest_SubtractingDifferentSignValues()
TEST_ExpectTrue(main.ToString() == "-1457931745873178552"); TEST_ExpectTrue(main.ToString() == "-1457931745873178552");
} }
protected static function Test_ToInt() protected static function Test_ToInt() {
{
Issue("Testing conversion for non-overflowing values."); Issue("Testing conversion for non-overflowing values.");
TEST_ExpectTrue(__().math.MakeBigInt_S("0").ToInt() == 0); TEST_ExpectTrue(__().math.MakeBigInt_S("0").ToInt() == 0);
TEST_ExpectTrue(__().math.MakeBigInt_S("-0").ToInt() == 0); TEST_ExpectTrue(__().math.MakeBigInt_S("-0").ToInt() == 0);
@ -264,8 +249,7 @@ protected static function Test_ToInt()
__().math.MakeBigInt_S("-32545657348437563873").ToInt() == -2147483648); __().math.MakeBigInt_S("-32545657348437563873").ToInt() == -2147483648);
} }
defaultproperties defaultproperties {
{
caseGroup = "Math" caseGroup = "Math"
caseName = "BigInt" caseName = "BigInt"
} }
Loading…
Cancel
Save