1vanK 3 rokov pred
rodič
commit
eb2031e30a

+ 14 - 0
Source/Samples/55_Clicker/CMakeLists.txt

@@ -0,0 +1,14 @@
+# Copyright (c) 2008-2022 the Urho3D project
+# License: MIT
+
+# Define target name
+set (TARGET_NAME 55_Clicker)
+
+# Define source files
+define_source_files (EXTRA_H_FILES ${COMMON_SAMPLE_H_FILES})
+
+# Setup target with resource copying
+setup_main_executable ()
+
+# Setup test cases
+setup_test ()

+ 149 - 0
Source/Samples/55_Clicker/Clicker.cpp

@@ -0,0 +1,149 @@
+// Copyright (c) 2008-2022 the Urho3D project
+// License: MIT
+
+#include <Urho3D/Core/CoreEvents.h>
+#include <Urho3D/Core/ProcessUtils.h>
+#include <Urho3D/Input/Input.h>
+#include <Urho3D/UI/Font.h>
+#include <Urho3D/UI/Text.h>
+#include <Urho3D/UI/UI.h>
+
+#include "Clicker.h"
+
+#include <Urho3D/DebugNew.h>
+
+// Expands to this example's entry-point
+URHO3D_DEFINE_APPLICATION_MAIN(Clicker)
+
+Clicker::Clicker(Context* context)
+    : Sample(context)
+{
+}
+
+void Clicker::Start()
+{
+    Sample::Start();
+    CreateUI();
+    Sample::InitMouseMode(MM_FREE);
+    SubscribeToEvents();
+}
+
+void Clicker::CreateUI()
+{
+    ResourceCache* cache = GetSubsystem<ResourceCache>();
+    XMLFile* style = cache->GetResource<XMLFile>("UI/DefaultStyle.xml");
+    UIElement* uiRoot = GetSubsystem<UI>()->GetRoot();
+    uiRoot->SetDefaultStyle(style);
+
+    // Text in the center of the screen will initially contain hint, and then score
+    Text* scoreText = uiRoot->CreateChild<Text>("Score");
+    scoreText->SetText("Hold LMB to play.\nClick RMB to upgrade power.");
+    scoreText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 30);
+    scoreText->SetColor(Color::GREEN);
+    scoreText->SetHorizontalAlignment(HA_CENTER);
+    scoreText->SetVerticalAlignment(VA_CENTER);
+
+    Text* powerText = uiRoot->CreateChild<Text>("Power");
+    powerText->SetText("Power: " + power_.ToString());
+    powerText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 30);
+    powerText->SetColor(Color::WHITE);
+    powerText->SetPosition({10, 10});
+}
+
+void Clicker::SubscribeToEvents()
+{
+    SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(Clicker, HandleUpdate));
+    SubscribeToEvent(E_MOUSEBUTTONDOWN, URHO3D_HANDLER(Clicker, HandleMouseButtonDown));
+}
+
+static String ShortNumberRepresentation(const BigInt& value)
+{
+    String str = value.ToString();
+
+    u32 len = str.Length();
+
+    if (len > 45)
+        return str.Substring(0, len - 45) + " quattuordecillion";
+
+    if (len > 42)
+        return str.Substring(0, len - 42) + " tredecillion";
+
+    if (len > 39)
+        return str.Substring(0, len - 39) + " duodecillion";
+
+    if (len > 36)
+        return str.Substring(0, len - 36) + " undecillion";
+
+    if (len > 33)
+        return str.Substring(0, len - 33) + " decillion";
+
+    if (len > 30)
+        return str.Substring(0, len - 30) + " nonillion";
+
+    if (len > 27)
+        return str.Substring(0, len - 27) + " octillion";
+
+    if (len > 24)
+        return str.Substring(0, len - 24) + " septillion";
+
+    if (len > 21)
+        return str.Substring(0, len - 21) + " sextillion";
+
+    if (len > 18)
+        return str.Substring(0, len - 18) + " quintillion";
+
+    if (len > 15)
+        return str.Substring(0, len - 15) + " quadrillion";
+
+    if (len > 12)
+        return str.Substring(0, len - 12) + " trillion";
+
+    if (len > 9)
+        return str.Substring(0, len - 9) + " billion";
+
+    if (len > 6)
+        return str.Substring(0, len - 6) + " million";
+
+    if (len > 3)
+        return str.Substring(0, len - 3) + " thousand";
+
+    return str;
+}
+
+void Clicker::HandleUpdate(StringHash eventType, VariantMap& eventData)
+{
+    using namespace Update;
+
+    float timeStep = eventData[P_TIMESTEP].GetFloat();
+
+    clickDelay -= timeStep;
+
+    Input* input = GetSubsystem<Input>();
+
+    if (input->GetMouseButtonDown(MOUSEB_LEFT) && clickDelay <= 0.f)
+    {
+        score_ = score_ + power_;
+
+        UIElement* uiRoot = GetSubsystem<UI>()->GetRoot();
+        Text* scoreText = static_cast<Text*>(uiRoot->GetChild("Score", false));
+        scoreText->SetText(ShortNumberRepresentation(score_));
+
+        clickDelay = 0.2f;
+    }
+}
+
+void Clicker::HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
+{
+    using namespace MouseButtonDown;
+
+    MouseButton button = (MouseButton)eventData[P_BUTTON].GetUInt();
+    
+    if (button == MOUSEB_RIGHT)
+    {
+        power_ = power_ * 2;
+
+        UIElement* uiRoot = GetSubsystem<UI>()->GetRoot();
+        Text* powerText = static_cast<Text*>(uiRoot->GetChild("Power", false));
+        powerText->SetText("Power: " + ShortNumberRepresentation(power_));
+    }
+}

+ 53 - 0
Source/Samples/55_Clicker/Clicker.h

@@ -0,0 +1,53 @@
+// Copyright (c) 2008-2022 the Urho3D project
+// License: MIT
+
+#pragma once
+
+#include <Urho3D/Math/BigInt.h>
+
+#include "Sample.h"
+
+class Clicker : public Sample
+{
+    URHO3D_OBJECT(Clicker, Sample);
+
+public:
+    /// Construct.
+    explicit Clicker(Context* context);
+
+    /// Setup after engine initialization and before running the main loop.
+    void Start() override;
+
+protected:
+    /// Return XML patch instructions for screen joystick layout for a specific sample app, if any.
+    String GetScreenJoystickPatchString() const override {
+        return
+            "<patch>"
+            "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Hat0']]\">"
+            "        <attribute name=\"Is Visible\" value=\"false\" />"
+            "    </add>"
+            "</patch>";
+    }
+
+private:
+    /// Current score.
+    BigInt score_;
+
+    /// Number of points received per click.
+    BigInt power_{1};
+
+    /// Delay after click.
+    float clickDelay = 0.f;
+
+    /// Construct UI elements.
+    void CreateUI();
+    
+    /// Subscribe to application-wide logic update events.
+    void SubscribeToEvents();
+    
+    /// Handle the logic update event.
+    void HandleUpdate(StringHash eventType, VariantMap& eventData);
+
+    /// Handle the mouse click event.
+    void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData);
+};

+ 3 - 0
Source/Tools/Tests/CMakeLists.txt

@@ -9,3 +9,6 @@ define_source_files (RECURSE GROUP)
 
 # Setup target
 setup_executable (TOOL)
+
+# Setup test cases
+setup_test ()

+ 2 - 10
Source/Tools/Tests/Main.cpp

@@ -1,23 +1,15 @@
 // Copyright (c) 2008-2022 the Urho3D project
 // License: MIT
 
-#include <Urho3D/Core/ProcessUtils.h>
-
-#ifdef WIN32
-#include <windows.h>
-#endif
-
-#include <Urho3D/DebugNew.h>
-
 #include <iostream>
 
-using namespace Urho3D;
-
 void Test_Container_Str();
+void Test_Math_BigInt();
 
 void Run()
 {
     Test_Container_Str();
+    Test_Math_BigInt();
 }
 
 int main(int argc, char* argv[])

+ 87 - 0
Source/Tools/Tests/Math/BigInt.cpp

@@ -0,0 +1,87 @@
+// Copyright (c) 2008-2022 the Urho3D project
+// License: MIT
+
+#include "../ForceAssert.h"
+
+#include <Urho3D/Container/Str.h>
+#include <Urho3D/Math/BigInt.h>
+
+#include <Urho3D/DebugNew.h>
+
+using namespace Urho3D;
+
+void Test_Math_BigInt()
+{
+    // Constructors
+    assert(BigInt().ToString() == "0");
+    assert(BigInt((i32)0x7FFFFFFF).ToString() == "2147483647");
+    assert(BigInt((u32)0x80000000).ToString() == "2147483648");
+    assert(BigInt((u32)0xFFFFFFFF).ToString() == "4294967295");
+    assert(BigInt((i64)0x7FFFFFFFFFFFFFFF).ToString() == "9223372036854775807"); // (i64) need for macOS
+    assert(BigInt((u64)0x8000000000000000).ToString() == "9223372036854775808"); // (u64) need for macOS
+    assert(BigInt((u64)0xFFFFFFFFFFFFFFFF).ToString() == "18446744073709551615"); // (u64) need for macOS
+    assert(BigInt("0").ToString() == "0");
+    assert(BigInt("-0").ToString() == "0");
+    assert(BigInt("3").ToString() == "3");
+    assert(BigInt("-7").ToString() == "-7");
+    assert(BigInt("123456789").ToString() == "123456789");
+    assert(BigInt("-123456789").ToString() == "-123456789");
+    assert(BigInt("123000789").ToString() == "123000789");
+    assert(BigInt("-1230000").ToString() == "-1230000");
+    assert(BigInt("0001230000").ToString() == "1230000");
+    assert(BigInt("-0001230000").ToString() == "-1230000");
+    
+    {
+        BigInt bi{"-99999999999999999999999999999999999999999999999999999999999999999999"
+                  "999999999999999999999999999999999999999999999999999999999999999999999"
+                  "999999999999999999999999999999999999999999999999999999999999999999999"};
+        
+        assert(bi.ToString() ==
+               "-99999999999999999999999999999999999999999999999999999999999999999999"
+               "999999999999999999999999999999999999999999999999999999999999999999999"
+               "999999999999999999999999999999999999999999999999999999999999999999999");
+    }
+
+    // Comparison
+    assert(BigInt("0") == BigInt("-0"));
+    assert(BigInt("10") < BigInt("100"));
+    assert(BigInt("10") > BigInt("-100"));
+    assert(BigInt("-10") > BigInt("-100"));
+
+    // Sum of values with same sign
+    assert((BigInt("0") + BigInt("0")).ToString() == "0");
+    assert((BigInt("000") + BigInt("000")).ToString() == "0");
+    assert((BigInt("1") + BigInt("2")).ToString() == "3");
+    assert((BigInt("1000") + BigInt("200")).ToString() == "1200");
+    assert((BigInt("-1000") + BigInt("-234")).ToString() == "-1234");
+    assert((BigInt("-1000") - BigInt("234")).ToString() == "-1234");
+    assert((BigInt("-1000") - BigInt("0")).ToString() == "-1000");
+    assert((BigInt("9999999999999999999999") + BigInt("9999999999999999999999")).ToString() == "19999999999999999999998");
+    assert((BigInt("9999999999999999999999") + BigInt("1")).ToString() == "10000000000000000000000");
+
+    // Sum of values with opposite sign
+    assert((BigInt("000") - BigInt("000")).ToString() == "0");
+    assert((BigInt("1000") - BigInt("1000")).ToString() == "0");
+    assert((BigInt("1000") - BigInt("234")).ToString() == "766");
+    assert((BigInt("234") - BigInt("1000")).ToString() == "-766");
+    assert((BigInt("1000") - BigInt("0")).ToString() == "1000");
+    assert((BigInt("0") - BigInt("034005")).ToString() == "-34005");
+    assert((BigInt("10000000000000000000000") - BigInt("1")).ToString() == "9999999999999999999999");
+    assert((BigInt("-10000000000000000000000") + BigInt("1")).ToString() == "-9999999999999999999999");
+
+    // Multiply
+    assert((BigInt("0") * BigInt("0")).ToString() == "0");
+    assert((BigInt("1") * BigInt("1")).ToString() == "1");
+    assert((BigInt("1") * BigInt("9999999999999999999999")).ToString() == "9999999999999999999999");
+    assert((BigInt("0") * BigInt("9999999999999999999999")).ToString() == "0");
+    assert((BigInt("10") * BigInt("2")).ToString() == "20");
+    assert((BigInt("-99999") * BigInt("99999")).ToString() == "-9999800001");
+    
+    {
+        BigInt bi1{"-99999999999999999999999999999999999999999999999999999999999999999999"};
+        BigInt bi2{"99999999999999999999999999999999999999999999999999999999999999999999"};
+        String str = (bi1 * bi2).ToString();
+        assert(str == "-99999999999999999999999999999999999999999999999999999999999999999998"
+                      "00000000000000000000000000000000000000000000000000000000000000000001");
+    }
+}

+ 1 - 0
Source/Urho3D/AngelScript/Generated_Includes.h

@@ -118,6 +118,7 @@
 #include "../Input/InputConstants.h"
 #include "../LibraryInfo.h"
 #include "../Math/AreaAllocator.h"
+#include "../Math/BigInt.h"
 #include "../Math/BoundingBox.h"
 #include "../Math/Color.h"
 #include "../Math/Frustum.h"

+ 11 - 0
Source/Urho3D/AngelScript/Generated_Members.h

@@ -494,6 +494,17 @@ template <class T> void RegisterMembers_BiasParameters(asIScriptEngine* engine,
     #endif
 }
 
+// class BigInt | File: ../Math/BigInt.h
+template <class T> void RegisterMembers_BigInt(asIScriptEngine* engine, const char* className)
+{
+    // String BigInt::ToString() const
+    engine->RegisterObjectMethod(className, "String ToString() const", AS_METHODPR(T, ToString, () const, String), AS_CALL_THISCALL);
+
+    #ifdef REGISTER_MEMBERS_MANUAL_PART_BigInt
+        REGISTER_MEMBERS_MANUAL_PART_BigInt();
+    #endif
+}
+
 // struct Billboard | File: ../Graphics/BillboardSet.h
 template <class T> void RegisterMembers_Billboard(asIScriptEngine* engine, const char* className)
 {

+ 3 - 0
Source/Urho3D/AngelScript/Generated_ObjectTypes.cpp

@@ -63,6 +63,9 @@ void ASRegisterGeneratedObjectTypes(asIScriptEngine* engine)
     // struct BiasParameters | File: ../Graphics/Light.h
     engine->RegisterObjectType("BiasParameters", sizeof(BiasParameters), asOBJ_VALUE | asGetTypeTraits<BiasParameters>() | asOBJ_POD | asOBJ_APP_CLASS_ALLFLOATS);
 
+    // class BigInt | File: ../Math/BigInt.h
+    // Not registered because have @nobind mark
+
     // struct Billboard | File: ../Graphics/BillboardSet.h
     engine->RegisterObjectType("Billboard", 0, asOBJ_REF | asOBJ_NOCOUNT);
 

+ 347 - 0
Source/Urho3D/Math/BigInt.cpp

@@ -0,0 +1,347 @@
+// Copyright (c) 2008-2022 the Urho3D project
+// License: MIT
+
+#include "BigInt.h"
+
+#include "../Core/StringUtils.h"
+
+#include "../DebugNew.h"
+
+namespace Urho3D
+{
+
+#if true
+    static constexpr BigInt::Digit BASE = 1000'000'000; // 10^9
+
+    // Number of decimal digits in each magnitude_ element
+    static constexpr i32 BASE_DIGITS = 9;
+#else // For tests
+    static constexpr BigInt::Digit BASE = 10;
+    static constexpr i32 BASE_DIGITS = 1;
+#endif
+
+// Compare magnitudes
+static bool FirstIsLess(const Vector<BigInt::Digit>& first, const Vector<BigInt::Digit>& second)
+{
+    if (first.Size() != second.Size())
+        return first.Size() < second.Size();
+
+    for (i32 i = (i32)first.Size() - 1; i >= 0; --i)
+    {
+        if (first[i] != second[i])
+            return first[i] < second[i];
+    }
+
+    return false; // first == b
+}
+
+// a + b
+static Vector<BigInt::Digit> SumMagnitudes(const Vector<BigInt::Digit>& a, const Vector<BigInt::Digit>& b)
+{
+    Vector<BigInt::Digit> ret;
+
+    u32 maxSize = Max(a.Size(), b.Size());
+    ret.Resize(maxSize, 0);
+
+    for (u32 i = 0; i < maxSize; ++i)
+    {
+        BigInt::Digit a_element = i < a.Size() ? a[i] : 0;
+        BigInt::Digit b_element = i < b.Size() ? b[i] : 0;
+        BigInt::Digit sum = a_element + b_element;
+
+        ret[i] += sum; // ret[i] can be not zero
+
+        while (ret[i] >= BASE)
+        {
+            ret[i] -= BASE; // Use div & mod instead loop?
+
+            if (i + 1 < ret.Size())
+                ++ret[i + 1];
+            else
+                ret.Push(1);
+        }
+    }
+
+    return ret;
+}
+
+// a - b (a must be >= b)
+static Vector<BigInt::Digit> DiffMagnitudes(const Vector<BigInt::Digit>& a, const Vector<BigInt::Digit>& b)
+{
+    Vector<BigInt::Digit> ret;
+    ret.Resize(a.Size(), 0);
+
+    for (u32 i = 0; i < a.Size(); ++i)
+    {
+        BigInt::Digit a_element = a[i];
+        BigInt::Digit b_element = i < b.Size() ? b[i] : 0;
+        BigInt::Digit diff = a_element - b_element;
+
+        ret[i] += diff; // ret[i] can be not zero
+
+        while (ret[i] < 0)
+        {
+            ret[i] += BASE;
+
+            assert(i + 1 < ret.Size());
+            --ret[i + 1];
+        }
+    }
+
+    // Remove leading zeros
+    while (ret.Size() >= 2 && ret.Back() == 0)
+        ret.Pop();
+
+    return ret;
+}
+
+BigInt::BigInt()
+{
+    // Init as zero
+    positive_ = true;
+    magnitude_.Resize(1, 0);
+}
+
+BigInt::BigInt(const String& str)
+{
+    if (str.Empty())
+    {
+        // Init as zero
+        positive_ = true;
+        magnitude_.Resize(1, 0);
+        return;
+    }
+
+    i32 firstDigitPos;
+
+    if (str[0] == '-')
+    {
+        positive_ = false;
+        firstDigitPos = 1;
+    }
+    else if (str[0] == '+')
+    {
+        positive_ = true;
+        firstDigitPos = 1;
+    }
+    else
+    {
+        positive_ = true;
+        firstDigitPos = 0;
+    }
+
+    // Skip leading zeros
+    for (i32 i = firstDigitPos; i < (i32)str.Length(); ++i)
+    {
+        if (str[i] != '0')
+            break;
+
+        ++firstDigitPos;
+    }
+
+    i32 lastDigitPos = -1;
+    for (i32 i = firstDigitPos; i < (i32)str.Length(); ++i)
+    {
+        if (!IsDigit(str[i]))
+            break;
+        
+        lastDigitPos = i;
+    }
+
+    if (lastDigitPos == -1)
+    {
+        // Init as zero
+        positive_ = true;
+        magnitude_.Resize(1, 0);
+        return;
+    }
+
+    i32 numDigits = lastDigitPos - firstDigitPos + 1;
+    magnitude_.Reserve(numDigits / BASE_DIGITS + 1);
+
+    // Retrieve chunks of 9 digits in reverse order and save to single i32
+    i32 i = lastDigitPos - BASE_DIGITS + 1;
+    for (; i > firstDigitPos; i -= BASE_DIGITS)
+    {
+        String chunk = str.Substring(i, BASE_DIGITS);
+        magnitude_.Push(ToInt64(chunk)); // To Digit
+    }
+
+    String lastChunk = str.Substring(firstDigitPos, BASE_DIGITS + i - firstDigitPos);
+    magnitude_.Push(ToInt64(lastChunk)); // To Digit
+}
+
+BigInt::BigInt(i32 value)
+{
+    positive_ = (value >= 0);
+    value = Urho3D::Abs(value);
+
+    while (value != 0)
+    {
+        Digit mod = value % BASE;
+        magnitude_.Push(mod);
+        value /= BASE;
+    }
+}
+
+BigInt::BigInt(i64 value)
+{
+    positive_ = (value >= 0);
+    value = Urho3D::Abs(value);
+
+    while (value != 0)
+    {
+        Digit mod = value % BASE;
+        magnitude_.Push(mod);
+        value /= BASE;
+    }
+}
+
+BigInt::BigInt(u32 value)
+{
+    positive_ = true;
+
+    while (value != 0)
+    {
+        Digit mod = value % BASE;
+        magnitude_.Push(mod);
+        value /= BASE;
+    }
+}
+
+BigInt::BigInt(u64 value)
+{
+    positive_ = true;
+
+    while (value != 0)
+    {
+        Digit mod = (Digit)(value % BASE);
+        magnitude_.Push(mod);
+        value /= BASE;
+    }
+}
+
+bool BigInt::operator <(const BigInt& rhs) const
+{
+    if (positive_ != rhs.positive_)
+        return !positive_;
+
+    if (positive_)
+        return FirstIsLess(magnitude_, rhs.magnitude_);
+    else
+        return FirstIsLess(rhs.magnitude_, magnitude_);
+}
+
+String BigInt::ToString() const
+{
+    assert(magnitude_.Size() > 0);
+
+    String ret;
+
+    if (!positive_)
+        ret = "-";
+
+    // Beginning of the number without leading zeros
+    i32 i = (i32)magnitude_.Size() - 1;
+    ret += String(magnitude_[i]);
+    --i;
+
+    // Another chunks with leading zeros
+    for (; i >= 0; --i)
+    {
+        String str(magnitude_[i]);
+        
+        if (str.Length() < BASE_DIGITS)
+        {
+            String zeros('0', BASE_DIGITS - str.Length());
+            str = zeros + str;
+        }
+
+        ret += str;
+    }
+
+    return ret;
+}
+
+BigInt BigInt::operator +(const BigInt& rhs) const
+{
+    BigInt ret;
+
+    if (positive_ == rhs.positive_)
+    {
+        ret.positive_ = positive_;
+        ret.magnitude_ = SumMagnitudes(magnitude_, rhs.magnitude_);
+    }
+    else
+    {
+        if (FirstIsLess(magnitude_, rhs.magnitude_))
+        {
+            ret.positive_ = rhs.positive_;
+            ret.magnitude_ = DiffMagnitudes(rhs.magnitude_, magnitude_);
+        }
+        else
+        {
+            ret.positive_ = positive_;
+            ret.magnitude_ = DiffMagnitudes(magnitude_, rhs.magnitude_);
+        }
+    }
+    
+    return ret;
+}
+
+BigInt BigInt::operator -(const BigInt& rhs) const
+{
+    BigInt ret;
+
+    if (positive_ != rhs.positive_)
+    {
+        ret.positive_ = positive_;
+        ret.magnitude_ = SumMagnitudes(magnitude_, rhs.magnitude_);
+    }
+    else
+    {
+        if (FirstIsLess(magnitude_, rhs.magnitude_))
+        {
+            ret.positive_ = !rhs.positive_;
+            ret.magnitude_ = DiffMagnitudes(rhs.magnitude_, magnitude_);
+        }
+        else
+        {
+            ret.positive_ = positive_;
+            ret.magnitude_ = DiffMagnitudes(magnitude_, rhs.magnitude_);
+        }
+    }
+
+    return ret;
+}
+
+BigInt BigInt::operator *(const BigInt& rhs) const
+{
+    BigInt ret;
+    ret.magnitude_.Resize(magnitude_.Size() + rhs.magnitude_.Size(), 0);
+
+    if (positive_ == rhs.positive_)
+        ret.positive_ = true;
+    else
+        ret.positive_ = false;
+
+    for (u32 this_index = 0; this_index < magnitude_.Size(); ++this_index)
+    {
+        for (u32 rhs_index = 0; rhs_index < rhs.magnitude_.Size(); ++rhs_index)
+            ret.magnitude_[this_index + rhs_index] += magnitude_[this_index] * rhs.magnitude_[rhs_index];
+    }
+
+    for (u32 i = 0; i < ret.magnitude_.Size() - 1; ++i)
+    {
+        ret.magnitude_[i + 1] += ret.magnitude_[i] / BASE;
+        ret.magnitude_[i] %= BASE;
+    }
+
+    // Remove leading zeros
+    while (ret.magnitude_.Size() >= 2 && ret.magnitude_.Back() == 0)
+        ret.magnitude_.Pop();
+
+    return ret;
+}
+
+} // namespace Urho3D

+ 49 - 0
Source/Urho3D/Math/BigInt.h

@@ -0,0 +1,49 @@
+// Copyright (c) 2008-2022 the Urho3D project
+// License: MIT
+
+#pragma once
+
+#include "../Container/Pair.h"
+#include "../Container/Str.h"
+#include "../Container/Vector.h"
+#include "MathDefs.h"
+
+namespace Urho3D
+{
+
+/// @nobind
+class URHO3D_API BigInt
+{
+public:
+    /// i64 can conatins 10^9 * 10^9.
+    using Digit = i64;
+
+private:
+    /// Sign.
+    bool positive_;
+
+    /// Array of digits with base 10^9 and reverse order (each element contains value in range [0 .. BASE-1]).
+    Vector<Digit> magnitude_;
+
+public:
+    BigInt();
+    BigInt(const String& str);
+    BigInt(i32 value);
+    BigInt(i64 value);
+    BigInt(u32 value);
+    BigInt(u64 value);
+
+    bool operator ==(const BigInt& rhs) const { return positive_ == rhs.positive_ && magnitude_ == rhs.magnitude_; }
+    bool operator !=(const BigInt& rhs) const { return positive_ != rhs.positive_ || magnitude_ != rhs.magnitude_; }
+
+    bool operator <(const BigInt& rhs) const;
+    bool operator >(const BigInt& rhs) const { return rhs < *this; }
+
+    BigInt operator +(const BigInt& rhs) const;
+    BigInt operator -(const BigInt& rhs) const;
+    BigInt operator *(const BigInt& rhs) const;
+
+    String ToString() const;
+};
+
+} // namespace Urho3D