/** * Author: dkanus * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore * License: GPL * Copyright 2022-2023 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 . */ class BigInt extends AcediaObject dependson(MathApi); /// A simple big integer implementation. /// /// [`BigInt`]'s main purpose is 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. /// [`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; }; /// Result of comparison for [`BigInt`]s with each other. enum BigIntCompareResult { BICR_Less, BICR_Equal, BICR_Greater }; /// Does stored [`BigInt`] have a negative sign? 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: 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. /// /// # 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 digits; /// 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() { SetZero(); } protected function Finalizer() { negative = false; digits.length = 0; } // Auxiliary method to set current value to zero private function SetZero() { negative = false; digits.length = 1; digits[0] = 0; } // 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 SetMinimalNegative() { negative = true; digits.length = 10; digits[0] = 8; digits[1] = 4; digits[2] = 6; digits[3] = 3; digits[4] = 8; digits[5] = 4; digits[6] = 7; digits[7] = 4; digits[8] = 1; digits[9] = 2; } // Removes unnecessary zeroes from leading digit positions `digits`. // Does not change contained value. private final function TrimLeadingZeroes() { local int i, zeroesToRemove; // Finds 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) { SetZero(); } else { digits.length = digits.length - zeroesToRemove; } } /// Changes current value of [`BigInt`] to given value. public final function Set(BigInt value) { if (value != none) { value.TrimLeadingZeroes(); digits = value.digits; negative = value.negative; } } /// Changes current value of [`BigInt`] to given value. public final function SetInt(int value) { local MathApi.IntegerDivisionResult divisionResult; negative = false; digits.length = 0; 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) { SetMinimalNegative(); return; } else { negative = true; value *= -1; } } if (value == 0) { digits[0] = 0; } else { while (value > 0) { divisionResult = __().math.IntegerDivision(value, 10); value = divisionResult.quotient; digits[digits.length] = divisionResult.remainder; } } TrimLeadingZeroes(); } /// Changes current value of [`BigInt`] to the value, given by decimal representation. /// /// If `none` or 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(BaseText value) { local int i; local byte nextDigit; local bool newNegative; local array newDigits; local Parser parser; local Basetext.Character nextCharacter; if (value == none) { return false; } parser = value.Parse(); newNegative = ParseSign(parser); // Reset to valid state whether sign was consumed or not parser.Confirm(); parser.R(); newDigits.length = parser.GetRemainingLength(); // Parse new one i = newDigits.length - 1; while (!parser.HasFinished()){ // This should not happen, but just in case if (i < 0) { break; } parser.MCharacter(nextCharacter); nextDigit = __().text.CharacterToInt(nextCharacter, 10); if (nextDigit < 0) { return false; } newDigits[i] = nextDigit; i -= 1; } parser.FreeSelf(); digits = newDigits; negative = newNegative; TrimLeadingZeroes(); return true; } // Tries to parse either `+` or `-` and returns `true` iff it parsed `-`. // If neither got parsed, `parser` will enter failed state. // Assumes `parser` isn't `none`. private final function bool ParseSign(Parser parser) { parser.Match(P("-")); negative = parser.Ok(); if (parser.Ok()) { negative = true; } else { parser.R(); 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; wrapper = __().text.FromStringM(value); result = SetDecimal(wrapper); wrapper.FreeSelf(); return result; } // Auxiliary method for comparing two [`BigInt`]s by their absolute value. private function BigIntCompareResult _compareAbsolute(BigInt other) { local int i; local array otherDigits; otherDigits = other.digits; if (digits.length == otherDigits.length) { for (i = digits.length - 1; i >= 0; i -= 1) { if (digits[i] < otherDigits[i]) { return BICR_Less; } if (digits[i] > otherDigits[i]) { return BICR_Greater; } } return BICR_Equal; } if (digits.length < otherDigits.length) { return BICR_Less; } return BICR_Greater; } /// Compares caller [`BigInt`] to [`other`]. /// /// [`BigIntCompareResult`] representing the result of comparison is returned as a result. /// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then /// it means that caller [`BigInt`] is smaller that `other`. /// If argument is `none`, then it is considered to be less ([`BICR_Less`]) than caller [`BigInt`]. 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 = _compareAbsolute(other); if (resultForModulus == BICR_Equal) return BICR_Equal; if (negative && (resultForModulus == BICR_Greater)) return BICR_Less; if (!negative && (resultForModulus == BICR_Less)) return BICR_Less; return BICR_Greater; } /// Compares caller [`BigInt`] to [`other`]. /// /// [`BigIntCompareResult`] representing the result of comparison is returned as a result. /// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then /// 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 a decimal representation of a number. /// /// [`BigIntCompareResult`] representing the result of comparison is returned as a result. /// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then /// it means that caller [`BigInt`] is smaller that `other`. /// If argument is `none` or is an invalid decimal representation (digits only, possibly with /// leading sign, then it is considered to be less ([`BICR_Less`]) than caller [`BigInt`]. public function BigIntCompareResult CompareDecimal(BaseText other) { local BigInt wrapper; local BigIntCompareResult result; wrapper = _.math.MakeBigInt(other); result = Compare(wrapper); _.memory.Free(wrapper); return result; } /// Compares caller [`BigInt`] to a decimal representation of a number. /// /// [`BigIntCompareResult`] representing the result of comparison is returned as a result. /// It describes how caller [`BigInt`] relates to the `other`, e.g. if `BICR_Less` was returned then /// it means that caller [`BigInt`] is smaller that `other`. /// If argument is an invalid decimal representation (digits only, possibly with leading sign, then /// it is considered to be less ([`BICR_Less`]) than caller [`BigInt`]. 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; local byte carry, digitSum; local array otherDigits; if (other == none) { return; } otherDigits = other.digits; if (digits.length < otherDigits.length) { digits.length = otherDigits.length; } else { otherDigits.length = digits.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; } // 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; local int carry, nextDigit; local array minuendDigits, subtrahendDigits; local BigIntCompareResult resultForModulus; if (other == none) { return; } resultForModulus = _compareAbsolute(other); if (resultForModulus == BICR_Equal) { SetZero(); return; } if (resultForModulus == BICR_Less) { negative = !negative; minuendDigits = other.digits; subtrahendDigits = digits; } else { minuendDigits = digits; subtrahendDigits = other.digits; } digits.length = minuendDigits.length; subtrahendDigits.length = minuendDigits.length; carry = 0; for (i = 0; i < digits.length; i += 1) { nextDigit = int(minuendDigits[i]) - int(subtrahendDigits[i]) + carry; if (nextDigit < 0) { nextDigit += 10; carry = -1; } else { carry = 0; } digits[i] = nextDigit; } TrimLeadingZeroes(); } /// Adds another value to the caller [`BigInt`]. /// /// If argument is `none`, then given method does nothing. public function Add(BigInt other) { if (other == none) { return; } if (negative == other.negative) { _add(other); } else { _sub(other); } } /// Adds another value to the caller [`BigInt`]. public function AddInt(int other) { local BigInt otherObject; otherObject = _.math.ToBigInt(other); Add(otherObject); _.memory.Free(otherObject); } /// Adds decimal representation of the number to the caller [`BigInt`]. /// /// If `none` or invalid decimal representation (digits only, possibly with leading sign) is given - /// does nothing. public function AddDecimal(BaseText other) { local BigInt otherObject; if (other == none) { return; } otherObject = _.math.MakeBigInt(other); Add(otherObject); _.memory.Free(otherObject); } /// Adds decimal representation of the number to the caller [`BigInt`]. /// /// If invalid decimal representation (digits only, possibly with leading sign) is given - /// does nothing. public function AddDecimal_S(string other) { local BigInt otherObject; otherObject = _.math.MakeBigInt_S(other); Add(otherObject); _.memory.Free(otherObject); } /// Subtracts another value to the caller [`BigInt`]. /// /// If argument is `none`, then given method does nothing. public function Subtract(BigInt other) { if (negative != other.negative) { _add(other); } else { _sub(other); } } /// Adds another value to the caller [`BigInt`]. public function SubtractInt(int other) { local BigInt otherObject; otherObject = _.math.ToBigInt(other); Subtract(otherObject); _.memory.Free(otherObject); } /// Subtracts decimal representation of the number to the caller [`BigInt`]. /// /// If `none` or invalid decimal representation (digits only, possibly with leading sign) is given - /// does nothing. public function SubtractDecimal(BaseText other) { local BigInt otherObject; if (other == none) { return; } otherObject = _.math.MakeBigInt(other); Subtract(otherObject); _.memory.Free(otherObject); } /// Adds decimal representation of the number to the caller [`BigInt`]. /// /// If invalid decimal representation (digits only, possibly with leading sign) is given - /// does nothing. public function SubtractDecimal_S(string other) { local BigInt otherObject; otherObject = _.math.MakeBigInt_S(other); Subtract(otherObject); _.memory.Free(otherObject); } /// Checks if caller [`BigInt`] is negative. /// /// Returns if stored value is negative and `false` otherwise. /// Zero is not considered negative number. 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. public function int ToInt() { local int i; local int accumulator; local int safeDigitsAmount; if (digits.length <= 0) { return 0; } if (digits.length > DIGITS_IN_MAX_INT) { if (negative) { return (-maxInt - 1); } else { return maxInt; } } // At most `DIGITS_IN_MAX_INT - 1` iterations safeDigitsAmount = Min(DIGITS_IN_MAX_INT - 1, digits.length); for (i = safeDigitsAmount - 1; i >= 0; i -= 1) { accumulator *= 10; accumulator += digits[i]; } if (negative) { accumulator *= -1; } accumulator = AddUnsafeDigitToInt(accumulator); return accumulator; } /// 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; local bool noOverflow; if (digits.length < DIGITS_IN_MAX_INT) { return accumulator; } unsafeDigit = digits[DIGITS_IN_MAX_INT - 1]; // `maxInt` stats with `2`, so if last/unsafe digit is either `0` or `1`, there is no overflow, // otherwise - check rest of the digits noOverflow = (unsafeDigit < 2); if (unsafeDigit == 2) { // Include `maxInt` and `-maxInt-1` (minimal possible value) into an overflow too - this way // we still give a correct result, but do not have to worry about `int`-arithmetic error noOverflow = noOverflow || (negative && (accumulator > -ALMOST_MAX_INT - 1)) || (!negative && (accumulator < ALMOST_MAX_INT)); } if (noOverflow) { if (negative) { accumulator -= unsafeDigit * LAST_DIGIT_ORDER; } else { accumulator += unsafeDigit * LAST_DIGIT_ORDER; } return accumulator; } // Handle overflow if (negative) { return (-maxInt - 1); } return maxInt; } /// Converts caller [`BigInt`] into [`Text`] representation. public function Text ToText() { return ToText_M().IntoText(); } /// Converts caller [`BigInt`] into [`MutableText`] representation. 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; } /// Converts caller [`BigInt`] into [`string`] representation. 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; } /// Restores [`BigInt`] from the [`BigIntData`] value. /// /// This method is created to make an efficient way to store [`BigInt`]. 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`]. public function BigIntData ToData() { local BigIntData result; result.negative = negative; result.digits = digits; return result; } defaultproperties { }