diff --git a/sources/BaseRealm/API/Math/BigInt.uc b/sources/BaseRealm/API/Math/BigInt.uc index 678f936..66758d6 100644 --- a/sources/BaseRealm/API/Math/BigInt.uc +++ b/sources/BaseRealm/API/Math/BigInt.uc @@ -1,8 +1,6 @@ /** - * 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. + * A simple big integer implementation, mostly to allow Acedia's databases to + * store integers of arbitrary size. * Copyright 2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. @@ -23,6 +21,39 @@ class BigInt extends AcediaObject 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 digits; +}; + +/** + * Used to represent a result of comparison for `BigInt`s with each other. + */ enum BigIntCompareResult { BICR_Less, @@ -30,24 +61,38 @@ enum BigIntCompareResult BICR_Greater }; +// Does stored `BigInt` has negative sign? 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[3] = 3` // `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 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; +// 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; protected function Constructor() { - // Init with zero SetZero(); } @@ -57,7 +102,7 @@ protected function Finalizer() digits.length = 0; } -// ??? +// Auxiliary method to set current value to zero private function BigInt SetZero() { negative = false; @@ -66,8 +111,8 @@ private function BigInt SetZero() return self; } -// Minimal `int` value `-2,147,483,648` is slightly a pain to handle, so just -// use this pre-made constructor for it +// Minimal `int` value `-2,147,483,648` is somewhat of a pain to handle, so +// just use this auxiliary pre-made constructor for it private function BigInt SetMinimalNegative() { 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) { local MathAPI.IntegerDivisionResult divisionResult; @@ -146,7 +217,18 @@ public final function BigInt SetInt(int value) 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 byte nextDigit; @@ -158,14 +240,16 @@ public final function BigInt Set(BaseText value) } parser = value.Parse(); 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.R(); + // Reset current value + digits.length = 0; digits.length = parser.GetRemainingLength(); - /*if (digits.length <= 0) - { - parser.FreeSelf(); - return SetZero(); - }*/ + // Parse new one i = digits.length - 1; while (!parser.HasFinished()) { @@ -183,17 +267,29 @@ public final function BigInt Set(BaseText value) 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; wrapper = __().text.FromStringM(value); - Set(wrapper); + SetDecimal(wrapper); wrapper.FreeSelf(); 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 array otherDigits; @@ -218,17 +314,30 @@ public function BigIntCompareResult _compareModulus(BigInt other) 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) { local BigIntCompareResult resultForModulus; + if (other == none) { + return BICR_Less; + } if (negative && !other.negative) { return BICR_Less; } if (!negative && other.negative) { return BICR_Greater; } - resultForModulus = _compareModulus(other); + resultForModulus = _compareAbsolute(other); if (resultForModulus == BICR_Equal) { return BICR_Equal; } @@ -240,6 +349,70 @@ public function BigIntCompareResult Compare(BigInt other) 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) { local int i; @@ -269,6 +442,8 @@ private function _add(BigInt other) // 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) { local int i; @@ -279,7 +454,7 @@ private function _sub(BigInt other) if (other == none) { return; } - resultForModulus = _compareModulus(other); + resultForModulus = _compareAbsolute(other); if (resultForModulus == BICR_Equal) { SetZero(); @@ -315,8 +490,17 @@ private function _sub(BigInt other) 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) { + if (other == none) { + return self; + } if (negative == other.negative) { _add(other); } @@ -326,6 +510,14 @@ public function BigInt Add(BigInt other) 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) { local BigInt otherObject; @@ -336,6 +528,53 @@ public function BigInt AddInt(int other) 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) { if (negative != other.negative) { @@ -347,21 +586,92 @@ public function BigInt Subtract(BigInt other) 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) { local BigInt otherObject; 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); 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() { + // Handle special case of zero first (it ignores `negative` flag) + if (digits.length == 1 && digits[0] == 0) { + return false; + } 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() { local int i; @@ -394,7 +704,10 @@ public function int ToInt() 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) { local int unsafeDigit; @@ -433,11 +746,21 @@ private function int AddUnsafeDigitToInt(int accumulator) return MaxInt; } +/** + * Converts caller `BigInt` into `Text` representation. + * + * @return `Text` representation of the caller `BigInt`. + */ public function Text ToText() { return ToText_M().IntoText(); } +/** + * Converts caller `BigInt` into `MutableText` representation. + * + * @return `MutableText` representation of the caller `BigInt`. + */ public function MutableText ToText_M() { local int i; @@ -453,6 +776,11 @@ public function MutableText ToText_M() return result; } +/** + * Converts caller `BigInt` into `string` representation. + * + * @return `string` representation of the caller `BigInt`. + */ public function string ToString() { local int i; @@ -467,6 +795,45 @@ public function string ToString() 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 { } \ No newline at end of file diff --git a/sources/BaseRealm/API/Math/MathAPI.uc b/sources/BaseRealm/API/Math/MathAPI.uc index 8b004f1..7f105b5 100644 --- a/sources/BaseRealm/API/Math/MathAPI.uc +++ b/sources/BaseRealm/API/Math/MathAPI.uc @@ -31,6 +31,13 @@ struct IntegerDivisionResult 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) { local BigInt result; @@ -39,20 +46,45 @@ public function BigInt ToBigInt(int 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; 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) { local BigInt result; result = BigInt(_.memory.Allocate(class'BigInt')); - return result.Set_S(value); + return result.SetDecimal_S(value); } /**