From 73698364dbe29e81c279b7b903fd22e88345696f Mon Sep 17 00:00:00 2001 From: 1vanK <1vanK@users.noreply.github.com> Date: Wed, 13 Mar 2024 06:45:28 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BF=D1=80=D0=B5=D1=81=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8?= =?UTF-8?q?=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 15 + .gitattributes | 4 + .github/workflows/tester.yml | 120 ++++++ .gitignore | 6 + CMakeLists.txt | 67 +++ big_int.cpp | 802 +++++++++++++++++++++++++++++++++++ big_int.hpp | 125 ++++++ docs/1_basics.md | 134 ++++++ docs/1_basics_add_1.cpp | 83 ++++ docs/1_basics_add_2.cpp | 82 ++++ docs/1_basics_div.cpp | 219 ++++++++++ docs/1_basics_mul_1.cpp | 119 ++++++ docs/1_basics_mul_2.cpp | 147 +++++++ docs/1_basics_sub_1.cpp | 153 +++++++ docs/1_basics_sub_2.cpp | 150 +++++++ docs/2_div_len.cpp | 166 ++++++++ docs/2_div_len.md | 64 +++ docs/3_brute_force_div.md | 87 ++++ docs/3_brute_force_div_1.cpp | 237 +++++++++++ docs/3_brute_force_div_2.cpp | 259 +++++++++++ docs/4_long_div.md | 176 ++++++++ docs/4_long_div_1.cpp | 299 +++++++++++++ docs/4_long_div_2.cpp | 332 +++++++++++++++ docs/4_long_div_3.cpp | 344 +++++++++++++++ docs/4_long_div_4.cpp | 349 +++++++++++++++ docs/libs.md | 38 ++ docs/markdown.md | 28 ++ docs/names.md | 5 + force_assert.hpp | 16 + license.txt | 21 + readme.md | 11 + tester.cpp | 307 ++++++++++++++ 32 files changed, 4965 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/tester.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 big_int.cpp create mode 100644 big_int.hpp create mode 100644 docs/1_basics.md create mode 100644 docs/1_basics_add_1.cpp create mode 100644 docs/1_basics_add_2.cpp create mode 100644 docs/1_basics_div.cpp create mode 100644 docs/1_basics_mul_1.cpp create mode 100644 docs/1_basics_mul_2.cpp create mode 100644 docs/1_basics_sub_1.cpp create mode 100644 docs/1_basics_sub_2.cpp create mode 100644 docs/2_div_len.cpp create mode 100644 docs/2_div_len.md create mode 100644 docs/3_brute_force_div.md create mode 100644 docs/3_brute_force_div_1.cpp create mode 100644 docs/3_brute_force_div_2.cpp create mode 100644 docs/4_long_div.md create mode 100644 docs/4_long_div_1.cpp create mode 100644 docs/4_long_div_2.cpp create mode 100644 docs/4_long_div_3.cpp create mode 100644 docs/4_long_div_4.cpp create mode 100644 docs/libs.md create mode 100644 docs/markdown.md create mode 100644 docs/names.md create mode 100644 force_assert.hpp create mode 100644 license.txt create mode 100644 readme.md create mode 100644 tester.cpp diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4f6de69 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# Заставляем VS и другие IDE сохранять файлы в кодировке UTF-8 +# https://editorconfig.org + +# В родительских папках .editorconfig искаться не будет +root = true + +[*] +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +# Не меняет формат концов строк. Может возникнуть конфликт с настройками git +#end_of_line = lf diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..98e890e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Автоматически нормализуем концы строк, если у пользователя не настроен параметр autocrlf +# https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings +# https://www.aleksandrhovhannisyan.com/blog/crlf-vs-lf-normalizing-line-endings-in-git/ +* text=auto diff --git a/.github/workflows/tester.yml b/.github/workflows/tester.yml new file mode 100644 index 0000000..1e166c4 --- /dev/null +++ b/.github/workflows/tester.yml @@ -0,0 +1,120 @@ +# Copyright (c) the Dviglo project +# License: MIT + +name: Tester + +on: + push: + pull_request: + +jobs: + windows: + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + compiler: [vs, mingw] + build_type: [debug, release] + + name: windows-${{ matrix.compiler }}-${{ matrix.build_type }} + + steps: + - name: Устанавливаем MinGW + if: matrix.compiler == 'mingw' + uses: msys2/setup-msys2@v2 + with: + update: true + install: mingw-w64-x86_64-toolchain + + - name: Добавляем в PATH путь к MinGW + if: matrix.compiler == 'mingw' + shell: bash + run: echo "${RUNNER_TEMP}/msys64/mingw64/bin" >> $GITHUB_PATH + + - name: Скачиваем репозиторий + uses: actions/checkout@v4 + with: + fetch-depth: 0 + path: repo + + - name: Генерируем проекты + shell: bash + run: | + args=(repo -B build) + + if [ "${{ matrix.compiler }}" == "vs" ] + then + args+=(-G "Visual Studio 17 2022") + else + args+=(-G "MinGW Makefiles") + args+=(-D CMAKE_BUILD_TYPE=${{ matrix.build_type }}) + fi + + cmake "${args[@]}" + + - name: Компилируем + shell: bash + run: | + args=(--build build) + + if [ "${{ matrix.compiler }}" == "vs" ] + then + args+=(--config ${{ matrix.build_type }}) + fi + + cmake "${args[@]}" + + - name: CTest + shell: bash + run: | + args=(--verbose --test-dir build --timeout 60) + + if [ "${{ matrix.compiler }}" == "vs" ] + then + args+=(-C ${{ matrix.build_type }}) + fi + + ctest "${args[@]}" + + linux: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + compiler: + - { + id: gcc, + c: gcc-13, + cxx: g++-13, + } + - { + id: clang, + c: clang-13, + cxx: clang++-13, + } + build_type: [debug, release] + + name: linux-${{ matrix.compiler.id }}-${{ matrix.build_type }} + + steps: + - name: Скачиваем репозиторий + uses: actions/checkout@v4 + with: + fetch-depth: 0 + path: repo + + - name: Генерируем проекты + run: | + cmake repo -B build -G "Unix Makefiles" \ + -D CMAKE_C_COMPILER=${{ matrix.compiler.c }} -D CMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} \ + -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} + + - name: Компилируем + run: | + cmake --build build + + - name: CTest + run: | + ctest --verbose --test-dir build --timeout 60 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55086dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# https://git-scm.com/docs/gitignore +# https://www.atlassian.com/ru/git/tutorials/saving-changes/gitignore + +# Игнорируем папки, которые создаёт VS Code +/.vscode/ +/build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..121342c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,67 @@ +# Copyright (c) the Dviglo project +# License: MIT + +# Указываем минимальную версию CMake +cmake_minimum_required(VERSION 3.16) + +# Название проекта +project(big_int) + +# Название таргета +set(target_name tester) + +# Версия стандарта C++ +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Если используется одноконфигурационный генератор и конфигурация не указана +if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) + # то конфигурацией по умолчанию будет Release + set(CMAKE_BUILD_TYPE Release) + # Нельзя оставлять переменную CMAKE_BUILD_TYPE пустой (подробности в Dviglo2D) +endif() + +# Указываем Студии на то, что исходники в кодировке UTF-8. +# Это позволяет писать U'🍌'. У других компиляторов, похоже, нет с этим проблем +# https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2295r6.pdf +if(MSVC) + add_compile_options(/utf-8) +endif() + +# Выводим больше предупреждений +if(MSVC) + add_compile_options(/W4) +else() + add_compile_options(-Wall -Wextra -Wpedantic) + + if(NOT MINGW) + add_compile_options(-fsanitize=undefined) + add_link_options(-fsanitize=undefined) + endif() +endif() + +# Создаём список файлов +file(GLOB source_files *.cpp *.hpp) + +# Создаём приложение +add_executable(${target_name} ${source_files}) + +# Отладочная версия приложения будет иметь суффикс _d +set_property(TARGET ${target_name} PROPERTY DEBUG_POSTFIX _d) + +# Заставляем VS отображать дерево каталогов +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${source_files}) + +# Включаем поддержку папок в IDE +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# Включаем тестирование +enable_testing() + +# Добавляем приложение в список тестируемых +add_test(NAME ${target_name} COMMAND ${target_name}) + +# В Visual Studio таргет будет назначен стартовым вместо ALL_BUILD, +# чтобы потом не делать это вручную при отладке приложения +set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${target_name}) diff --git a/big_int.cpp b/big_int.cpp new file mode 100644 index 0000000..af1b2fd --- /dev/null +++ b/big_int.cpp @@ -0,0 +1,802 @@ +// Copyright (c) the Dviglo project +// License: MIT + +#include "big_int.hpp" + +#include +#include +#include +#include + +using namespace std; + + +namespace dviglo +{ + +constexpr uint32_t base = BigInt::base; +constexpr uint32_t chunk_length = BigInt::chunk_length; +using Digit = BigInt::Digit; + +// Тип удвоенной (double) длины. Умещает квадрат цифры +using DDigit = uint64_t; + +/// Определяет, меньше ли первый модуль +static bool first_is_less(const vector& first, const vector& second) +{ + // Проверяем, что нет ведущих нулей + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + // В модулях не должно быть ведущих нулей + if (first.size() != second.size()) + return first.size() < second.size(); // Если модуль короче, значит он меньше + + // Сравниваем разряды, начиная с конца (со старших разрядов) + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; // first == second +} + +/// Складывает модули чисел столбиком +static vector add_magnitudes(const vector& a, const vector& b) +{ + // Выясняем, какой модуль длиннее + const vector& long_mag = a.size() >= b.size() ? a : b; + const vector& short_mag = a.size() < b.size() ? a : b; + + vector ret; + + // Длина результата равна long_mag.size() или long_mag.size() + 1 + ret.reserve(long_mag.size() + 1); + + // Перенос в следующий столбец + uint32_t carry = 0; // Всегда 0 или 1 + + // Складываем цифры модулей, начиная с младших разрядов + for (size_t i = 0; i < short_mag.size(); ++i) + { + // uint32_t хватает для хранения 999'999'999 + 999'999'999 + 1 + uint32_t sum = long_mag[i] + short_mag[i] + carry; + + carry = sum / base; + assert(carry == 0 || carry == 1); + ret.push_back(sum % base); + } + + // Оставшиеся цифры более длинного модуля + for (size_t i = short_mag.size(); i < long_mag.size(); ++i) + { + uint32_t sum = long_mag[i] + carry; + carry = sum / base; + assert(carry == 0 || carry == 1); + ret.push_back(sum % base); + } + + if (carry) + ret.push_back(1); + + assert(ret.size() == long_mag.size() || long_mag.size() + 1); + + return ret; +} + +/// Вычитает модули чисел столбиком. Уменьшаемое minuend должно быть >= вычитаемого subtrahend +static vector sub_magnitudes(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size(), 0); + + // Заём из следующего столбца + uint32_t borrow = 0; // Всегда 0 или 1 + + // Вычитаем цифры модулей, начиная с младших разрядов + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + + // Если занимали на предыдущем шаге, то нужно вычесть на 1 больше + Digit subtrahend_digit = subtrahend[i] + borrow; + + // Определяем, нужен ли заём на данном шаге + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + + // Занимаем из старшего разряда, если нужно + minuend_digit += base * borrow; + + ret[i] = minuend_digit - subtrahend_digit; + } + + // Оставшиеся цифры minuend + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + // Убираем ведущие нули + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +/// Перемножает модули чисел столбиком. +/// Смотрите "Умножение столбиком и смена base" в туторе +static vector mul_magnitudes(const vector& a, const vector& b) +{ + vector ret; + ret.resize(a.size() + b.size(), 0); + + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + DDigit v = ret[ret_index] + (DDigit)a[a_index] * b[b_index] + carry; + assert(v <= (DDigit)base * base - 1); + carry = Digit(v / base); + assert(carry < base); + ret[ret_index] = v % base; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + // Убираем ведущие нули + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +/// Без угадываний делит модуль длинного числа на цифру (столбиком). +/// Если знаменатель == 0, возвращает {0, 0} +static pair, vector> div_by_digit(const vector& numerator, Digit denominator) +{ + // Если числитель или знаменатель == 0 + if (numerator == vector{0} || denominator == 0) + return {vector{0}, vector{0}}; + + // Если числитель меньше знаменателя + if (first_is_less(numerator, vector{denominator})) + return {vector{0}, numerator}; + + // Неполное частное + vector quotient; + quotient.reserve(numerator.size()); + + // Текущий кусок числителя (и одновременно остаток) + DDigit chunk = 0; + + // Извлекаем цифры числителя, начиная с конца (со старших разрядов) + for (size_t i = numerator.size() - 1; i != size_t(-1); --i) + { + chunk = chunk * base + numerator[i]; + + // Делим кусок на знаменатель и получаем очередную цифру результата + assert(chunk / denominator < base); + Digit digit = Digit(chunk / denominator); + + // Добавляем цифру к результату + quotient.push_back(digit); + + // chunk %= denominator + chunk -= (DDigit)digit * denominator; + assert(chunk < base); + } + + // Мы добавляли цифры в конец вектора, а не в начало, поэтому реверсируем + reverse(quotient.begin(), quotient.end()); + + // Убираем ведущие нули + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + return {quotient, {(Digit)chunk}}; // В chunk находится остаток +} + +#ifndef NDEBUG +/// Отбрасывает младший разряд +static vector div_by_base(const vector& magnitude) +{ + if (magnitude.size() == 1) // Модуль < base + return vector{0}; + + // Отбрасываем цифру слева + return vector(magnitude.cbegin() + 1, magnitude.cend()); +} +#endif + +/// Делит кусок числителя на знаменатель. +/// Кусок и знаменатель таковы, что в результате всегда одна цифра. +/// К тому же аргументы должны быть нормализованы (старшая цифра знаменателя >= base / 2) +static Digit div_chunk(const vector& chunk, const vector& denominator) +{ + if (first_is_less(chunk, denominator)) + return 0; + + assert(chunk.size() == denominator.size() // Длина куска равна длине знаменателя + || (chunk.size() == denominator.size() + 1 // Или кусок длиннее знаменателя на 1 цифру + && first_is_less(div_by_base(chunk), denominator))); // И эта цифра не лишняя + + // Когда в знаменателе одна цифра, используется div_by_digit() + assert(denominator.size() >= 2); + + // Должно быть нормализовано + assert(denominator.back() >= base / 2); + + // Две старшие цифры куска. + // Если длина куска равна длине знаменателя, то старшая цифра будет 0 + DDigit chunk_2_digits = chunk.back(); + if (chunk.size() != denominator.size()) + chunk_2_digits = chunk_2_digits * base + chunk[chunk.size() - 2]; + + // Третья цифра куска + DDigit chunk_digit_3 = (chunk.size() == denominator.size()) + ? chunk[chunk.size() - 2] // Старшая цифра куска - 0, но его нет в массиве + : chunk[chunk.size() - 3]; + + // Старшая цифра знаменателя + DDigit denominator_digit_1 = denominator.back(); + + // Вторая цифра знаменателя + DDigit denominator_digit_2 = denominator[denominator.size() - 2]; + + // Делим две старшие цифры куска на старшую цифру знаменателя + DDigit digit = chunk_2_digits / denominator_digit_1; + + // Остаток может быть больше base + DDigit remainder = chunk_2_digits - digit * denominator_digit_1; + + // Уменьшаем цифру, если она точно слишком большая + if (digit == base || digit * denominator_digit_2 > remainder * base + chunk_digit_3) + { + --digit; + remainder += denominator_digit_1; + + if (remainder < base && (digit == base || digit * denominator_digit_2 > remainder * base + chunk_digit_3)) + --digit; + } + + assert(digit < base); + + // Теперь проверяем, нет ли промаха на 1 + if (first_is_less(chunk, mul_magnitudes(vector{(Digit)digit}, denominator))) + --digit; + + assert(!first_is_less(chunk, mul_magnitudes(vector{(Digit)digit}, denominator))); + + return (Digit)digit; +} + +/// Возвращает неполное частное и остаток (делит столбиком). +/// Если знаменатель == 0, возвращает {0, 0} +static pair, vector> div_mod_magnitudes(const vector& numerator, const vector& denominator) +{ + // Если в знаменателе одна цифра + if (denominator.size() == 1) + return div_by_digit(numerator, denominator[0]); + + // Если числитель или знаменатель == 0 + if (numerator == vector{0} || denominator == vector{0}) + return {vector{0}, vector{0}}; + + // Если числитель меньше знаменателя + if (first_is_less(numerator, denominator)) + return {vector{0}, numerator}; + + // Нормализующий множитель + Digit scale = base / (denominator.back() + 1); + assert(scale <= base / 2); // Старшая цифра denominator не может быть 0, т.е. минимум 1 + + // Нормализованный числитель + vector norm_numerator = (scale == 1) + ? numerator + : mul_magnitudes(vector{scale}, numerator); + + // Нормализованный знаменатель + vector norm_denominator = (scale == 1) + ? denominator + : mul_magnitudes(vector{scale}, denominator); + + // Неполное частное + vector quotient; + quotient.reserve(norm_numerator.size() - norm_denominator.size() + 1); + + // Текущий кусок числителя (и одновременно остаток) + vector chunk; + quotient.reserve(norm_denominator.size() + 1); + + // Копируем цифры числителя, начиная с конца (со старших разрядов) + for (size_t i = norm_numerator.size() - 1; i != size_t(-1); --i) + { + // Копируем очередную цифру числителя в начало куска (так как цифры хранятся в обратном порядке) + chunk.insert(chunk.begin(), norm_numerator[i]); // chunk = chunk * base + norm_numerator[i] + + // Убираем ведущие нули (когда chunk поделился без остатка и стал равен 0, а потом добавили 0) + while (chunk.size() > 1 && chunk.back() == 0) + chunk.pop_back(); + + // Делим кусок на знаменатель и получаем очередную цифру результата + Digit digit = div_chunk(chunk, norm_denominator); + + // Добавляем цифру к результату + quotient.push_back(digit); + + // кусок -= цифра * знаменатель ⇔ кусок %= знаменатель + chunk = sub_magnitudes(chunk, mul_magnitudes(vector{digit}, norm_denominator)); + } + + // Мы добавляли цифры в конец вектора, а не в начало, поэтому реверсируем + reverse(quotient.begin(), quotient.end()); + + // Убираем ведущие нули + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + // Денормализуем остаток + if (scale != 1) + { + pair, vector> denorm_chunk = div_by_digit(chunk, scale); + assert(denorm_chunk.second == vector{0}); // Должно поделиться без остатка + chunk = denorm_chunk.first; + } + + return {quotient, chunk}; // В chunk находится остаток +} + +/// Генерирует случайное число из диапазона [min, max] (включительно) +uint32_t generate_random(uint32_t min, uint32_t max) +{ + // Используем random_device только для генерации seed, так как он медленный + static random_device device; + static uint32_t random_seed = device(); + static mt19937 generator(random_seed); + + uniform_int_distribution dist(min, max); + return dist(generator); +} + +BigInt BigInt::generate(size_t length) +{ + vector magnitude(length); + + // Старший разряд не должен быть 0 + magnitude[length - 1] = generate_random(1, BigInt::base - 1); + + // Остальные разряды могут быть 0 + for (size_t i = 0; i < length - 1; ++i) + magnitude[i] = generate_random(0, BigInt::base - 1); + + BigInt ret; + ret.positive_ = true; + ret.magnitude_ = magnitude; + + return ret; +} + +BigInt::BigInt() +{ + positive_ = true; + magnitude_.push_back(0); +} + +BigInt::BigInt(int32_t value) +{ + positive_ = (value >= 0); + + // Сперва преобразуем в int64_t, чтобы не было UB в std::abs(-2147483648) + uint64_t val = (uint64_t)std::abs((int64_t)value); + + while (val != 0) + { + Digit mod = val % base; + magnitude_.push_back(mod); + val /= base; + } + + if (!magnitude_.size()) // value == 0 + magnitude_.push_back(0); +} + +BigInt::BigInt(uint32_t value) +{ + positive_ = true; + + while (value != 0) + { + Digit mod = value % base; + magnitude_.push_back(mod); + value /= base; + } + + if (!magnitude_.size()) // value == 0 + magnitude_.push_back(0); +} + +/// Обходит undefined behavior в std::abs() +static uint64_t fixed_abs(int64_t value) +{ + static_assert(is_same_v + && is_same_v); + + if (value == -9223372036854775807 - 1) + return 9223372036854775808u; + else + return (uint64_t)std::abs(value); +} + +BigInt::BigInt(int64_t value) +{ + positive_ = (value >= 0); + + uint64_t val = fixed_abs(value); + + while (val != 0) + { + Digit mod = val % base; + magnitude_.push_back(mod); + val /= base; + } + + if (!magnitude_.size()) // value == 0 + magnitude_.push_back(0); +} + +BigInt::BigInt(uint64_t value) +{ + positive_ = true; + + while (value != 0) + { + Digit mod = (Digit)(value % base); + magnitude_.push_back(mod); + value /= base; + } + + if (!magnitude_.size()) // value == 0 + magnitude_.push_back(0); +} + +static uint32_t to_uint32_t(const string& chunk) +{ + unsigned long long val = stoull(chunk); + assert(val <= base); + return (uint32_t)val; +} + +BigInt::BigInt(const string& str) +{ + if (str.empty()) + { + // Инцициализируем нулём + positive_ = true; + magnitude_.push_back(0); + return; + } + + size_t first_digit_pos; + + if (str[0] == '-') + { + positive_ = false; + first_digit_pos = 1; + } + else if (str[0] == '+') + { + positive_ = true; + first_digit_pos = 1; + } + else + { + positive_ = true; + first_digit_pos = 0; + } + + // Проверяем, что после знака только цифры + for (size_t i = first_digit_pos; i < str.length(); ++i) + { + if (!isdigit(str[i])) + { + // Инцициализируем нулём + positive_ = true; + magnitude_.push_back(0); + return; + } + } + + // Пропускаем ведущие нули + for (; first_digit_pos < str.length(); ++first_digit_pos) + { + if (str[first_digit_pos] != '0') + break; + } + + // Если после знака только нули или ничего нет + if (first_digit_pos == str.length()) + { + // Инцициализируем нулём + positive_ = true; + magnitude_.push_back(0); + return; + } + + size_t num_digits = str.length() - first_digit_pos; + size_t num_chunks = num_digits / chunk_length; + size_t rest_length = num_digits - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) // Первый кусок полный + { + first_chunk_length = chunk_length; + } + else // Первый кусок неполный + { + first_chunk_length = rest_length; + ++num_chunks; + } + + magnitude_.reserve(num_chunks); + + // Извлекаем куски в обратном порядке и преобразуем в Digit (кроме первого куска) + for (size_t i = 0; i < num_chunks - 1; ++i) // i - номер куска с конца + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + magnitude_.push_back(to_uint32_t(chunk)); + } + + // Преобразуем в Digit первый кусок + string first_chunk = str.substr(first_digit_pos, first_chunk_length); + magnitude_.push_back(to_uint32_t(first_chunk)); +} + +bool BigInt::is_zero() const +{ + assert(magnitude_.size() > 0); + + // Проверяем, что нет ведущих нулей (вдруг число ещё не нормализовано и состоит из одних нулей) + assert(magnitude_.size() == 1 || magnitude_.back() != 0); + + if (magnitude_.size() == 1 && magnitude_[0] == 0) + { + assert(positive_); + return true; + } + + return false; +} + +void BigInt::set_positive(bool positive) +{ + if (!is_zero()) // Ноль всегда положительный + positive_ = positive; +} + +strong_ordering BigInt::operator<=>(const BigInt& rhs) const +{ + // Если у чисел разные знаки + if (positive_ != rhs.positive_) + { + // То, положительное больше отрицательного + return positive_ ? strong_ordering::greater : strong_ordering::less; + } + + // В этой точке у чисел одинаковый знак + + // Если у чисел разная длина + if (magnitude_.size() != rhs.magnitude_.size()) + { + // Если числа положительные + if (positive_) + { + // То более длинное число больше + return (magnitude_.size() > rhs.magnitude_.size()) ? strong_ordering::greater : strong_ordering::less; + } + else // А если числа отрицательные + { + // То более короткое число больше + return (magnitude_.size() < rhs.magnitude_.size()) ? strong_ordering::greater : strong_ordering::less; + } + } + + // В этой точке у чисел одинаковая длина + + // Сравниваем цифры в обратном порядке + for (size_t i = magnitude_.size() - 1; i != size_t(-1); --i) + { + if (magnitude_[i] != rhs.magnitude_[i]) + { + if (positive_) + return (magnitude_[i] > rhs.magnitude_[i]) ? strong_ordering::greater : strong_ordering::less; + else + return (magnitude_[i] < rhs.magnitude_[i]) ? strong_ordering::greater : strong_ordering::less; + } + } + + return strong_ordering::equal; +} + +BigInt BigInt::operator+(const BigInt& rhs) const +{ + BigInt ret; + + if (positive_ == rhs.positive_) + { + ret.positive_ = positive_; + ret.magnitude_ = add_magnitudes(magnitude_, rhs.magnitude_); + } + else + { + if (first_is_less(magnitude_, rhs.magnitude_)) + { + ret.positive_ = rhs.positive_; + ret.magnitude_ = sub_magnitudes(rhs.magnitude_, magnitude_); + } + else + { + ret.positive_ = positive_; + ret.magnitude_ = sub_magnitudes(magnitude_, rhs.magnitude_); + } + } + + return ret; +} + + +BigInt BigInt::operator-(const BigInt& rhs) const +{ + BigInt ret; + + if (positive_ != rhs.positive_) + { + ret.positive_ = positive_; + ret.magnitude_ = add_magnitudes(magnitude_, rhs.magnitude_); + } + else + { + if (first_is_less(magnitude_, rhs.magnitude_)) + { + ret.positive_ = !rhs.positive_; + ret.magnitude_ = sub_magnitudes(rhs.magnitude_, magnitude_); + } + else + { + ret.positive_ = positive_; + ret.magnitude_ = sub_magnitudes(magnitude_, rhs.magnitude_); + } + } + + return ret; +} + +BigInt BigInt::operator*(const BigInt& rhs) const +{ + BigInt ret; + ret.magnitude_ = mul_magnitudes(magnitude_, rhs.magnitude_); + ret.set_positive(positive_ == rhs.positive_); + return ret; +} + +BigInt BigInt::operator/(const BigInt& rhs) const +{ + pair, vector> dm = div_mod_magnitudes(magnitude_, rhs.magnitude_); + + BigInt ret; + ret.magnitude_ = dm.first; // Неполное частное + + // https://en.cppreference.com/w/cpp/language/operator_arithmetic#Built-in_multiplicative_operators + // (a/b)*b + a%b == a + // 7/3 = {2, 1} | 2*3 + 1 == 7 + // -7/3 = {-2, -1} | -2*3 + -1 == -7 + // 7/-3 = {-2, 1} | -2*-3 + 1 == 7 + // -7/-3 = {2, -1} | 2*-3 + -1 == -7 + + if (positive_ != rhs.positive_) // Если знаки числителя и знаменателя разные + ret.set_positive(false); // Тогда неполное частное отрицательное + + return ret; +} + +BigInt BigInt::operator%(const BigInt& rhs) const +{ + pair, vector> dm = div_mod_magnitudes(magnitude_, rhs.magnitude_); + + BigInt ret; + ret.magnitude_ = dm.second; // Остаток + + // Знак остатка совпадает со знаком числителя + ret.set_positive(positive_); + + return ret; +} + +BigInt BigInt::operator-() const +{ + BigInt ret = *this; + ret.set_positive(!positive_); + return ret; +} + +BigInt& BigInt::operator+=(const BigInt& rhs) +{ + BigInt result = *this + rhs; + swap(this->positive_, result.positive_); + swap(this->magnitude_, result.magnitude_); + return *this; +} + +BigInt& BigInt::operator-=(const BigInt& rhs) +{ + BigInt result = *this - rhs; + swap(this->positive_, result.positive_); + swap(this->magnitude_, result.magnitude_); + return *this; +} + +BigInt& BigInt::operator*=(const BigInt& rhs) +{ + BigInt result = *this * rhs; + swap(this->positive_, result.positive_); + swap(this->magnitude_, result.magnitude_); + return *this; +} + +BigInt& BigInt::operator/=(const BigInt& rhs) +{ + BigInt result = *this / rhs; + swap(this->positive_, result.positive_); + swap(this->magnitude_, result.magnitude_); + return *this; +} + +BigInt& BigInt::operator%=(const BigInt& rhs) +{ + BigInt result = *this % rhs; + swap(this->positive_, result.positive_); + swap(this->magnitude_, result.magnitude_); + return *this; +} + +string BigInt::to_string() const +{ + assert(magnitude_.size() > 0); + + string ret; + + if (!positive_) + { + ret.reserve(magnitude_.size() * chunk_length + 1); + ret = "-"; + } + else + { + ret.reserve(magnitude_.size() * chunk_length); + } + + // Первый кусок результата без ведущих нулей + ret += std::to_string(magnitude_.back()); + + // Остальные куски результата с ведущими нулями + for (size_t i = magnitude_.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", magnitude_[i], chunk_length); + + return ret; +} + +} // namespace dviglo diff --git a/big_int.hpp b/big_int.hpp new file mode 100644 index 0000000..c955aa7 --- /dev/null +++ b/big_int.hpp @@ -0,0 +1,125 @@ +// Copyright (c) the Dviglo project +// License: MIT + +#pragma once + +#include // uint64_t, ... +#include +#include + + +namespace dviglo +{ + +class BigInt +{ +public: + +#if true + static constexpr uint32_t base = 1'000'000'000; + + /// Число десятичных цифр в каждой цифре Digit при преобразовании в строку и обратно + static constexpr size_t chunk_length = 9; +#elif true // Для тестов + static constexpr uint32_t base = 1'000; + static constexpr size_t chunk_length = 3; +#else + static constexpr uint32_t base = 1'0; + static constexpr size_t chunk_length = 1; +#endif + + /// Отдельная цифра длинного числа + using Digit = uint32_t; + + /// Генерирует случайное положительное число указанной длины. Никогда не генерирует 0 + static BigInt generate(size_t length); + +private: + /// Знак числа (ноль всегда положительный) + bool positive_; + + /// Цифры числа в обратном порядке. Всегда содержит как минимум одну цифру + std::vector magnitude_; + +public: + /// Инициализирует нулём + BigInt(); + + BigInt(int32_t value); + BigInt(uint32_t value); + BigInt(int64_t value); + BigInt(uint64_t value); + BigInt(const std::string& str); + + bool is_zero() const; + bool is_positive() const { return positive_; } + bool is_negative() const { return !positive_; } + void set_positive(bool positive); + + bool operator==(const BigInt& rhs) const { return positive_ == rhs.positive_ && magnitude_ == rhs.magnitude_; } + std::strong_ordering operator<=>(const BigInt& rhs) const; + + BigInt operator+(const BigInt& rhs) const; + BigInt operator-(const BigInt& rhs) const; + + BigInt operator*(const BigInt& rhs) const; + + /// Возвращает 0, если rhs ноль + BigInt operator/(const BigInt& rhs) const; + + /// Возвращаеть 0, если rhs ноль + BigInt operator%(const BigInt& rhs) const; + + BigInt operator-() const; + + BigInt& operator+=(const BigInt& rhs); + BigInt& operator-=(const BigInt& rhs); + BigInt& operator*=(const BigInt& rhs); + BigInt& operator/=(const BigInt& rhs); + BigInt& operator%=(const BigInt& rhs); + + /// Prefix increment operator + BigInt& operator++() { this->operator+=(1); return *this; } + + /// Postfix increment operator + BigInt operator++(int) { BigInt ret = *this; ++*this; return ret; } + + /// Prefix decrement operator + BigInt& operator--() { this->operator-=(1); return *this; } + + /// Postfix decrement operator + BigInt operator--(int) { BigInt ret = *this; --*this; return ret; } + + std::string to_string() const; +}; + +inline BigInt operator+(int32_t lhs, const BigInt& rhs) { return BigInt(lhs) + rhs; } +inline BigInt operator+(uint32_t lhs, const BigInt& rhs) { return BigInt(lhs) + rhs; } +inline BigInt operator+(int64_t lhs, const BigInt& rhs) { return BigInt(lhs) + rhs; } +inline BigInt operator+(uint64_t lhs, const BigInt& rhs) { return BigInt(lhs) + rhs; } + +inline BigInt operator-(int32_t lhs, const BigInt& rhs) { return BigInt(lhs) - rhs; } +inline BigInt operator-(uint32_t lhs, const BigInt& rhs) { return BigInt(lhs) - rhs; } +inline BigInt operator-(int64_t lhs, const BigInt& rhs) { return BigInt(lhs) - rhs; } +inline BigInt operator-(uint64_t lhs, const BigInt& rhs) { return BigInt(lhs) - rhs; } + +inline BigInt operator*(int32_t lhs, const BigInt& rhs) { return BigInt(lhs) * rhs; } +inline BigInt operator*(uint32_t lhs, const BigInt& rhs) { return BigInt(lhs) * rhs; } +inline BigInt operator*(int64_t lhs, const BigInt& rhs) { return BigInt(lhs) * rhs; } +inline BigInt operator*(uint64_t lhs, const BigInt& rhs) { return BigInt(lhs) * rhs; } + +inline BigInt operator/(int32_t lhs, const BigInt& rhs) { return BigInt(lhs) / rhs; } +inline BigInt operator/(uint32_t lhs, const BigInt& rhs) { return BigInt(lhs) / rhs; } +inline BigInt operator/(int64_t lhs, const BigInt& rhs) { return BigInt(lhs) / rhs; } +inline BigInt operator/(uint64_t lhs, const BigInt& rhs) { return BigInt(lhs) / rhs; } + +inline BigInt operator%(int32_t lhs, const BigInt& rhs) { return BigInt(lhs) % rhs; } +inline BigInt operator%(uint32_t lhs, const BigInt& rhs) { return BigInt(lhs) % rhs; } +inline BigInt operator%(int64_t lhs, const BigInt& rhs) { return BigInt(lhs) % rhs; } +inline BigInt operator%(uint64_t lhs, const BigInt& rhs) { return BigInt(lhs) % rhs; } + +inline BigInt operator""_bi(const char* str, std::size_t) { return BigInt(str); } + +inline BigInt abs(const BigInt& value) { return value.is_negative() ? -value : value; } + +} // namespace dviglo diff --git a/docs/1_basics.md b/docs/1_basics.md new file mode 100644 index 0000000..81cba8a --- /dev/null +++ b/docs/1_basics.md @@ -0,0 +1,134 @@ +**Главы:** +1) [Основы длинной арифметики](1_basics.md) +2) [Длина неполного частного](2_div_len.md) +3) [Деление путём подбора разрядов](3_brute_force_div.md) +4) [Деление столбиком](4_long_div.md) + +---------------------------------------------- + +# 1. Основы длинной арифметики + +Основание (base) системы счисления (СС) — число цифр: +* в десятичной СС 10 цифр: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 +* в двоичной — две: 0, 1 +* в шестнадцатеричной — 16: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F +* и т.д. + +В позиционной СС значение каждой цифры зависит от позиции (разряда). +Например в числе 472 четыре сотни, семь десятков и две единицы: `4·100 + 7·10 + 2·1`. + +Разряды нумеруются с конца. Разряды слева — старшие, а справа — младшие. +Номер разряда совпадает со степенью base: 4·102 + 7·101 + 2·100. + +Большие числа можно хранить в виде массивов цифр (по одной цифре в каждом элементе массива). +При этом математические операции придётся реализовать самостоятельно (например столбиком, как в школе). + +Литература: +1. https://e-maxx.ru/algo/big_integer (ещё в разделе [bookz](http://e-maxx.ru/bookz/) есть полезные Кнут и Окулов) +2. https://ru.wikipedia.org/wiki/Длинная_арифметика +3. https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic +4. https://ru.wikipedia.org/wiki/Позиционная_система_счисления + +***Внимание! Уже упомянутая литература в последующих темах не упоминается.*** + +## Сложение столбиком и реверс цифр + +Пример: + +``` + 238 ++ 932 + ──── + 1170 +``` + +1) Складываем младшие разряды (единицы): `8 + 2 = 10`. Так как `10 >= base (10)`, + то есть перенос в следующий столбец +2) Складываем разряды десятков: `3 + 3 + 1 = 7`. Переноса в столбец сотен нет, так как `7 < 10` +3) Складываем разряды сотен: `2 + 9 + 0 = 11`. Так как `11 >= 10`, то есть перенос в столбец тысяч + +Реализация этого алгоритма: [1_basics_add_1.cpp](1_basics_add_1.cpp). + +***Внимание! В исходниках зачастую используется код из предыдущих программ, поэтому важно +изучать их последовательно.*** + +Тестировать код онлайн можно на сайтах: +1) +2) + +Опции для GCC: `-O0 -Wall -Wextra -Wpedantic -fsanitize=undefined -std=c++20`. + +В приведённом коде имеется 2 проблемы: +1) Приходится добавлять цифры в начало массива +2) Если числа разной длины, то приходится складывать цифры с разными индексами + +Эти проблемы легко обойти, если хранить цифры в обратном порядке. +Реализация: [1_basics_add_2.cpp](1_basics_add_2.cpp). + +Литература: +1. https://en.wikipedia.org/wiki/Carry_(arithmetic) +2. [Гуровиц и Гольдштейн](https://informatics.msk.ru/course/view.php?id=17#section-5) +3. https://www.geeksforgeeks.org/sum-two-large-numbers/ + +## Умножение столбиком и смена base + +Рассмотрим программу [1_basics_mul_1.cpp](1_basics_mul_1.cpp), в которой реализовано умножение +столбиком положительных десятичных чисел, а также ввод длинных чисел в виде строк. +В каждом элементе массива (тип uint32_t) мы храним по одной десятичной цифре. +Лучше будет использовать не десятичную СС, а СС с основанием 109 (в этой СС не 10 цифр, а 1'000'000'000 цифр). +При этом массивы с цифрами будут короче, а значит будет меньше потребление памяти и быстрее вычисления. + +Почему выбрано именно base 109? +1) Числа из СС с основанием 10x легко преобразовывать в десятичные строки и обратно + (одна цифра длинного числа содержит ровно x десятичных цифр) +2) Тип uint32_t умещает 109 − 1, но не умещает 1010 − 1, + поэтому base 1010 уже не годится + +Таким образом, программа будет иметь вид: [1_basics_mul_2.cpp](1_basics_mul_2.cpp). +Обратите внимание, что при промежуточных вычислениях +используется тип uint64_t, который способен уместить значение base2 − 1. + +Литература: +1. https://en.wikipedia.org/wiki/Multiplication_algorithm#Other_notations +2. http://cppalgo.blogspot.com/2010/05/blog-post.html +3. https://www.geeksforgeeks.org/multiply-large-numbers-represented-as-strings/ +4. https://brestprog.by/topics/longarithmetics/ + +## Вычитание столбиком + +Рассмотрим программу [1_basics_sub_1.cpp](1_basics_sub_1.cpp), в которой реализовано вычитание столбиком длинных чисел. +В целом, алгоритм похож на сложение [1_basics_add_2.cpp](1_basics_add_2.cpp), +только в нём происходит не перенос единицы в старший разряд, а заём единицы из старшего разряда. +Так как цифры хранятся в типе uint32_t, то перед вычитанием цифр производится сравнение, +чтобы результатом вычисления не стало отрицательное число (и переполнение). +Для избавления от лишних ветвлений воспользуемся тем фактом, что в C++ при преобразовании +bool в число всегда получается 0 или 1. + +``` +uint32_t borrow = minuend_digit < subtrahend_digit; +assert(borrow == 0 || borrow == 1); +``` + +Таким образом, программа будет иметь следующий вид: [1_basics_sub_2.cpp](1_basics_sub_2.cpp). + +## Деление путём вычитаний и сравнение + +Самый простой и самый медленный способ деления: из числителя (делимого) вычитаем знаменатель (делитель) +до тех пор, пока остаток не станет меньше знаменателя. + +Пример: `91 / 8 = 91 − 8 − 8 − 8 − 8 …` +Восьмёрку удаётся вычесть 11 раз (значит **неполное частное** = 11) и остаётся 3, которая меньше 8. + +Таким образом, для данного алгоритма также потребуется операция сравнения. +Реализация: [1_basics_div.cpp](1_basics_div.cpp). Обратите внимание, что при сравнении очень важно, +чтобы у чисел не было ведущих нулей. + +Недостатоком алгоритма является то, что если числитель большой, а знаменатель маленький, то число циклов будет огромным. + +Литература: +1. https://ru.wikipedia.org/wiki/Алгоритм_деления#Деление_путём_вычитаний +2. https://en.wikipedia.org/wiki/Division_algorithm + +---------------------------------------------- + +Следующая глава: [2. Длина неполного частного](2_div_len.md) diff --git a/docs/1_basics_add_1.cpp b/docs/1_basics_add_1.cpp new file mode 100644 index 0000000..4ad73da --- /dev/null +++ b/docs/1_basics_add_1.cpp @@ -0,0 +1,83 @@ +// Сложение столбиком + +#include +#include +#include +#include +#include + +using namespace std; + + +// Отдельная цифра длинного числа +using Digit = uint32_t; + +// Складывает два положительных длинных десятичных числа столбиком +vector add(const vector& a, const vector& b) +{ + // Выясняем, какое число длиннее + const vector& long_num = a.size() >= b.size() ? a : b; + const vector& short_num = a.size() >= b.size() ? b : a; + + vector ret; // Результат + + // Насколько число short_num короче числа long_num + size_t len_diff = long_num.size() - short_num.size(); + + // Перенос в старший разряд (всегда 0 или 1) + Digit carry = 0; + + // Цикл начинается справа (с младших разрядов) + for (size_t index_short = short_num.size() - 1; index_short != size_t(-1); --index_short) + { + // Разряды, которые нужно складывать, имеют разные индексы (из-за разной длины чисел) + size_t index_long = index_short + len_diff; + + uint32_t sum = long_num[index_long] + short_num[index_short] + carry; + carry = sum / 10; + assert(carry == 0 || carry == 1); + ret.insert(ret.begin(), sum % 10); // Добавляем цифру слева + } + + // Оставшиеся цифры более длинного числа + for (size_t index_long = len_diff - 1; index_long != size_t(-1); --index_long) + { + uint32_t sum = long_num[index_long] + carry; + carry = sum / 10; + assert(carry == 0 || carry == 1); + ret.insert(ret.begin(), sum % 10); // Добавляем цифру слева + } + + if (carry) + ret.insert(ret.begin(), 1); + + return ret; +} + +// Преобразует длинное число в строку +string to_string(const vector& big_num) +{ + string ret; + + for (Digit digit : big_num) + ret += std::to_string(digit); + + return ret; +} + +// Печатает два длинных числа и их сумму +void show(const vector& a, const vector& b) +{ + cout << to_string(a) << " + " << to_string(b) << " = " << to_string(add(a, b)) << endl; +} + +int main() +{ + show({1, 2, 3}, {4, 5, 6}); // 123 + 456 = 579 + show({9, 9, 9}, {1, 2}); // 999 + 12 = 1011 + show({1, 2}, {9, 9, 9}); // 12 + 999 = 1011 + show({1, 2, 3}, {9, 9, 9}); // 123 + 999 = 1122 + show({9, 9, 9, 9}, {9, 9, 9, 9}); // 9999 + 9999 = 19998 + + return 0; +} diff --git a/docs/1_basics_add_2.cpp b/docs/1_basics_add_2.cpp new file mode 100644 index 0000000..a34dbb6 --- /dev/null +++ b/docs/1_basics_add_2.cpp @@ -0,0 +1,82 @@ +// Сложение столбиком: обратный порядок цифр + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +// Отдельная цифра длинного числа +using Digit = uint32_t; + +// Складывает два положительных длинных десятичных числа столбиком. +// Функция изменена по сравнению с 1_basics_add_1.cpp (теперь цифры в обратном порядке) +vector add(const vector& a, const vector& b) +{ + // Выясняем, какое число длиннее + const vector& long_num = a.size() >= b.size() ? a : b; + const vector& short_num = a.size() >= b.size() ? b : a; + + vector ret; // Результат + + // Перенос в старший разряд (всегда 0 или 1) + Digit carry = 0; + + // Цикл начинается слева (с младших разрядов) + for (size_t i = 0; i < short_num.size(); ++i) + { + uint32_t sum = long_num[i] + short_num[i] + carry; + carry = sum / 10; + assert(carry == 0 || carry == 1); + ret.push_back(sum % 10); // Добавляем цифру в конец + } + + // Оставшиеся цифры более длинного числа + for (size_t i = short_num.size(); i < long_num.size(); ++i) + { + uint32_t sum = long_num[i] + carry; + carry = sum / 10; + assert(carry == 0 || carry == 1); + ret.push_back(sum % 10); + } + + if (carry) + ret.push_back(1); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция изменена по сравнению с 1_basics_add_1.cpp (теперь цифры в обратном порядке) +string to_string(const vector& big_num) +{ + string ret; + + // Цикл по цифрам числа в обратном порядке + for (Digit digit : big_num | views::reverse) + ret += std::to_string(digit); + + return ret; +} + +// Печатает два длинных числа и их сумму +void show(const vector& a, const vector& b) +{ + cout << to_string(a) << " + " << to_string(b) << " = " << to_string(add(a, b)) << endl; +} + +// Функция изменена по сравнению с 1_basics_add_1.cpp (теперь цифры в обратном порядке) +int main() +{ + show({3, 2, 1}, {6, 5, 4}); // 123 + 456 = 579 + show({9, 9, 9}, {2, 1}); // 999 + 12 = 1011 + show({2, 1}, {9, 9, 9}); // 12 + 999 = 1011 + show({3, 2, 1}, {9, 9, 9}); // 123 + 999 = 1122 + show({9, 9, 9, 9}, {9, 9, 9, 9}); // 9999 + 9999 = 19998 + + return 0; +} diff --git a/docs/1_basics_div.cpp b/docs/1_basics_div.cpp new file mode 100644 index 0000000..eda85f3 --- /dev/null +++ b/docs/1_basics_div.cpp @@ -0,0 +1,219 @@ +// Деление путём вычитаний и сравнение + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +constexpr size_t chunk_length = 9; + +// Убеждаемся, что bool всегда преобразуется в 1 или 0 +static_assert(uint32_t(5 > 3) == 1 && uint32_t(bool(7)) == 1); + +// Складывает два положительных длинных числа столбиком. +// Функция изменена по сравнению с 1_basics_add_2.cpp (base вместо 10) +vector add(const vector& a, const vector& b) +{ + const vector& long_num = a.size() >= b.size() ? a : b; + const vector& short_num = a.size() >= b.size() ? b : a; + + vector ret; + + Digit carry = 0; + + for (size_t i = 0; i < short_num.size(); ++i) + { + uint32_t sum = long_num[i] + short_num[i] + carry; + carry = sum / base; + assert(carry == 0 || carry == 1); + ret.push_back(sum % base); + } + + for (size_t i = short_num.size(); i < long_num.size(); ++i) + { + uint32_t sum = long_num[i] + carry; + carry = sum / base; + assert(carry == 0 || carry == 1); + ret.push_back(sum % base); + } + + if (carry) + ret.push_back(1); + + return ret; +} + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend. +// Функция скопирована из 1_basics_sub_2.cpp +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + Digit borrow = 0; + + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = subtrahend[i] + borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Определяет, меньше ли первое число +bool first_is_less(const vector& first, const vector& second) +{ + // Проверяем, что нет ведущих нулей + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + // В числах не должно быть ведущих нулей + if (first.size() != second.size()) + return first.size() < second.size(); // Если число короче, значит оно меньше + + // Сравниваем разряды, начиная с конца (со старших разрядов) + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; // first == second +} + +// Определяет, что длинное число равно нулю +bool is_zero(const vector& number) +{ + return (number.size() == 1 && number[0] == 0); +} + +// Возвращает неполное частное и остаток. +// Если знаменатель == 0, возвращает {0, 0} +pair, vector> div_mod(const vector& numerator, const vector& denominator) +{ + if (is_zero(denominator)) + return {vector{0}, vector{0}}; + + vector quotient{0}; // Неполное частное + vector remainder = numerator; // Остаток + + // Пока remainder >= denominator + while (!first_is_less(remainder, denominator)) + { + quotient = add(quotient, {1}); // ++quotient + remainder = sub(remainder, denominator); // remainder -= denominator + } + + return {quotient, remainder}; +} + +// Печатает два длинных числа и результат их деления +void show(const vector& a, const vector& b) +{ + pair, vector> result = div_mod(a, b); + + cout << to_string(a) << " / " << to_string(b) << " = " << to_string(result.first) + << " (remainder = " << to_string(result.second) << ")" << endl; +} + +int main() +{ + show(to_num("0"), to_num("0")); // 0, 0 + show(to_num("5"), to_num("3")); // 1, 2 + show(to_num("123"), to_num("67")); // 1, 56 + show(to_num("512"), to_num("17")); // 30, 2 + show(to_num("999"), to_num("999")); // 1, 0 + show(to_num("9999"), to_num("000")); // 0, 0 + + // 566, 994340 + show(to_num("567000000"), to_num("1000010")); + + // 5000, 1111 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222222222222222222")); + + // 130686, 5304519702476588520600956014 + show(to_num("1293564533453453419234546456456456"), to_num("9898223443473294328742373747")); + + return 0; +} diff --git a/docs/1_basics_mul_1.cpp b/docs/1_basics_mul_1.cpp new file mode 100644 index 0000000..2af96ae --- /dev/null +++ b/docs/1_basics_mul_1.cpp @@ -0,0 +1,119 @@ +// Умножение столбиком + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +// Отдельная цифра длинного числа +using Digit = uint32_t; + +// Перемножает два положительных длинных десятичных числа столбиком +vector mul(const vector& a, const vector& b) +{ + // В векторах должна быть хотя бы одна цифра + assert(a.size() > 0 && b.size() > 0); + + vector ret; // Результат + + // Максимальная длина результата = a.size() + b.size() (когда 999... * 999...), + // а минимальная - 1 (например при умножении на 0) + ret.resize(a.size() + b.size(), 0); + + // Каждую цифру числа a умножаем на число b (начиная с младших разрядов) + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; // Перенос в старший разряд + // Максимальный перенос при умножении двух цифр = 8 (при 9 * 9). + // Так как одновременно выполняется сложение, то может добавляться перенос от сложения (максимум 1). + // Таким образом carry может достигать 9 (например при 99 * 99) + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + + ret[ret_index] += a[a_index] * b[b_index] + carry; + assert(ret[ret_index] <= 9 + 9 * 9 + 9); + // Максимальное значение в каждой ячейке вектора = + // (base - 1) + (base - 1)^2 + (base - 1) = base^2 - 1 + // (например при 999 * 999) + + carry = ret[ret_index] / 10; + assert(carry <= 9); + ret[ret_index] %= 10; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + // Убираем ведущие нули + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_add_2.cpp +string to_string(const vector& big_num) +{ + string ret; + + for (Digit digit : big_num | views::reverse) + ret += std::to_string(digit); + + return ret; +} + +// Преобразует строку в длинное число +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + vector ret; + + // Цикл по символам строки в обратном порядке + for (char c : str | views::reverse) + ret.push_back(Digit(c - '0')); + + // Убираем ведущие нули + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Печатает два длинных числа и их произведение +void show(const vector& a, const vector& b) +{ + cout << to_string(a) << " * " << to_string(b) << " = " << to_string(mul(a, b)) << endl; +} + +int main() +{ + show(to_num("2"), to_num("8")); // 16 + show(to_num("123"), to_num("67")); // 8241 + show(to_num("17"), to_num("512")); // 8704 + show(to_num("99"), to_num("99")); // 9801 + show(to_num("999"), to_num("999")); // 998001 + show(to_num("000"), to_num("999")); // 0 + show(to_num("3"), to_num("745")); // 2235 + + // 567005670000000 + show(to_num("1000010"), to_num("567000000")); + + // 1280399079067456714112168894673698375814428579386489438786544 + show(to_num("1293564533453453419234546456456456"), to_num("989822344347329432874237374")); + + // 99999999999999999999999999999989990000000000000000000000000000001 + show(to_num("9999999999999999999999999999999999"), to_num("9999999999999999999999999999999")); + + return 0; +} diff --git a/docs/1_basics_mul_2.cpp b/docs/1_basics_mul_2.cpp new file mode 100644 index 0000000..816ed41 --- /dev/null +++ b/docs/1_basics_mul_2.cpp @@ -0,0 +1,147 @@ +// Умножение столбиком: смена base + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +// Основание системы счисления +constexpr uint32_t base = 1'000'000'000; + +// Отдельная цифра длинного числа (0, 1, 2, ..., base-1) +using Digit = uint32_t; + +// Тип удвоенной (double) длины. Умещает квадрат цифры +using DDigit = uint64_t; + +// Число десятичных цифр в каждой цифре Digit при преобразовании в строку и обратно +constexpr size_t chunk_length = 9; + +// Перемножает два положительных длинных числа столбиком. +// Функция изменена по сравнению с 1_basics_mul_1.cpp (сменили base) +vector mul(const vector& a, const vector& b) +{ + assert(a.size() > 0 && b.size() > 0); + + vector ret; + ret.resize(a.size() + b.size(), 0); + + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + + // uint32_t не может уместить base^2 - 1, а uint64_t - может + DDigit v = ret[ret_index] + (DDigit)a[a_index] * b[b_index] + carry; + assert(v <= (DDigit)base * base - 1); + + carry = Digit(v / base); + assert(carry < base); + ret[ret_index] = v % base; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция изменена по сравнению с 1_basics_mul_1.cpp +// (теперь одна цифра длинного числа соответствует девяти символам строки, а не одному) +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + // Первый кусок результата без ведущих нулей + string ret = std::to_string(big_num.back()); + + // Остальные куски результата с ведущими нулями + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция изменена по сравнению с 1_basics_mul_1.cpp +// (теперь одна цифра длинного числа соответствует девяти символам строки, а не одному) +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; // Число кусков в строке + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) // Первый кусок полный + { + first_chunk_length = chunk_length; + } + else // Первый кусок неполный + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + // Извлекаем куски в обратном порядке и преобразуем в Digit (кроме первого куска) + for (size_t i = 0; i < num_chunks - 1; ++i) // i - номер куска с конца + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + // Преобразуем в Digit первый кусок + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + // Убираем ведущие нули + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Печатает два длинных числа и их произведение +void show(const vector& a, const vector& b) +{ + cout << to_string(a) << " * " << to_string(b) << " = " << to_string(mul(a, b)) << endl; +} + +int main() +{ + show(to_num("2"), to_num("8")); // 16 + show(to_num("123"), to_num("67")); // 8241 + show(to_num("17"), to_num("512")); // 8704 + show(to_num("99"), to_num("99")); // 9801 + show(to_num("999"), to_num("999")); // 998001 + show(to_num("000"), to_num("999")); // 0 + show(to_num("3"), to_num("745")); // 2235 + + // 567005670000000 + show(to_num("1000010"), to_num("567000000")); + + // 1280399079067456714112168894673698375814428579386489438786544 + show(to_num("1293564533453453419234546456456456"), to_num("989822344347329432874237374")); + + // 99999999999999999999999999999989990000000000000000000000000000001 + show(to_num("9999999999999999999999999999999999"), to_num("9999999999999999999999999999999")); + + return 0; +} diff --git a/docs/1_basics_sub_1.cpp b/docs/1_basics_sub_1.cpp new file mode 100644 index 0000000..81b7e4a --- /dev/null +++ b/docs/1_basics_sub_1.cpp @@ -0,0 +1,153 @@ +// Вычитание столбиком + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +constexpr size_t chunk_length = 9; + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + // Заём из следующего столбца + Digit borrow = 0; // Всегда 0 или 1 + + // Вычитаем цифры чисел, начиная с младших разрядов + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + + // Если занимали на предыдущем шаге, то нужно вычесть на 1 больше + Digit subtrahend_digit = subtrahend[i] + borrow; + borrow = 0; + + // Если нужен заём на данном шаге + if (minuend_digit < subtrahend_digit) + { + // Занимаем из старшего разряда + minuend_digit += base; + borrow = 1; + } + + ret[i] = minuend_digit - subtrahend_digit; + } + + // Оставшиеся цифры minuend + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = 0; + + if (minuend_digit < subtrahend_digit) + { + minuend_digit += base; + borrow = 1; + } + + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + // Убираем ведущие нули + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Печатает два длинных числа и их разность +void show(const vector& a, const vector& b) +{ + cout << to_string(a) << " - " << to_string(b) << " = " << to_string(sub(a, b)) << endl; +} + +int main() +{ + show(to_num("0"), to_num("0")); // 0 + show(to_num("5"), to_num("3")); // 2 + show(to_num("123"), to_num("67")); // 56 + show(to_num("512"), to_num("17")); // 495 + show(to_num("999"), to_num("999")); // 0 + show(to_num("9999"), to_num("000")); // 9999 + + // 565999990 + show(to_num("567000000"), to_num("1000010")); + + // 11111111111111111108888888888888888889 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222")); + + // 1293563543631109071905113582219082 + show(to_num("1293564533453453419234546456456456"), to_num("989822344347329432874237374")); + + // 9990000000000000000000000000000000 + show(to_num("9999999999999999999999999999999999"), to_num("9999999999999999999999999999999")); + + return 0; +} diff --git a/docs/1_basics_sub_2.cpp b/docs/1_basics_sub_2.cpp new file mode 100644 index 0000000..28b7d86 --- /dev/null +++ b/docs/1_basics_sub_2.cpp @@ -0,0 +1,150 @@ +// Вычитание столбиком: убираем ветвления + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +constexpr size_t chunk_length = 9; + +// Убеждаемся, что bool всегда преобразуется в 1 или 0 +static_assert(uint32_t(5 > 3) == 1 && uint32_t(bool(7)) == 1); + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend. +// Функция изменена по сравнению с 1_basics_sub_1.cpp (убраны ветвления) +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + // Заём из следующего столбца + Digit borrow = 0; // Всегда 0 или 1 + + // Вычитаем цифры чисел, начиная с младших разрядов + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + + // Если занимали на предыдущем шаге, то нужно вычесть на 1 больше + Digit subtrahend_digit = subtrahend[i] + borrow; + + // Определяем, нужен ли заём на данном шаге + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + + // Занимаем из старшего разряда, если нужно + minuend_digit += base * borrow; + + ret[i] = minuend_digit - subtrahend_digit; + } + + // Оставшиеся цифры minuend + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + // Убираем ведущие нули + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Печатает два длинных числа и их разность +void show(const vector& a, const vector& b) +{ + cout << to_string(a) << " - " << to_string(b) << " = " << to_string(sub(a, b)) << endl; +} + +int main() +{ + show(to_num("0"), to_num("0")); // 0 + show(to_num("5"), to_num("3")); // 2 + show(to_num("123"), to_num("67")); // 56 + show(to_num("512"), to_num("17")); // 495 + show(to_num("999"), to_num("999")); // 0 + show(to_num("9999"), to_num("000")); // 9999 + + // 565999990 + show(to_num("567000000"), to_num("1000010")); + + // 11111111111111111108888888888888888889 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222")); + + // 1293563543631109071905113582219082 + show(to_num("1293564533453453419234546456456456"), to_num("989822344347329432874237374")); + + // 9990000000000000000000000000000000 + show(to_num("9999999999999999999999999999999999"), to_num("9999999999999999999999999999999")); + + return 0; +} diff --git a/docs/2_div_len.cpp b/docs/2_div_len.cpp new file mode 100644 index 0000000..a3af20c --- /dev/null +++ b/docs/2_div_len.cpp @@ -0,0 +1,166 @@ +// Точная длина неполного частного + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +using Digit = uint32_t; + +#if true +constexpr uint32_t base = 1'0; +constexpr size_t chunk_length = 1; +#elif true +constexpr uint32_t base = 1'00; +constexpr size_t chunk_length = 2; +#elif true +constexpr uint32_t base = 1'000'000'000; +constexpr size_t chunk_length = 9; +#endif + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Определяет, меньше ли первое число. +// Функция скопирована из 1_basics_div.cpp +bool first_is_less(const vector& first, const vector& second) +{ + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + if (first.size() != second.size()) + return first.size() < second.size(); + + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; +} + +// Определяет, что длинное число равно нулю. +// Функция скопирована из 1_basics_div.cpp +bool is_zero(const vector& number) +{ + return (number.size() == 1 && number[0] == 0); +} + +// Определяет длину неполного частного +size_t cal_quotient(const vector& numerator, const vector& denominator) +{ + if (is_zero(denominator)) + return 1; // Пусть при делении на 0 результатом будет 0 (длина = 1) + + if (first_is_less(numerator, denominator)) // Если числитель меньше знаменателя + return 1; // Неполное частное = 0 (длина = 1) + + vector chunk; // Первое неполное делимое + + // Ищем первое неполное делимое (начинаем со старших разрядов) + for (size_t i = numerator.size() - 1; i != size_t(-1); --i) + { + // Добавляем цифру слева (так как храним цифры в обратном порядке) + chunk.insert(chunk.begin(), numerator[i]); + + if (!first_is_less(chunk, denominator)) // chunk >= denominator + break; // Нашли первое неполное делимое + } + + // Неполное делимое максимум на одну цифру длиннее знаменателя + assert(chunk.size() == denominator.size() || chunk.size() == denominator.size() + 1); + + size_t ret = numerator.size() - chunk.size() + 1; + assert(ret == numerator.size() - denominator.size() || ret == numerator.size() - denominator.size() + 1); + + return ret; +} + +// Печатает два длинных числа и длину неполного частного +void show(const vector& a, const vector& b) +{ + cout << to_string(a) << " / " << to_string(b) << " | result length = " << cal_quotient(a, b) << endl; +} + +int main() +{ + show(to_num("0"), to_num("0")); // 1 (0) + show(to_num("5"), to_num("3")); // 1 (1) + show(to_num("123"), to_num("67")); // 1 (1) + show(to_num("512"), to_num("17")); // 2 (30) + show(to_num("999"), to_num("999")); // 1 (1) + show(to_num("9999"), to_num("000")); // 1 (0) + + // 3 (результат деления = 566) + show(to_num("567000000"), to_num("1000010")); + + // 4 (5000) + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222222222222222222")); + + // 6 (130686) + show(to_num("1293564533453453419234546456456456"), to_num("9898223443473294328742373747")); + + // 50 (13068658137053464352525468785867118980456379357846) + show(to_num("12935645334534534192345464564564563443473294328742373747"), to_num("989822")); + + // 10 (3000000000) + show(to_num("15000000000"), to_num("5")); + + return 0; +} diff --git a/docs/2_div_len.md b/docs/2_div_len.md new file mode 100644 index 0000000..3497b1c --- /dev/null +++ b/docs/2_div_len.md @@ -0,0 +1,64 @@ +**Главы:** +1) [Основы длинной арифметики](1_basics.md) +2) [Длина неполного частного](2_div_len.md) +3) [Деление путём подбора разрядов](3_brute_force_div.md) +4) [Деление столбиком](4_long_div.md) + +---------------------------------------------- + +# 2. Длина неполного частного + +Длину результата деления можно узнать до выполнения деления. + +### Максимальная длина неполного частного + +1\) Результат деления будет максимально длинным, если в знаменателе 1: + +`989901 / 1 = 989901` ⇒ `длина результата = длина числителя = 6` + +2\) Результат будет максимально коротким, если знаменатель равен числителю: + +`989901 / 989901 = 1` ⇒ `длина результата = 1` + +3\) Ещё пример: + +`989901 / 99 = 9999` ⇒ `длина результата = длина числителя − длина знаменателя = 6 − 2 = 4` + +4\) Ещё пример (без учёта остатка): + +`989901 / 70 = 14141` ⇒ `длина результата = длина числителя − длина знаменателя + 1 = 6 − 2 + 1 = 5` + +Формула 4) подходит и для случаев 1) и 2), но не подходит для примера 3). +Таким образом, эта формула определяет **максимальную** длину результата. + +### Точная длина неполного частного + +Допустим нам нужно разделить 10512 на 23. + +1\) Ищем первое неполное делимое ("неполное", потому что это фрагмент делимого): + +1. 1 не годится, так как меньше 23 +2. 10 не годится, так как меньше 23 +3. 105 - первое неполное делимое, так как >= 23 + +2\) Первое неполное делимое даёт одну цифру результата. Плюс остальные цифры числителя дают по одной цифре результата: + +`длина результата = длина числителя - длина первого неполного делимого + 1 = 5 - 3 + 1 = 3` + +На самом деле при делении каждая цифра числителя даёт одну цифру результата +(это будет понятно позже при рассмотрении деления столбиком), но ведущие нули +отбрасываются, а нули внутри числа - нет: `11832 / 29 = 00408 = 408`. + +Реализация: [2_div_len.cpp](2_div_len.cpp). + +Обычно точно вычислять длину результата не требуется. +Для выделения места достаточно формулы максимальной длины, которая +может превысить реальную длину максимум на 1. + +Источники: +1. https://www.youtube.com/watch?v=ioeAg9emsqE +2. https://www.youtube.com/watch?v=p-CtgmGOqPs + +---------------------------------------------- + +Следующая глава: [3. Деление путём подбора разрядов](3_brute_force_div.md) diff --git a/docs/3_brute_force_div.md b/docs/3_brute_force_div.md new file mode 100644 index 0000000..f38cc61 --- /dev/null +++ b/docs/3_brute_force_div.md @@ -0,0 +1,87 @@ +**Главы:** +1) [Основы длинной арифметики](1_basics.md) +2) [Длина неполного частного](2_div_len.md) +3) [Деление путём подбора разрядов](3_brute_force_div.md) +4) [Деление столбиком](4_long_div.md) + +---------------------------------------------- + +# 3. Деление путём подбора разрядов + +Рассмотрим ещё один очень простой алгоритм деления. + +Допустим нам нужно разделить 63 на 3. +Для простоты будем использовать десятичную СС и обычный порядок цифр. + +1\) `Максимальная длина результата = 2 − 1 + 1 = 2`. Обнуляем массив результата: + +`результат = {0, 0}` + +2\) Увеличим старший разряд результата на 1: + +`результат = {1, 0} // 10` + +3\) Проверяем, что `результат * знаменатель <= числитель`: + +`{1, 0} * {3} <= {6, 3} // 30 <= 63` + +Условие выполняется. + +4\) Снова увеличим старший разряд на 1 и снова проверяем: + +``` +результат = {2, 0} // 20 +{2, 0} * {3} <= {6, 3} // 60 <= 63 +``` +Условие выполняется. + +5\) Снова увеличим старший разряд на 1 и снова проверяем: + +``` +результат = {3, 0} // 30 +{3, 0} * {3} <= {6, 3} // 90 <= 63 +``` + +Условие не выполняется. Значит на предыдущем шаге был найден старший разряд результата. + +6\) Вычитаем из старшего разряда 1, чтобы вернуть предыдущее значение: + +`результат = {2, 0} // 20` + +7\) Старший разряд мы подобрали, теперь начинаем увеличивать на 1 следующий разряд: + +``` +результат = {2, 1} // 21 +{2, 1} * {3} <= {6, 3} // 63 <= 63 +``` + +Условие выполняется. + +8\) Снова увеличиваем на 1 первый разряд (нумерация с 0): + +``` +результат = {2, 2} // 22 +{2, 2} * {3} <= {6, 3} // 66 <= 63 +``` + +Условие не выполняется. Значит на предыдущем шаге был найден первый разряд результата. + +9\) Вычитаем из первого разряда единицу: + +`результат = {2, 1} // 21` + +Разрядов больше нет, ответ получен. + +Реализация: [3_brute_force_div_1.cpp](3_brute_force_div_1.cpp). + +Из-за перебора цифр этот алгоритм также очень медленный. В худшем варианте каждую цифру результата придётся проверять base раз. +Подбор цифр можно значительно ускорить с помощью [двоичного поиска](https://ru.wikipedia.org/wiki/Двоичный_поиск). +Релизация: [3_brute_force_div_2.cpp](3_brute_force_div_2.cpp). + +Проблемой остаётся то, что при каждой проверке длинные числа перемножаются целиком. + +Источник: . + +---------------------------------------------- + +Следующая глава: [4. Деление столбиком](4_long_div.md) diff --git a/docs/3_brute_force_div_1.cpp b/docs/3_brute_force_div_1.cpp new file mode 100644 index 0000000..8bbcfb9 --- /dev/null +++ b/docs/3_brute_force_div_1.cpp @@ -0,0 +1,237 @@ +// Деление путём подбора разрядов + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +using DDigit = uint64_t; +constexpr size_t chunk_length = 9; + +// Убеждаемся, что bool всегда преобразуется в 1 или 0 +static_assert(uint32_t(5 > 3) == 1 && uint32_t(bool(7)) == 1); + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend. +// Функция скопирована из 1_basics_sub_2.cpp +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + Digit borrow = 0; + + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = subtrahend[i] + borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Перемножает два положительных длинных числа столбиком. +// Функция скопирована из 1_basics_mul_2.cpp +vector mul(const vector& a, const vector& b) +{ + assert(a.size() > 0 && b.size() > 0); + + vector ret; + ret.resize(a.size() + b.size(), 0); + + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + DDigit v = ret[ret_index] + (DDigit)a[a_index] * b[b_index] + carry; + assert(v <= (DDigit)base * base - 1); + carry = Digit(v / base); + assert(carry < base); + ret[ret_index] = v % base; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Определяет, меньше ли первое число. +// Функция скопирована из 1_basics_div.cpp +bool first_is_less(const vector& first, const vector& second) +{ + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + if (first.size() != second.size()) + return first.size() < second.size(); + + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; +} + +// Определяет, что длинное число равно нулю. +// Функция скопирована из 1_basics_div.cpp +bool is_zero(const vector& number) +{ + return (number.size() == 1 && number[0] == 0); +} + +// Возвращает неполное частное и остаток. +// Если знаменатель == 0, возвращает {0, 0}. +// Функция изменена по сравнению с 1_basics_div.cpp (подбираем разряды, а не вычитаем в цикле) +pair, vector> div_mod(const vector& numerator, const vector& denominator) +{ + // Если числитель или знаменатель == 0 + if (is_zero(numerator) || is_zero(denominator)) + return {vector{0}, vector{0}}; + + // Если числитель меньше знаменателя + if (first_is_less(numerator, denominator)) + return {vector{0}, numerator}; + + // Неполное частное + vector quotient; + quotient.resize(numerator.size() - denominator.size() + 1, 0); + + // Подбираем разряды, начиная со старшего разряда (с конца) + for (size_t i = quotient.size() - 1; i != size_t(-1); --i) + { + // Пока quotient * denominator <= numerator + while (!first_is_less(numerator, mul(quotient, denominator))) // Ведущие нули убираются при умножении + ++quotient[i]; // Увеличиваем разряд + + assert(quotient[i] > 0); + --quotient[i]; // Откатываем разряд на один шаг назад + } + + // Убираем ведущие нули + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + // Остаток = numerator - quotient * denominator + vector remainder = sub(numerator, mul(quotient, denominator)); + + return {quotient, remainder}; +} + +// Печатает два длинных числа и результат их деления +void show(const vector& a, const vector& b) +{ + pair, vector> result = div_mod(a, b); + + cout << to_string(a) << " / " << to_string(b) << " = " << to_string(result.first) + << " (remainder = " << to_string(result.second) << ")" << endl; +} + +int main() +{ + show(to_num("0"), to_num("0")); // 0, 0 + show(to_num("5"), to_num("3")); // 1, 2 + show(to_num("123"), to_num("67")); // 1, 56 + show(to_num("512"), to_num("17")); // 30, 2 + show(to_num("999"), to_num("999")); // 1, 0 + show(to_num("9999"), to_num("000")); // 0, 0 + + // 566, 994340 + show(to_num("567000000"), to_num("1000010")); + + // 5000, 1111 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222222222222222222")); + + // 130686, 5304519702476588520600956014 + show(to_num("1293564533453453419234546456456456"), to_num("9898223443473294328742373747")); + + return 0; +} diff --git a/docs/3_brute_force_div_2.cpp b/docs/3_brute_force_div_2.cpp new file mode 100644 index 0000000..984b990 --- /dev/null +++ b/docs/3_brute_force_div_2.cpp @@ -0,0 +1,259 @@ +// Деление путём подбора разрядов: двоичный поиск + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +using DDigit = uint64_t; +constexpr size_t chunk_length = 9; + +// Убеждаемся, что bool всегда преобразуется в 1 или 0 +static_assert(uint32_t(5 > 3) == 1 && uint32_t(bool(7)) == 1); + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend. +// Функция скопирована из 1_basics_sub_2.cpp +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + Digit borrow = 0; + + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = subtrahend[i] + borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Перемножает два положительных длинных числа столбиком. +// Функция скопирована из 1_basics_mul_2.cpp +vector mul(const vector& a, const vector& b) +{ + assert(a.size() > 0 && b.size() > 0); + + vector ret; + ret.resize(a.size() + b.size(), 0); + + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + DDigit v = ret[ret_index] + (DDigit)a[a_index] * b[b_index] + carry; + assert(v <= (DDigit)base * base - 1); + carry = Digit(v / base); + assert(carry < base); + ret[ret_index] = v % base; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Определяет, меньше ли первое число. +// Функция скопирована из 1_basics_div.cpp +bool first_is_less(const vector& first, const vector& second) +{ + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + if (first.size() != second.size()) + return first.size() < second.size(); + + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; +} + +// Определяет, что длинное число равно нулю. +// Функция скопирована из 1_basics_div.cpp +bool is_zero(const vector& number) +{ + return (number.size() == 1 && number[0] == 0); +} + +// Возвращает неполное частное и остаток. +// Если знаменатель == 0, возвращает {0, 0}. +// Функция изменена по сравнению с 3_brute_force_div_1.cpp (двоичный поиск вместо перебора) +pair, vector> div_mod(const vector& numerator, const vector& denominator) +{ + // Если числитель или знаменатель == 0 + if (is_zero(numerator) || is_zero(denominator)) + return {vector{0}, vector{0}}; + + // Если числитель меньше знаменателя + if (first_is_less(numerator, denominator)) + return {vector{0}, numerator}; + + // Неполное частное + vector quotient; + quotient.resize(numerator.size() - denominator.size() + 1, 0); + + // Подбираем разряды, начиная со старшего разряда (с конца) + for (size_t i = quotient.size() - 1; i != size_t(-1); --i) + { + Digit left = 0; // Левая граница диапазона цифр + Digit right = base - 1; // Правая граница + Digit best_digit = 0; // Максимальная подходящая цифра + + while (left <= right) + { + // Делим диапазон цифр на две части + Digit middle = left + (right - left) / 2; // (left + right) / 2, но без переполнения + quotient[i] = middle; // Будем проверять эту цифру + + if (first_is_less(numerator, mul(quotient, denominator))) // Ведущие нули убираются при умножении + { + // Цифра больше, чем нужно. Отбрасываем правую половину диапазона + right = middle - 1; + } + else // quotient * denominator <= numerator + { + // Цифра нам подходит, но вдруг найдём цифру больше. Отбрасываем левую половину диапазона + best_digit = middle; + left = middle + 1; + } + } + + quotient[i] = best_digit; + } + + // Убираем ведущие нули + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + // Остаток = numerator - quotient * denominator + vector remainder = sub(numerator, mul(quotient, denominator)); + + return {quotient, remainder}; +} + +// Печатает два длинных числа и результат их деления +void show(const vector& a, const vector& b) +{ + pair, vector> result = div_mod(a, b); + + cout << to_string(a) << " / " << to_string(b) << " = " << to_string(result.first) + << " (remainder = " << to_string(result.second) << ")" << endl; +} + +// Функция изменена по сравнению с 3_brute_force_div_1.cpp (добавлен пример с маленьким знаменателем) +int main() +{ + show(to_num("0"), to_num("0")); // 0, 0 + show(to_num("5"), to_num("3")); // 1, 2 + show(to_num("123"), to_num("67")); // 1, 56 + show(to_num("512"), to_num("17")); // 30, 2 + show(to_num("999"), to_num("999")); // 1, 0 + show(to_num("9999"), to_num("000")); // 0, 0 + + // 566, 994340 + show(to_num("567000000"), to_num("1000010")); + + // 5000, 1111 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222222222222222222")); + + // 130686, 5304519702476588520600956014 + show(to_num("1293564533453453419234546456456456"), to_num("9898223443473294328742373747")); + + // 13068658137053464352525468785867118980456379357846, 530335 + show(to_num("12935645334534534192345464564564563443473294328742373747"), to_num("989822")); + + return 0; +} diff --git a/docs/4_long_div.md b/docs/4_long_div.md new file mode 100644 index 0000000..eae0462 --- /dev/null +++ b/docs/4_long_div.md @@ -0,0 +1,176 @@ +**Главы:** +1) [Основы длинной арифметики](1_basics.md) +2) [Длина неполного частного](2_div_len.md) +3) [Деление путём подбора разрядов](3_brute_force_div.md) +4) [Деление столбиком](4_long_div.md) + +---------------------------------------------- + +# 4. Деление столбиком + +Деление уголком позволяет делить числитель покусочно, а значит при проверках перемножаются более короткие числа. + +Допустим нам нужно разделить 10512 на 23. +Для простоты будем использовать десятичную СС и обычный порядок цифр. + +1\) Инициализация переменных: + +``` +результат = {} // Пустой массив +кусок = {} +``` + +2\) При сложении и умножении мы начинали с младших разрядов, а при делении начинаем со старших. +Копируем очередную цифру числителя в кусок: + +``` +// Для краткости пишем 1234 вместо {1, 2, 3, 4} +кусок = 1 // Дописали 1 +``` + +Дописывание цифры - это то же самое, что и `кусок = кусок * base + цифра`. + +3\) Делим кусок на знаменатель (подбором) и получаем очередную цифру результата: + +``` +разряд = кусок / знаменатель = 1 / 23 = 0 +результат = 0 // Ведущие нули потом нужно убрать +``` + +4\) Что осталось от куска (`кусок = кусок % знаменатель`): + +``` +кусок = кусок - разряд * знаменатель = 1 - 0 * 23 = 1 // Кусок не поменялся +``` + +5\) Повторяем алгоритм, начиная с шага 2, пока не кончатся цифры числителя: + +``` +кусок = 10 // Дописали 0 +разряд = кусок / знаменатель = 10 / 23 = 0 +результат = 00 // Дописали 0 +кусок = кусок - разряд * знаменатель = 10 - 0 * 23 = 10 // Кусок не поменялся + +кусок = 105 // Дописали 5 +разряд = кусок / знаменатель = 105 / 23 = 4 +результат = 004 // Дописали 4 +кусок = кусок - разряд * знаменатель = 105 - 4 * 23 = 13 + +кусок = 131 // Дописали 1 +разряд = кусок / знаменатель = 131 / 23 = 5 +результат = 0045 // Дописали 5 +кусок = кусок - разряд * знаменатель = 131 - 5 * 23 = 16 + +кусок = 162 // Дописали 2 +разряд = кусок / знаменатель = 162 / 23 = 7 +результат = 00457 // Дописали 7 +кусок = кусок - разряд * знаменатель = 162 - 7 * 23 = 1 +``` + +6\) В итоге кусок содержит остаток от деления: + +``` +остаток = кусок = 1 +результат = 457 // Убрали ведущие нули +``` + +Релизация: [4_long_div_1.cpp](4_long_div_1.cpp). + +## Оптмизируем деление куска на знаменатель + +Мы формировали кусок таким образом, чтобы деление `кусок / знаменатель` давало ровно одну цифру результата, +то есть `кусок < знаменатель · base`. Это открывает ряд путей для оптимизации. + +Если `кусок < знаменателя`, то `кусок / знаменатель = 0` и оптимизировать тут нечего. Поэтому далее +будем рассматривать ситуацию, когда `кусок ≥ знаменателя`. + +### Оптимизация 1 + +Обратите внимание, что кусок максимум на одну цифру длиннее знаменателя. То есть, +если в знаменателе одна цифра, то кусок состоит из максимум двух цифр, +а значит умещается в регистре процессора. В этом случае можно использовать обычное +процессорное деление и ничего подбирать не нужно. + +Релизация: [4_long_div_2.cpp](4_long_div_2.cpp). + +### Оптимизация 2 + +В литературе считается, что `длина куска = длина знаменателя + 1` (если `длина куска = длине знаменателя`, +то к куску дописывается ведущий 0). + +Существует теорема, что если поделить две старших цифры числителя на старшую цифру знаменателя, +то полученная `примерная_цифра` будет довольно близко к искомой цифре и не меньше неё: `примерная_цифра ≥ цифра`. +Это позволяет при двоичном поиске в качестве правой границы использовать не `base - 1`, а меньшее число. + +Но и значительные отклонения тоже бывают. Пример: + +``` +кусок = 94 +знаменатель = 19 +примерная_цифра = 09 / 1 = 9 +цифра = 94 / 19 = 4 +ошибка = примерная_цифра - цифра = 5 +``` + +Пример при base = 109: + +``` +кусок = 1873'135157604'149223893 +знаменатель = 3119'654553545 +примерная_цифра = 1873'135157604 / 3119 = 600556318 +цифра = 600430312 +ошибка = примерная_цифра - цифра = 126006 +``` + +Следует учесть, что иногда `примерная_цифра` может получиться больше `base - 1`, тогда её нужно ограничить сверху. +Пример: + +``` +кусок = 169 +знаменатель = 19 +примерная_цифра = 16 / 1 = 16 +примерная_цифра = min(примерная_цифра, 9) = 9 +цифра = 169/19 = 8 +ошибка = примерная_цифра - цифра = 1 +``` + +Релизация: [4_long_div_3.cpp](4_long_div_3.cpp). + +### Оптимизация 3 + +Ещё одна теорема утверждает, что если старшая цифра знаменателя ≥ `base / 2`, то `примерная_цифра` +будет больше искомой цифры максимум на 2, т.е. возможны всего 3 варианта: + +1. `цифра = примерная_цифра` +2. `цифра = примерная_цифра - 1` +3. `цифра = примерная_цифра - 2` + +Это позволяет отказаться от двоичного поиска и вернуться к перебору цифр, так как потребуется максимум две проверки. + +Если старшая цифра знаменателя < `base / 2`, тогда перед делением нужно +умножить числитель и знаменатель на коэффициент, который даст нам подходящий знаменатель. +Это называется нормализацией. +Искомое частное после нормализации дроби не изменится, так как +`(числитель · коэффициент) / (знаменатель · коэффициент) = числитель / знаменатель`, +а вот искомый остаток после всех вычислений нужно разделить на коэффициент. +Так как коэффициент - это одна цифра, то у нас для деления на коэффициент уже есть функция `div_by_digit()`. + +Реализация: [4_long_div_4.cpp](4_long_div_4.cpp). + +Литература: +1. https://ru.wikipedia.org/wiki/Деление_столбиком +2. https://en.wikipedia.org/wiki/Long_division +3. https://ru.wikipedia.org/wiki/Деление_с_остатком +4. https://www.codeproject.com/Articles/1276311/Multiple-Precision-Arithmetic-Division-Algorithm +5. http://algolist.manual.ru/maths/longnum.php +6. https://www.geeksforgeeks.org/divide-large-number-represented-string/ +7. [Pope, Stein. Multiple Precision Arithmetic](https://dl.acm.org/doi/10.1145/367487.367499) +8. [Krishnamurthy, Nandi. On the Normalization Requirement of Divisor in Divide-and-Correct Methods](https://dl.acm.org/doi/abs/10.1145/363848.363867) +9. [Дональд Кнут. Искусство программирования. Том 2. Третье издание. Раздел 4.3.1](https://djvu.online/file/AK1GKM4qtVd6r#p=311), + а также [упражнения](https://djvu.online/file/AK1GKM4qtVd6r#p=323) + и [ответы на упражнения](https://djvu.online/file/AK1GKM4qtVd6r#p=685) +10. [Кнут на английском](https://www.haio.ir/app/uploads/2022/01/The-art-of-computer-programming.-Vol.2.-Seminumerical-algorithms-by-Knuth-Donald-E-z-lib.org_.pdf#page=285) (перевод на русский — неточный) +11. https://rsdn.org/forum/alg/2355320.hot +12. [Hansen. Multiple-Length Division Revisited: A Tour of the Minefield](https://surface.syr.edu/eecs_techreports/166/) +13. [Brent, Zimmermann. Modern Computer Arithmetic](https://maths-people.anu.edu.au/~brent/pub/pub226.html) +14. [Verma. Implementing Basic Arithmetic for Large Integers: Division](https://mathsanew.com/articles_html/23/implementing_large_integers_division.html) diff --git a/docs/4_long_div_1.cpp b/docs/4_long_div_1.cpp new file mode 100644 index 0000000..d1dc318 --- /dev/null +++ b/docs/4_long_div_1.cpp @@ -0,0 +1,299 @@ +// Деление столбиком + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +using DDigit = uint64_t; +constexpr size_t chunk_length = 9; + +// Убеждаемся, что bool всегда преобразуется в 1 или 0 +static_assert(uint32_t(5 > 3) == 1 && uint32_t(bool(7)) == 1); + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend. +// Функция скопирована из 1_basics_sub_2.cpp +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + Digit borrow = 0; + + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = subtrahend[i] + borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Перемножает два положительных длинных числа столбиком. +// Функция скопирована из 1_basics_mul_2.cpp +vector mul(const vector& a, const vector& b) +{ + assert(a.size() > 0 && b.size() > 0); + + vector ret; + ret.resize(a.size() + b.size(), 0); + + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + DDigit v = ret[ret_index] + (DDigit)a[a_index] * b[b_index] + carry; + assert(v <= (DDigit)base * base - 1); + carry = Digit(v / base); + assert(carry < base); + ret[ret_index] = v % base; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Определяет, меньше ли первое число. +// Функция скопирована из 1_basics_div.cpp +bool first_is_less(const vector& first, const vector& second) +{ + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + if (first.size() != second.size()) + return first.size() < second.size(); + + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; +} + +// Определяет, что длинное число равно нулю. +// Функция скопирована из 1_basics_div.cpp +bool is_zero(const vector& number) +{ + return (number.size() == 1 && number[0] == 0); +} + +// Отбрасывает младший разряд +vector div_by_base(const vector& number) +{ + if (number.size() == 1) // Число < base + return vector{0}; + + // Отбрасываем цифру слева + return vector(number.cbegin() + 1, number.cend()); +} + +// Делит кусок числителя на знаменатель (подбором). +// Числитель и знаменатель таковы, что в результате всегда одна цифра. +// Это фрагмент функции div_mod() из 3_brute_force_div_2.cpp +Digit div_chunk(const vector& chunk, const vector& denominator) +{ + if (first_is_less(chunk, denominator)) + return 0; + + assert(chunk.size() == denominator.size() // Длина числителя равна длине знаменателя + || (chunk.size() == denominator.size() + 1 // Или числитель длиннее знаменателя на 1 цифру + && first_is_less(div_by_base(chunk), denominator))); // И эта цифра не лишняя + + Digit left = 0; + Digit right = base - 1; + Digit best_digit = 0; + + while (left <= right) + { + Digit middle = left + (right - left) / 2; + vector digit{middle}; + + if (first_is_less(chunk, mul(digit, denominator))) + { + right = middle - 1; + } + else + { + best_digit = middle; + left = middle + 1; + } + } + + return best_digit; +} + +// Возвращает неполное частное и остаток. +// Если знаменатель == 0, возвращает {0, 0}. +// Функция изменена по сравнению с 3_brute_force_div_2.cpp (делим числитель уголком, то есть покусочно) +pair, vector> div_mod(const vector& numerator, const vector& denominator) +{ + // Если числитель или знаменатель == 0 + if (is_zero(numerator) || is_zero(denominator)) + return {vector{0}, vector{0}}; + + // Если числитель меньше знаменателя + if (first_is_less(numerator, denominator)) + return {vector{0}, numerator}; + + vector quotient; // Неполное частное + vector chunk; // Текущий кусок числителя (и одновременно остаток) + + // Копируем цифры числителя, начиная с конца (со старших разрядов) + for (size_t i = numerator.size() - 1; i != size_t(-1); --i) + { + // Копируем очередную цифру числителя в начало куска (так как цифры хранятся в обратном порядке) + chunk.insert(chunk.begin(), numerator[i]); // chunk = chunk * base + numerator[i] + + // Убираем ведущие нули (когда chunk поделился без остатка и стал равен 0, а потом добавили 0) + while (chunk.size() > 1 && chunk.back() == 0) + chunk.pop_back(); + + // Делим кусок на знаменатель и получаем очередную цифру результата + Digit digit = div_chunk(chunk, denominator); + // Цифра равна 0, когда кусок меньше знаменателя + + // Добавляем цифру к результату + quotient.push_back(digit); + + // кусок -= цифра * знаменатель + chunk = sub(chunk, mul(vector{digit}, denominator)); // chunk %= denominator + // Если цифра равна 0, то кусок не меняется + } + + // Мы добавляли цифры в конец вектора, а не в начало, поэтому реверсируем + reverse(quotient.begin(), quotient.end()); + + // Убираем ведущие нули + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + return {quotient, chunk}; // В chunk находится остаток +} + +// Печатает два длинных числа и результат их деления +void show(const vector& a, const vector& b) +{ + pair, vector> result = div_mod(a, b); + + cout << to_string(a) << " / " << to_string(b) << " = " << to_string(result.first) + << " (remainder = " << to_string(result.second) << ")" << endl; +} + +// Функция изменена по сравнению с 3_brute_force_div_2.cpp (добавлен пример, создающий ведущий ноль в chunk) +int main() +{ + show(to_num("0"), to_num("0")); // 0, 0 + show(to_num("5"), to_num("3")); // 1, 2 + show(to_num("123"), to_num("67")); // 1, 56 + show(to_num("512"), to_num("17")); // 30, 2 + show(to_num("999"), to_num("999")); // 1, 0 + show(to_num("9999"), to_num("000")); // 0, 0 + + // 566, 994340 + show(to_num("567000000"), to_num("1000010")); + + // 5000, 1111 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222222222222222222")); + + // 130686, 5304519702476588520600956014 + show(to_num("1293564533453453419234546456456456"), to_num("9898223443473294328742373747")); + + // 13068658137053464352525468785867118980456379357846, 530335 + show(to_num("12935645334534534192345464564564563443473294328742373747"), to_num("989822")); + + // 3000000000, 0 + show(to_num("15000000000"), to_num("5")); + + return 0; +} diff --git a/docs/4_long_div_2.cpp b/docs/4_long_div_2.cpp new file mode 100644 index 0000000..b121ca7 --- /dev/null +++ b/docs/4_long_div_2.cpp @@ -0,0 +1,332 @@ +// Деление столбиком: оптимизация при делении на одну цифру + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +using DDigit = uint64_t; +constexpr size_t chunk_length = 9; + +// Убеждаемся, что bool всегда преобразуется в 1 или 0 +static_assert(uint32_t(5 > 3) == 1 && uint32_t(bool(7)) == 1); + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend. +// Функция скопирована из 1_basics_sub_2.cpp +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + Digit borrow = 0; + + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = subtrahend[i] + borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Перемножает два положительных длинных числа столбиком. +// Функция скопирована из 1_basics_mul_2.cpp +vector mul(const vector& a, const vector& b) +{ + assert(a.size() > 0 && b.size() > 0); + + vector ret; + ret.resize(a.size() + b.size(), 0); + + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + DDigit v = ret[ret_index] + (DDigit)a[a_index] * b[b_index] + carry; + assert(v <= (DDigit)base * base - 1); + carry = Digit(v / base); + assert(carry < base); + ret[ret_index] = v % base; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Определяет, меньше ли первое число. +// Функция скопирована из 1_basics_div.cpp +bool first_is_less(const vector& first, const vector& second) +{ + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + if (first.size() != second.size()) + return first.size() < second.size(); + + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; +} + +// Определяет, что длинное число равно нулю. +// Функция скопирована из 1_basics_div.cpp +bool is_zero(const vector& number) +{ + return (number.size() == 1 && number[0] == 0); +} + +// Без угадываний делит длинное число на цифру (столбиком). +// Если знаменатель == 0, возвращает {0, 0}. +// Функция повторяет div_mod() из 4_long_div_1.cpp, только не вызывается div_chunk() +static pair, vector> div_by_digit(const vector& numerator, Digit denominator) +{ + // Если числитель или знаменатель == 0 + if (is_zero(numerator) || denominator == 0) + return {vector{0}, vector{0}}; + + // Если числитель меньше знаменателя + if (first_is_less(numerator, vector{denominator})) + return {vector{0}, numerator}; + + vector quotient; // Неполное частное + DDigit chunk = 0; // Текущий кусок числителя (и одновременно остаток) + + // Извлекаем цифры числителя, начиная с конца (со старших разрядов) + for (size_t i = numerator.size() - 1; i != size_t(-1); --i) + { + chunk = chunk * base + numerator[i]; + + // Делим кусок на знаменатель и получаем очередную цифру результата + assert(chunk / denominator < base); + Digit digit = Digit(chunk / denominator); + + // Добавляем цифру к результату + quotient.push_back(digit); + + // chunk %= denominator + chunk -= (DDigit)digit * denominator; + assert(chunk < base); + } + + // Мы добавляли цифры в конец вектора, а не в начало, поэтому реверсируем + reverse(quotient.begin(), quotient.end()); + + // Убираем ведущие нули + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + return {quotient, {(Digit)chunk}}; // В chunk находится остаток +} + +// Отбрасывает младший разряд. +// Функция скопирована из 4_long_div_1.cpp +vector div_by_base(const vector& number) +{ + if (number.size() == 1) + return vector{0}; + + return vector(number.cbegin() + 1, number.cend()); +} + +// Делит кусок числителя на знаменатель (подбором). +// Числитель и знаменатель таковы, что в результате всегда одна цифра. +// Функция скопирована из 4_long_div_1.cpp +Digit div_chunk(const vector& chunk, const vector& denominator) +{ + if (first_is_less(chunk, denominator)) + return 0; + + assert(chunk.size() == denominator.size() + || (chunk.size() == denominator.size() + 1 + && first_is_less(div_by_base(chunk), denominator))); + + Digit left = 0; + Digit right = base - 1; + Digit best_digit = 0; + + while (left <= right) + { + Digit middle = left + (right - left) / 2; + vector digit{middle}; + + if (first_is_less(chunk, mul(digit, denominator))) + { + right = middle - 1; + } + else + { + best_digit = middle; + left = middle + 1; + } + } + + return best_digit; +} + +// Возвращает неполное частное и остаток. +// Если знаменатель == 0, возвращает {0, 0}. +// Функция изменена по сравнению с 4_long_div_1.cpp +// (вызываем оптимизированную функцию, если в знаменателе одна цифра) +pair, vector> div_mod(const vector& numerator, const vector& denominator) +{ + // Если в знаменателе одна цифра + if (denominator.size() == 1) + return div_by_digit(numerator, denominator[0]); + + if (is_zero(numerator) || is_zero(denominator)) + return {vector{0}, vector{0}}; + + if (first_is_less(numerator, denominator)) + return {vector{0}, numerator}; + + vector quotient; + vector chunk; + + for (size_t i = numerator.size() - 1; i != size_t(-1); --i) + { + chunk.insert(chunk.begin(), numerator[i]); + + while (chunk.size() > 1 && chunk.back() == 0) + chunk.pop_back(); + + Digit digit = div_chunk(chunk, denominator); + quotient.push_back(digit); + chunk = sub(chunk, mul(vector{digit}, denominator)); + } + + reverse(quotient.begin(), quotient.end()); + + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + return {quotient, chunk}; +} + +// Печатает два длинных числа и результат их деления +void show(const vector& a, const vector& b) +{ + pair, vector> result = div_mod(a, b); + + cout << to_string(a) << " / " << to_string(b) << " = " << to_string(result.first) + << " (remainder = " << to_string(result.second) << ")" << endl; +} + +int main() +{ + show(to_num("0"), to_num("0")); // 0, 0 + show(to_num("5"), to_num("3")); // 1, 2 + show(to_num("123"), to_num("67")); // 1, 56 + show(to_num("512"), to_num("17")); // 30, 2 + show(to_num("999"), to_num("999")); // 1, 0 + show(to_num("9999"), to_num("000")); // 0, 0 + + // 566, 994340 + show(to_num("567000000"), to_num("1000010")); + + // 5000, 1111 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222222222222222222")); + + // 130686, 5304519702476588520600956014 + show(to_num("1293564533453453419234546456456456"), to_num("9898223443473294328742373747")); + + // 13068658137053464352525468785867118980456379357846, 530335 + show(to_num("12935645334534534192345464564564563443473294328742373747"), to_num("989822")); + + // 3000000000, 0 + show(to_num("15000000000"), to_num("5")); + + return 0; +} diff --git a/docs/4_long_div_3.cpp b/docs/4_long_div_3.cpp new file mode 100644 index 0000000..18fd286 --- /dev/null +++ b/docs/4_long_div_3.cpp @@ -0,0 +1,344 @@ +// Деление столбиком: уменьшаем правую границу двоичного поиска + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +using DDigit = uint64_t; +constexpr size_t chunk_length = 9; + +// Убеждаемся, что bool всегда преобразуется в 1 или 0 +static_assert(uint32_t(5 > 3) == 1 && uint32_t(bool(7)) == 1); + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend. +// Функция скопирована из 1_basics_sub_2.cpp +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + Digit borrow = 0; + + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = subtrahend[i] + borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Перемножает два положительных длинных числа столбиком. +// Функция скопирована из 1_basics_mul_2.cpp +vector mul(const vector& a, const vector& b) +{ + assert(a.size() > 0 && b.size() > 0); + + vector ret; + ret.resize(a.size() + b.size(), 0); + + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + DDigit v = ret[ret_index] + (DDigit)a[a_index] * b[b_index] + carry; + assert(v <= (DDigit)base * base - 1); + carry = Digit(v / base); + assert(carry < base); + ret[ret_index] = v % base; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Определяет, меньше ли первое число. +// Функция скопирована из 1_basics_div.cpp +bool first_is_less(const vector& first, const vector& second) +{ + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + if (first.size() != second.size()) + return first.size() < second.size(); + + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; +} + +// Определяет, что длинное число равно нулю. +// Функция скопирована из 1_basics_div.cpp +bool is_zero(const vector& number) +{ + return (number.size() == 1 && number[0] == 0); +} + +// Без угадываний делит длинное число на цифру (столбиком). +// Если знаменатель == 0, возвращает {0, 0}. +// Функция скопирована из 4_long_div_2.cpp +static pair, vector> div_by_digit(const vector& numerator, Digit denominator) +{ + if (is_zero(numerator) || denominator == 0) + return {vector{0}, vector{0}}; + + if (first_is_less(numerator, vector{denominator})) + return {vector{0}, numerator}; + + vector quotient; + DDigit chunk = 0; + + for (size_t i = numerator.size() - 1; i != size_t(-1); --i) + { + chunk = chunk * base + numerator[i]; + assert(chunk / denominator < base); + Digit digit = Digit(chunk / denominator); + quotient.push_back(digit); + chunk -= (DDigit)digit * denominator; + assert(chunk < base); + } + + reverse(quotient.begin(), quotient.end()); + + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + return {quotient, {(Digit)chunk}}; +} + +// Отбрасывает младший разряд. +// Функция скопирована из 4_long_div_1.cpp +vector div_by_base(const vector& number) +{ + if (number.size() == 1) + return vector{0}; + + return vector(number.cbegin() + 1, number.cend()); +} + +// Делит кусок числителя на знаменатель (подбором). +// Числитель и знаменатель таковы, что в результате всегда одна цифра. +// Функция изменена по сравнению с 4_long_div_2.cpp +// (правая граница ближе к искомой цифре) +Digit div_chunk(const vector& chunk, const vector& denominator) +{ + if (first_is_less(chunk, denominator)) + return 0; + + assert(chunk.size() == denominator.size() + || (chunk.size() == denominator.size() + 1 + && first_is_less(div_by_base(chunk), denominator))); + + // Две старшие цифры куска. + // Если длина куска равна длине знаменателя, то старшая цифра будет 0 + DDigit chunk_2_digits = chunk.back(); + if (chunk.size() != denominator.size()) + chunk_2_digits = chunk_2_digits * base + chunk[chunk.size() - 2]; + + // Делим две старшие цифры куска на старшую цифру знаменателя + DDigit dd_right = chunk_2_digits / denominator.back(); + //assert(dd_right < base - 1); // Для тестирования + + // Ограничиваем right сверху + Digit right = (Digit)min(dd_right, DDigit(base - 1)); + + //Digit first_right = right; // Для тестирования + + Digit left = 0; + Digit best_digit = 0; + + while (left <= right) + { + Digit middle = left + (right - left) / 2; + vector digit{middle}; + + if (first_is_less(chunk, mul(digit, denominator))) + { + right = middle - 1; + } + else + { + best_digit = middle; + left = middle + 1; + } + } + + //Digit error = first_right - best_digit; // Для тестирования + + return best_digit; +} + +// Возвращает неполное частное и остаток. +// Если знаменатель == 0, возвращает {0, 0}. +// Функция скопирована из 4_long_div_2.cpp +pair, vector> div_mod(const vector& numerator, const vector& denominator) +{ + if (denominator.size() == 1) + return div_by_digit(numerator, denominator[0]); + + if (is_zero(numerator) || is_zero(denominator)) + return {vector{0}, vector{0}}; + + if (first_is_less(numerator, denominator)) + return {vector{0}, numerator}; + + vector quotient; + vector chunk; + + for (size_t i = numerator.size() - 1; i != size_t(-1); --i) + { + chunk.insert(chunk.begin(), numerator[i]); + + while (chunk.size() > 1 && chunk.back() == 0) + chunk.pop_back(); + + Digit digit = div_chunk(chunk, denominator); + quotient.push_back(digit); + chunk = sub(chunk, mul(vector{digit}, denominator)); + } + + reverse(quotient.begin(), quotient.end()); + + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + return {quotient, chunk}; +} + +// Печатает два длинных числа и результат их деления +void show(const vector& a, const vector& b) +{ + pair, vector> result = div_mod(a, b); + + cout << to_string(a) << " / " << to_string(b) << " = " << to_string(result.first) + << " (remainder = " << to_string(result.second) << ")" << endl; +} + +// Функция изменена по сравнению с 4_long_div_2.cpp +// (добавлен пример, в котором right нужно ограничивать сверху, и пример с большой ошибкой) +int main() +{ + show(to_num("0"), to_num("0")); // 0, 0 + show(to_num("5"), to_num("3")); // 1, 2 + show(to_num("123"), to_num("67")); // 1, 56 + show(to_num("512"), to_num("17")); // 30, 2 + show(to_num("999"), to_num("999")); // 1, 0 + show(to_num("9999"), to_num("000")); // 0, 0 + + // 566, 994340 + show(to_num("567000000"), to_num("1000010")); + + // 5000, 1111 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222222222222222222")); + + // 130686, 5304519702476588520600956014 + show(to_num("1293564533453453419234546456456456"), to_num("9898223443473294328742373747")); + + // 13068658137053464352525468785867118980456379357846, 530335 + show(to_num("12935645334534534192345464564564563443473294328742373747"), to_num("989822")); + + // 3000000000, 0 + show(to_num("15000000000"), to_num("5")); + + // 999999997, 3600000000 + show(to_num("100000000600000000900000000"), to_num("100000000900000000")); + + // 600430312, 686904167853 + show(to_num("1873135157604149223893"), to_num("3119654553545")); + + return 0; +} diff --git a/docs/4_long_div_4.cpp b/docs/4_long_div_4.cpp new file mode 100644 index 0000000..478f66f --- /dev/null +++ b/docs/4_long_div_4.cpp @@ -0,0 +1,349 @@ +// Деление столбиком: нормализация + +#include +#include +#include +#include +#include +#include + +using namespace std; + + +constexpr uint32_t base = 1'000'000'000; +using Digit = uint32_t; +using DDigit = uint64_t; +constexpr size_t chunk_length = 9; + +// Убеждаемся, что bool всегда преобразуется в 1 или 0 +static_assert(uint32_t(5 > 3) == 1 && uint32_t(bool(7)) == 1); + +// Вычитает два положительных длинных числа столбиком. +// Уменьшаемое minuend должно быть >= вычитаемого subtrahend. +// Функция скопирована из 1_basics_sub_2.cpp +vector sub(const vector& minuend, const vector& subtrahend) +{ + vector ret; + ret.resize(minuend.size()); + + Digit borrow = 0; + + for (size_t i = 0; i < subtrahend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = subtrahend[i] + borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + for (size_t i = subtrahend.size(); i < minuend.size(); ++i) + { + Digit minuend_digit = minuend[i]; + Digit subtrahend_digit = borrow; + borrow = minuend_digit < subtrahend_digit; + assert(borrow == 0 || borrow == 1); + minuend_digit += base * borrow; + ret[i] = minuend_digit - subtrahend_digit; + } + + assert(borrow == 0); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Перемножает два положительных длинных числа столбиком. +// Функция скопирована из 1_basics_mul_2.cpp +vector mul(const vector& a, const vector& b) +{ + assert(a.size() > 0 && b.size() > 0); + + vector ret; + ret.resize(a.size() + b.size(), 0); + + for (size_t a_index = 0; a_index < a.size(); ++a_index) + { + Digit carry = 0; + + for (size_t b_index = 0; b_index < b.size(); ++b_index) + { + size_t ret_index = a_index + b_index; + DDigit v = ret[ret_index] + (DDigit)a[a_index] * b[b_index] + carry; + assert(v <= (DDigit)base * base - 1); + carry = Digit(v / base); + assert(carry < base); + ret[ret_index] = v % base; + } + + assert(ret[a_index + b.size()] == 0); + ret[a_index + b.size()] = carry; + } + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Преобразует длинное число в строку. +// Функция скопирована из 1_basics_mul_2.cpp +string to_string(const vector& big_num) +{ + assert(big_num.size() > 0); + + string ret = std::to_string(big_num.back()); + + for (size_t i = big_num.size() - 2; i != size_t(-1); --i) + ret += format("{0:0>{1}}", big_num[i], chunk_length); + + return ret; +} + +// Преобразует строку в длинное число. +// Функция скопирована из 1_basics_mul_2.cpp +vector to_num(const string& str) +{ + if (str.empty()) + return vector{0}; + + size_t num_chunks = str.length() / chunk_length; + size_t rest_length = str.length() - num_chunks * chunk_length; + size_t first_chunk_length; + + if (!rest_length) + { + first_chunk_length = chunk_length; + } + else + { + first_chunk_length = rest_length; + ++num_chunks; + } + + vector ret; + + for (size_t i = 0; i < num_chunks - 1; ++i) + { + size_t chunk_start = str.size() - (i + 1) * chunk_length; + string chunk = str.substr(chunk_start, chunk_length); + ret.push_back(stoi(chunk)); + } + + string first_chunk = str.substr(0, first_chunk_length); + ret.push_back(stoi(first_chunk)); + + while (ret.size() > 1 && ret.back() == 0) + ret.pop_back(); + + return ret; +} + +// Определяет, меньше ли первое число. +// Функция скопирована из 1_basics_div.cpp +bool first_is_less(const vector& first, const vector& second) +{ + assert(first.size() == 1 || first.back() != 0); + assert(second.size() == 1 || second.back() != 0); + + if (first.size() != second.size()) + return first.size() < second.size(); + + for (size_t i = first.size() - 1; i != size_t(-1); --i) + { + if (first[i] != second[i]) + return first[i] < second[i]; + } + + return false; +} + +// Определяет, что длинное число равно нулю. +// Функция скопирована из 1_basics_div.cpp +bool is_zero(const vector& number) +{ + return (number.size() == 1 && number[0] == 0); +} + +// Без угадываний делит длинное число на цифру (столбиком). +// Если знаменатель == 0, возвращает {0, 0}. +// Функция скопирована из 4_long_div_2.cpp +static pair, vector> div_by_digit(const vector& numerator, Digit denominator) +{ + if (is_zero(numerator) || denominator == 0) + return {vector{0}, vector{0}}; + + if (first_is_less(numerator, vector{denominator})) + return {vector{0}, numerator}; + + vector quotient; + DDigit chunk = 0; + + for (size_t i = numerator.size() - 1; i != size_t(-1); --i) + { + chunk = chunk * base + numerator[i]; + assert(chunk / denominator < base); + Digit digit = Digit(chunk / denominator); + quotient.push_back(digit); + chunk -= (DDigit)digit * denominator; + assert(chunk < base); + } + + reverse(quotient.begin(), quotient.end()); + + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + return {quotient, {(Digit)chunk}}; +} + +// Отбрасывает младший разряд. +// Функция скопирована из 4_long_div_1.cpp +vector div_by_base(const vector& number) +{ + if (number.size() == 1) + return vector{0}; + + return vector(number.cbegin() + 1, number.cend()); +} + +// Делит кусок числителя на знаменатель. +// Числитель и знаменатель таковы, что в результате всегда одна цифра. +// К тому же аргументы должны быть нормализованы (старшая цифра знаменателя >= base / 2). +// Функция изменена по сравнению с 4_long_div_3.cpp (перебор вместо двоичного поиска) +Digit div_chunk(const vector& chunk, const vector& denominator) +{ + if (first_is_less(chunk, denominator)) + return 0; + + assert(chunk.size() == denominator.size() + || (chunk.size() == denominator.size() + 1 + && first_is_less(div_by_base(chunk), denominator))); + + // Проверяем, что знаменатель нормализован + assert(denominator.back() >= base / 2); + + DDigit chunk_2_digits = chunk.back(); + if (chunk.size() != denominator.size()) + chunk_2_digits = chunk_2_digits * base + chunk[chunk.size() - 2]; + + // Делим две старшие цифры куска на старшую цифру знаменателя + Digit digit = (Digit)min(chunk_2_digits / denominator.back(), DDigit(base - 1)); + +#ifndef NDEBUG + size_t misses_count = 0; // Число промахов +#endif + + // while (chunk < digit * denominator) // Пока цифра слишком большая + while (first_is_less(chunk, mul(vector{digit}, denominator))) + { + --digit; + assert(++misses_count <= 2); + } + + return digit; +} + +// Возвращает неполное частное и остаток. +// Если знаменатель == 0, возвращает {0, 0}. +// Функция изменена по сравнению с 4_long_div_3.cpp (добавлена нормализация) +pair, vector> div_mod(const vector& numerator, const vector& denominator) +{ + if (denominator.size() == 1) + return div_by_digit(numerator, denominator[0]); + + if (is_zero(numerator) || is_zero(denominator)) + return {vector{0}, vector{0}}; + + if (first_is_less(numerator, denominator)) + return {vector{0}, numerator}; + + // Нормализующий множитель + Digit scale = base / (denominator.back() + 1); + assert(scale <= base / 2); // Старшая цифра denominator не может быть 0, т.е. минимум 1 + + // Нормализованный числитель + vector norm_numerator = (scale == 1) + ? numerator + : mul(vector{scale}, numerator); + + // Нормализованный знаменатель + vector norm_denominator = (scale == 1) + ? denominator + : mul(vector{scale}, denominator); + + vector quotient; + vector chunk; + + for (size_t i = norm_numerator.size() - 1; i != size_t(-1); --i) + { + chunk.insert(chunk.begin(), norm_numerator[i]); + + while (chunk.size() > 1 && chunk.back() == 0) + chunk.pop_back(); + + Digit digit = div_chunk(chunk, norm_denominator); + quotient.push_back(digit); + chunk = sub(chunk, mul(vector{digit}, norm_denominator)); + } + + reverse(quotient.begin(), quotient.end()); + + while (quotient.size() > 1 && quotient.back() == 0) + quotient.pop_back(); + + // Денормализуем остаток + if (scale != 1) + { + pair, vector> denorm_chunk = div_by_digit(chunk, scale); + assert(denorm_chunk.second == vector{0}); // Должно поделиться без остатка + chunk = denorm_chunk.first; + } + + return {quotient, chunk}; +} + +// Печатает два длинных числа и результат их деления +void show(const vector& a, const vector& b) +{ + pair, vector> result = div_mod(a, b); + + cout << to_string(a) << " / " << to_string(b) << " = " << to_string(result.first) + << " (remainder = " << to_string(result.second) << ")" << endl; +} + +int main() +{ + show(to_num("0"), to_num("0")); // 0, 0 + show(to_num("5"), to_num("3")); // 1, 2 + show(to_num("123"), to_num("67")); // 1, 56 + show(to_num("512"), to_num("17")); // 30, 2 + show(to_num("999"), to_num("999")); // 1, 0 + show(to_num("9999"), to_num("000")); // 0, 0 + + // 566, 994340 + show(to_num("567000000"), to_num("1000010")); + + // 5000, 1111 + show(to_num("11111111111111111111111111111111111111"), to_num("2222222222222222222222222222222222")); + + // 130686, 5304519702476588520600956014 + show(to_num("1293564533453453419234546456456456"), to_num("9898223443473294328742373747")); + + // 13068658137053464352525468785867118980456379357846, 530335 + show(to_num("12935645334534534192345464564564563443473294328742373747"), to_num("989822")); + + // 3000000000, 0 + show(to_num("15000000000"), to_num("5")); + + // 999999997, 3600000000 + show(to_num("100000000600000000900000000"), to_num("100000000900000000")); + + // 600430312, 686904167853 + show(to_num("1873135157604149223893"), to_num("3119654553545")); + + return 0; +} diff --git a/docs/libs.md b/docs/libs.md new file mode 100644 index 0000000..96852cc --- /dev/null +++ b/docs/libs.md @@ -0,0 +1,38 @@ +# Другие подобные библиотеки + +| Число проектов | Тег | +|:--------------:|:----------------------------------------------------------------------------------------:| +| 323 | [biginteger](https://github.com/topics/biginteger) | +| 245 | [bigint](https://github.com/topics/bigint) | +| 197 | [arbitrary-precision](https://github.com/topics/arbitrary-precision) | +| 162 | [bignumber](https://github.com/topics/bignumber) | +| 102 | [bignum](https://github.com/topics/bignum) | +| 90 | [bigdecimal](https://github.com/topics/bigdecimal) | +| 88 | [rational-numbers](https://github.com/topics/rational-numbers) | +| 53 | [integer-arithmetic](https://github.com/topics/integer-arithmetic) | +| 45 | [big-integer](https://github.com/topics/big-integer) | +| 42 | [large-numbers](https://github.com/topics/large-numbers) | +| 37 | [bignumbers](https://github.com/topics/bignumbers) | +| 35 | [biginteger-cpp](https://github.com/topics/biginteger-cpp) | +| 31 | [arbitrary-precision-integers](https://github.com/topics/arbitrary-precision-integers) | +| 31 | [big-number](https://github.com/topics/big-number) | +| 27 | [big-int](https://github.com/topics/big-int) | +| 24 | [multi-precision](https://github.com/topics/multi-precision) | +| 24 | [biginteger-library](https://github.com/topics/biginteger-library) | +| 21 | [big-numbers](https://github.com/topics/big-numbers) | +| 13 | [bigintegers](https://github.com/topics/bigintegers) | +| 8 | [multiple-precision](https://github.com/topics/multiple-precision) | +| 6 | [bigrational](https://github.com/topics/bigrational) | +| 5 | [big-integers](https://github.com/topics/big-integers) | +| 5 | [rational-arithmetic](https://github.com/topics/rational-arithmetic) | +| 4 | [large-number](https://github.com/topics/large-number) | +| 4 | [largenumbers](https://github.com/topics/largenumbers) | +| 3 | [infinite-precision](https://github.com/topics/infinite-precision) | +| 0 | [largenumber](https://github.com/topics/largenumber) | + +Ещё библиотеки: +* https://en.wikipedia.org/wiki/List_of_arbitrary-precision_arithmetic_software +* http://hvks.com/Numerical/arbitrary_precision.html +* https://mattmccutchen.net/bigint/ (public domain) +* https://github.com/weidai11/cryptopp +* https://github.com/llvm/llvm-project/blob/900be9013fdc3bab9fce906f8a71e59ecd8873b4/llvm/lib/Support/APInt.cpp#L1260 diff --git a/docs/markdown.md b/docs/markdown.md new file mode 100644 index 0000000..3835c1e --- /dev/null +++ b/docs/markdown.md @@ -0,0 +1,28 @@ +# Полезные символы + +Символы, которые используются в документации, но которых нет на клавиатуре: + +| Символ | Unicode | В HTML | Источники | Примечания | +|:------:|:-------:|-----------------------------------------------------|------------|-----------| +| − | U+2212 | `−` `−` `−` | [−](https://www.compart.com/en/unicode/U+2212), [Минус](https://ru.wikipedia.org/wiki/Минус), [Плюс и минус](https://ru.wikipedia.org/wiki/Знаки_плюса_и_минуса) | Не путать с [дефисом-минусом](https://ru.wikipedia.org/wiki/Дефис#Дефис_и_компьютеры) на клавиатуре. Минус используется в формулах, а дефис-минус в реальном коде | +| — | U+2014 | `—` `—` `—` | [—](https://www.compart.com/en/unicode/U+2014), [Тире](https://ru.wikipedia.org/wiki/Тире) | Длинное тире обозначает пропуск в предложении | +| · | U+00B7 | `·` `·` `·` `·` | [·](https://www.compart.com/en/unicode/U+00B7), [Знак умножения](https://ru.wikipedia.org/wiki/Знак_умножения) | [⋅](https://www.compart.com/en/unicode/U+22C5) не используется, так как есть не во всех шрифтах | +| … | U+2026 | `…` `…` `…` `…` | […](https://www.compart.com/en/unicode/U+2026), [Многоточие](https://ru.wikipedia.org/wiki/Многоточие), [Ellipsis](https://en.wikipedia.org/wiki/Ellipsis) | | +| ⇒ | U+21D2 | `⇒` `⇒` `⇒` | [⇒](https://www.compart.com/en/unicode/U+21D2) | | +| ⇔ | U+21D4 | `⇔` `⇔` `⇔` | [⇔](https://www.compart.com/en/unicode/U+21D4) | | +| ≤ | U+2264 | `≤` `≤` `≤` | [≤](https://www.compart.com/en/unicode/U+2264) | [⩽](https://www.compart.com/en/unicode/U+2A7D) не используется, так как есть не во всех шрифтах | +| ≥ | U+2265 | `≥` `≥` `≥` | [≥](https://www.compart.com/en/unicode/U+2265) | [⩾](https://www.compart.com/en/unicode/U+2A7E) не используется, так как есть не во всех шрифтах | + +Дополнительный источник: https://ru.wikipedia.org/wiki/Таблица_математических_символов + +# HTML-теги + +* Разрыв строки в любом месте: `
` +* `104` выглядит как 104 +* `104` выглядит как 104 + +Внутри блоков кода (`` `…` `` и `` ```…``` ``) нельзя использовать HTML-теги, но ведь можно и сами блоки кода создавать с помощью HTML-тегов: +* `a − b` выглядит как a − b +* `
a − b
` выглядит как
a − b
+ +Источник: https://daringfireball.net/projects/markdown/syntax diff --git a/docs/names.md b/docs/names.md new file mode 100644 index 0000000..10bd0c5 --- /dev/null +++ b/docs/names.md @@ -0,0 +1,5 @@ +# Названия больших чисел + +* +* +* diff --git a/force_assert.hpp b/force_assert.hpp new file mode 100644 index 0000000..045c6fc --- /dev/null +++ b/force_assert.hpp @@ -0,0 +1,16 @@ +// Copyright (c) the Dviglo project +// License: MIT + +#pragma once + +#ifdef assert + #error "Don't include force_assert.h after cassert" +#endif + +// Макрос NDEBUG определён в конфигурациях Release, RelWithDebInfo, MinSizeRel +// и не определён в конфигурации Debug. +// Макрос NDEBUG отключает assert(): https://en.cppreference.com/w/cpp/error/assert +// Нам же нужно, чтобы assert() был доступен даже в релизных конфигурациях +#undef NDEBUG + +#include diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..88c6920 --- /dev/null +++ b/license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) the Dviglo project. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..39cb1b4 --- /dev/null +++ b/readme.md @@ -0,0 +1,11 @@ +# BigInt + +Лицензия: MIT. + +## Использование + +Скопируйте файлы big_int.cpp и big_int.hpp в свой проект. + +## Документация + +В папке docs [туториал](docs/1_basics.md) с описанием алгоритмов. diff --git a/tester.cpp b/tester.cpp new file mode 100644 index 0000000..9ea0e79 --- /dev/null +++ b/tester.cpp @@ -0,0 +1,307 @@ +// Copyright (c) the Dviglo project +// License: MIT + +#include "force_assert.hpp" + +#include "big_int.hpp" + +#ifndef __clang__ + // TODO: Не компилируется на сервере ГитХаба + // https://github.com/actions/runner-images/issues/8659 + #include +#endif + +#include + +using namespace dviglo; +using namespace std; + + +void run() +{ + // BigInt() + assert(BigInt().to_string() == "0"); + + // BigInt(int32_t value) + assert(BigInt(0).to_string() == "0"); + assert(BigInt(-0).to_string() == "0"); + assert(BigInt(-1).to_string() == "-1"); + assert(BigInt(322).to_string() == "322"); + + // Типы int8_t, uint8_t, int16_t, uint16_t всегда используют конструктор BigInt(int32_t value) + assert(BigInt((int8_t)-12).to_string() == "-12"); + assert(BigInt((uint8_t)-12).to_string() == "244"); + assert(BigInt((int16_t)-12).to_string() == "-12"); + assert(BigInt((uint16_t)-12).to_string() == "65524"); + + assert(BigInt(0x7FFFFFFF).to_string() == "2147483647"); // BigInt(int32_t value) + assert(BigInt(-0x7FFFFFFF).to_string() == "-2147483647"); // BigInt(int32_t value) + assert(BigInt(0x80000000).to_string() == "2147483648"); // BigInt(uint32_t value) + assert(BigInt(-0x80000000).to_string() == "2147483648"); // BigInt(uint32_t value) + assert(BigInt(-0x7FFFFFFF - 1).to_string() == "-2147483648"); // BigInt(int32_t value) + assert(BigInt((int32_t)-0x80000000).to_string() == "-2147483648"); // BigInt(int32_t value) + assert(BigInt((int32_t)0x80000000).to_string() == "-2147483648"); // BigInt(int32_t value) + assert(BigInt(0xFFFFFFFF).to_string() == "4294967295"); // BigInt(uint32_t value) + + assert(BigInt(0x7FFFFFFFFFFFFFFF).to_string() == "9223372036854775807"); // BigInt(int64_t value) + assert(BigInt(-0x7FFFFFFFFFFFFFFF).to_string() == "-9223372036854775807"); // BigInt(int64_t value) + assert(BigInt(0x8000000000000000).to_string() == "9223372036854775808"); // BigInt(uint64_t value) + assert(BigInt(-0x8000000000000000).to_string() == "9223372036854775808"); // BigInt(uint64_t value) + assert(BigInt(-0x7FFFFFFFFFFFFFFF - 1).to_string() == "-9223372036854775808"); // BigInt(int64_t value) + assert(BigInt((int64_t)-0x8000000000000000).to_string() == "-9223372036854775808"); // BigInt(int64_t value) + assert(BigInt((int64_t)0x8000000000000000).to_string() == "-9223372036854775808"); // BigInt(int64_t value) + assert(BigInt(0xFFFFFFFFFFFFFFFF).to_string() == "18446744073709551615"); // BigInt(uint64_t value) + + // BigInt(const string& str) + assert(BigInt("").to_string() == "0"); + assert(BigInt("1abc").to_string() == "0"); + assert(BigInt("0").to_string() == "0"); + assert(BigInt("00000").to_string() == "0"); + assert(BigInt("-0").to_string() == "0"); + assert(BigInt("+00000").to_string() == "0"); + assert(BigInt("-").to_string() == "0"); + assert("-abc"_bi.to_string() == "0"); + assert("3"_bi.to_string() == "3"); + assert("-7"_bi.to_string() == "-7"); + assert("123456789"_bi.to_string() == "123456789"); + assert("-123456789"_bi.to_string() == "-123456789"); + assert("123000789"_bi.to_string() == "123000789"); + assert("-1230000"_bi.to_string() == "-1230000"); + assert("0001230000"_bi.to_string() == "1230000"); + assert("-0001230000"_bi.to_string() == "-1230000"); + + { + BigInt bi("-99999999999999999999999999999999999999999999999999999999999999999999" + "999999999999999999999999999999999999999999999999999999999999999999999" + "999999999999999999999999999999999999999999999999999999999999999999999"); + + assert(bi.to_string() == + "-99999999999999999999999999999999999999999999999999999999999999999999" + "999999999999999999999999999999999999999999999999999999999999999999999" + "999999999999999999999999999999999999999999999999999999999999999999999"); + } + + // Сравнение + assert("+0"_bi == "-0"_bi); + assert("10"_bi != "100"_bi); + assert("10"_bi != "-10"_bi); + assert("10"_bi < "100"_bi); + assert("10"_bi <= "100"_bi); + assert("10"_bi > "-100"_bi); + assert("-10"_bi > "-100"_bi); + + // Сумма чисел с одинаковыми знаками + assert(("0"_bi + "0"_bi).to_string() == "0"); + assert(("000"_bi + "000"_bi).to_string() == "0"); + assert(("1"_bi + "2"_bi).to_string() == "3"); + assert(("1000"_bi + "200"_bi).to_string() == "1200"); + assert(("-1000"_bi + "-234"_bi).to_string() == "-1234"); + assert(("-1000"_bi - "234"_bi).to_string() == "-1234"); + assert(("-1000"_bi - "0"_bi).to_string() == "-1000"); + assert(("9999999999999999999999"_bi + "9999999999999999999999"_bi).to_string() == "19999999999999999999998"); + assert(("9999999999999999999999"_bi + "1"_bi).to_string() == "10000000000000000000000"); + assert((BigInt(1) + 0xFFFFFFFFFFFFFFFF).to_string() == "18446744073709551616"); + assert((BigInt(0xFFFFFFFFFFFFFFFF) + 0xFFFFFFFFFFFFFFFF).to_string() == "36893488147419103230"); + assert(("999999999"_bi + 1).to_string() == "1000000000"); + + // Сумма чисел с разными знаками + assert(("000"_bi - "000"_bi).to_string() == "0"); + assert(("1000"_bi - "1000"_bi).to_string() == "0"); + assert(("1000"_bi - "234"_bi).to_string() == "766"); + assert(("234"_bi - "1000"_bi).to_string() == "-766"); + assert(("1000"_bi - "0"_bi).to_string() == "1000"); + assert(("0"_bi - "034005"_bi).to_string() == "-34005"); + assert(("10000000000000000000000"_bi - "1"_bi).to_string() == "9999999999999999999999"); + assert(("-10000000000000000000000"_bi + "1"_bi).to_string() == "-9999999999999999999999"); + assert((BigInt(1) - 0xFFFFFFFFFFFFFFFF).to_string() == "-18446744073709551614"); + assert(("1000000000"_bi - 1).to_string() == "999999999"); + + // Умножение + assert(("0"_bi * "0"_bi).to_string() == "0"); + assert(("1"_bi * "1"_bi).to_string() == "1"); + assert(("1"_bi * "9999999999999999999999"_bi).to_string() == "9999999999999999999999"); + assert(("0"_bi * "9999999999999999999999"_bi).to_string() == "0"); + assert(("10"_bi * "2"_bi).to_string() == "20"); + assert(("-99999"_bi * "99999"_bi).to_string() == "-9999800001"); + assert(("-99999"_bi * 0).is_zero()); + + { + BigInt bi1("-99999999999999999999999999999999999999999999999999999999999999999999"); + BigInt bi2("99999999999999999999999999999999999999999999999999999999999999999999"); + string str = (bi1 * bi2).to_string(); + + assert(str == "-99999999999999999999999999999999999999999999999999999999999999999998" + "00000000000000000000000000000000000000000000000000000000000000000001"); + } + + // Деление + assert(("0"_bi / "-0"_bi).to_string() == "0"); + assert(("0"_bi % "0"_bi).to_string() == "0"); + assert((0 % "-99999"_bi).is_zero()); + assert((0 / "-99999"_bi).is_zero()); + assert(("-99999"_bi / 0 ).is_zero()); + assert(("-99999"_bi % 0).is_zero()); + assert(("999999"_bi / 1234).to_string() == "810"); + assert(("999999"_bi % 1234).to_string() == "459"); + assert(1234 / "999999"_bi == 0); + assert(1234 % "999999"_bi == 1234); + assert("15000000000"_bi / 5 == 3000000000); + assert("15000000000"_bi % 5 == 0); + +//#define DV_DIV_BENCHMARK 1 + +#if DV_DIV_BENCHMARK + for (size_t i = 0; i < 500; ++i) + { +#endif + + { + BigInt num("231938472342346234324323000000000000000000000000000000002319384723423462343243229"); + BigInt denom("231938472342346234324323"); + assert((num / denom).to_string() == "1000000000000000000000000000000000000000000000000000000009"); + assert((num % denom).to_string() == "231938472342346234324322"); + } + + { + BigInt num("-99999999999999999999999999999999999999999999999999999999999999999998" + "00000000000000000000000000000000000000000000000000000000000000000001"); + + BigInt denom("-99999999999999999999999999999999999999999999999999999999999999999999"); + string str = (num / denom).to_string(); + assert(str == "99999999999999999999999999999999999999999999999999999999999999999999"); + str = (num % denom).to_string(); + assert(str == "0"); + } + + { + BigInt num("-435802934583983490082374236423984934573467632463298429342384273947239" + "4092837842374203943249384238234349872398472742934729342634293423466324" + "2648901364732130846878735410663454303254567487340544878735487765435465" + "0743776536457436534765347653567346573457734736475348573456376547982347"); + + BigInt denom("1574"); + string str = (num / denom).to_string(); + + assert(str == "-27687607025666041301294424169249360519280027475431920542718187" + "671362097159071425503201672486558057397394360854337629826742964" + "639729501056697998031362599829000558281709443109468395532075819" + "914154215684730263954939248826966800288104532887774291396026539" + "6910085990199146363753483"); + + str = (num % denom).to_string(); + assert(str == "-105"); + } + + { + BigInt num("-349085734578253735348534758935835793475395734759352656347535634755774" + "0723580375234537583583576347563752502027356273639176269161951915925632" + "0014317353754375627953238756791061730147353378930226652053001376145017" + "0425734653617324023456200264301460307750443478653045610544743561010347"); + + BigInt denom("-503485083745737260309431837463722349240239482347237423742734082340488" + "4981938283048785710573643765473563256295475639847513745638568435638456" + "0283451320657365783456387416275462738456832574567483538456348564382223" + "09374653294569378456392652346583573175463856345638456323"); + + string str = (num / denom).to_string(); + assert(str == "69333878171958"); + + str = (num % denom).to_string(); + + assert(str == "-355277755598817268744652856648785435651554474282312186955910523919129" + "5638576798214686747968542159396972700770195868886908838686710009987281" + "9161566184986846734557785127378321974842925484023620272716472703539239" + "06043797282605802653416184941793268609238254235294619913"); + } + +#if DV_DIV_BENCHMARK + } +#endif + + { + // https://en.cppreference.com/w/cpp/language/operator_arithmetic#Built-in_multiplicative_operators + // (a/b)*b + a%b == a + BigInt a("9999999843"); + BigInt b("99999998"); + assert(a/b*b + a%b == a); + + a = "-9999999843"_bi; + b = "99999998"_bi; + assert(a/b*b + a%b == a); + + a = "9999999843"_bi; + b = "-99999998"_bi; + assert(a/b*b + a%b == a); + + a = "-9999999843"_bi; + b = "-99999998"_bi; + assert(a/b*b + a%b == a); + + a = 0; + b = "-99999998"_bi; + assert(a/b*b + a%b == a); + + // Этот тест не должен быть успешным. Хотя деление на 0 и возвращает 0 + // без срабатывания исключения, но формула не работает + //a = "-99999998"_bi; + //b = 0; + //assert(a/b*b + a%b == a); + } + +//#define DV_DIV_RANDOM 1 + +#if DV_DIV_RANDOM + { + // Для отладки багов в делении + for (size_t i = 0; i < 10000; ++i) + { + BigInt a = BigInt::generate(4); + BigInt b = BigInt::generate(3); + cout << "Random a = " << a.to_string() << endl; + cout << "Random b = " << b.to_string() << endl; + assert(a / b * b + a % b == a); + } + } +#endif + + // Дополнительные операторы + { + BigInt bi = 1; + assert((bi++).to_string() == "1"); + assert(bi.to_string() == "2"); + assert((++bi).to_string() == "3"); + assert(bi.to_string() == "3"); + assert((bi--).to_string() == "3"); + assert(bi.to_string() == "2"); + assert((--bi).to_string() == "1"); + assert(bi.to_string() == "1"); + assert((bi += 10).to_string() == "11"); + assert((bi -= 2).to_string() == "9"); + assert((bi *= 2).to_string() == "18"); + } +} + +int main() +{ + setlocale(LC_CTYPE, "en_US.UTF-8"); + +#ifndef __clang__ // TODO: Не компилируется на сервере ГитХаба + auto begin_time = chrono::high_resolution_clock::now(); +#endif + + run(); + +#ifndef __clang__ // TODO: Не компилируется на сервере ГитХаба + auto end_time = chrono::high_resolution_clock::now(); + auto duration = end_time - begin_time; + auto duration_ms = chrono::duration_cast(duration).count(); +#endif + + cout << "Все тесты пройдены успешно" << endl; + +#ifndef __clang__ // TODO: Не компилируется на сервере ГитХаба + cout << "Время: " << duration_ms << " ms" << endl; +#endif + + return 0; +}