From 63d82f6e45a10389974df84444e1b2513a8b32b8 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=BA=D0=BB=D0=B5=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 15 + .gitattributes | 8 + .github/workflows/tester.yml | 120 ++++++ .gitignore | 6 + CMakeLists.txt | 95 ++++ build_linux.sh | 17 + build_mingw.bat | 23 + build_vs.bat | 25 ++ 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 | 185 ++++++++ docs/4_long_div_1.cpp | 299 +++++++++++++ docs/4_long_div_2.cpp | 332 ++++++++++++++ docs/4_long_div_3.cpp | 347 +++++++++++++++ docs/4_long_div_4.cpp | 351 +++++++++++++++ docs/4_long_div_5.cpp | 371 ++++++++++++++++ docs/libs.md | 38 ++ docs/markdown.md | 28 ++ docs/names.md | 5 + lib/dv_big_int.cpp | 808 +++++++++++++++++++++++++++++++++++ lib/dv_big_int.hpp | 125 ++++++ license.txt | 21 + logo.png | Bin 0 -> 43171 bytes readme.md | 17 + remove_spaces.py | 40 ++ tester/force_assert.hpp | 16 + tester/tester.cpp | 295 +++++++++++++ 38 files changed, 5487 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 100755 build_linux.sh create mode 100644 build_mingw.bat create mode 100644 build_vs.bat 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/4_long_div_5.cpp create mode 100644 docs/libs.md create mode 100644 docs/markdown.md create mode 100644 docs/names.md create mode 100644 lib/dv_big_int.cpp create mode 100644 lib/dv_big_int.hpp create mode 100644 license.txt create mode 100644 logo.png create mode 100644 readme.md create mode 100644 remove_spaces.py create mode 100644 tester/force_assert.hpp create mode 100644 tester/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..9a1189b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Автоматически нормализуем концы строк, если у пользователя не настроен параметр 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 + +# Батники с юниксовыми концами строк глючат +*.bat text eol=crlf +*.cmd text eol=crlf diff --git a/.github/workflows/tester.yml b/.github/workflows/tester.yml new file mode 100644 index 0000000..af568c2 --- /dev/null +++ b/.github/workflows/tester.yml @@ -0,0 +1,120 @@ +name: Tester + +on: + push: + paths-ignore: 'docs/**' + pull_request: + paths-ignore: 'docs/**' + workflow_dispatch: + +jobs: + linux: + runs-on: ubuntu-24.04 + + strategy: + fail-fast: false + matrix: + compiler: + - { + id: gcc, + c: gcc-13, + cxx: g++-13, + } + - { + id: clang, + c: clang-16, + cxx: clang++-16, + } + build_type: [debug, release] + + name: 🐧-${{ 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 + + windows: + runs-on: windows-latest + + strategy: + fail-fast: false + matrix: + compiler: [vs, mingw] + build_type: [debug, release] + + name: 🔲-${{ 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[@]}" 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..56b523a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,95 @@ +# Указываем минимальную версию CMake +cmake_minimum_required(VERSION 3.16) + +# Название проекта +project(dv_big_int) + +option(BIG_INT_ONLY_LIB "Не компилировать Тестер" OFF) + +# Версия стандарта 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() + +# Статически линкуем библиотеки, чтобы не копировать dll-ки +if(MINGW) + add_link_options(-static) +endif() + +# Включаем поддержку папок в IDE +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# ============================================== Библиотека ============================================== + +# Название таргета +set(target_name dv_big_int) + +# Создаём список файлов +file(GLOB source_files lib/*) + +# Создаём статическую библиотеку +add_library(${target_name} STATIC ${source_files}) + +# Делаем заголовочные файлы доступными библиотеке и приложениям +target_include_directories(${target_name} PUBLIC lib) + +# Заставляем VS отображать дерево каталогов +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${source_files}) + +# ============================================== Тестер ============================================== + +if(NOT BIG_INT_ONLY_LIB) + # Название таргета + set(target_name tester) + + # Создаём список файлов + file(GLOB source_files tester/*) + + # Создаём приложение + add_executable(${target_name} ${source_files}) + + # Подключаем библиотеку + target_link_libraries(${target_name} PRIVATE dv_big_int) + + # Отладочная версия приложения будет иметь суффикс _d + set_property(TARGET ${target_name} PROPERTY DEBUG_POSTFIX _d) + + # Заставляем VS отображать дерево каталогов + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${source_files}) + + # Включаем тестирование + 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}) +endif() diff --git a/build_linux.sh b/build_linux.sh new file mode 100755 index 0000000..34a5807 --- /dev/null +++ b/build_linux.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +build_type="-D CMAKE_BUILD_TYPE=Debug" +#build_type="-D CMAKE_BUILD_TYPE=Release" +#build_type="-D CMAKE_BUILD_TYPE=MinSizeRel" +#build_type="-D CMAKE_BUILD_TYPE=RelWithDebInfo" + +compiler="-D CMAKE_C_COMPILER=gcc-13 -D CMAKE_CXX_COMPILER=g++-13" +#compiler="-D CMAKE_C_COMPILER=clang-15 -D CMAKE_CXX_COMPILER=clang++-15" + +repo_dir=$(dirname "$0") + +# Генерируем проект +cmake "$repo_dir" -B "$repo_dir/../build" -G "Unix Makefiles" $build_type $compiler + +# Компилируем проект +cmake --build "$repo_dir/../build" diff --git a/build_mingw.bat b/build_mingw.bat new file mode 100644 index 0000000..bbaf236 --- /dev/null +++ b/build_mingw.bat @@ -0,0 +1,23 @@ +:: Меняем кодировку консоли на UTF-8 +chcp 65001 + +:: Указываем путь к cmake.exe и MinGW. Без system32 в PATH ссылки на папки не создаются +set "PATH=%SystemRoot%\system32;c:\programs\cmake\bin;c:\msys64\ucrt64\bin" + +set build_type=Debug +::set build_type=Release +::set build_type=MinSizeRel +::set build_type=RelWithDebInfo + +set "repo_dir=%~dp0" +:: Удаляем обратный слэш в конце +set "repo_dir=%repo_dir:~0,-1%" + +:: Генерируем проект +cmake "%repo_dir%" -B "%repo_dir%/../build_mingw" -G "MinGW Makefiles" -D CMAKE_BUILD_TYPE=%build_type% + +:: Компилируем проект +cmake --build "%repo_dir%/../build_mingw" + +:: Ждём нажатие Enter перед закрытием консоли +pause diff --git a/build_vs.bat b/build_vs.bat new file mode 100644 index 0000000..f428c6d --- /dev/null +++ b/build_vs.bat @@ -0,0 +1,25 @@ +:: Меняем кодировку консоли на UTF-8 +chcp 65001 + +:: Указываем путь к cmake.exe. Без system32 в PATH ссылки на папки не создаются +set "PATH=%SystemRoot%\system32;c:\programs\cmake\bin" + +set build_type=Debug +::set build_type=Release +::set build_type=MinSizeRel +::set build_type=RelWithDebInfo + +set "generator=Visual Studio 17" + +set "repo_dir=%~dp0" +:: Удаляем обратный слэш в конце +set "repo_dir=%repo_dir:~0,-1%" + +:: Генерируем проект +cmake "%repo_dir%" -B "%repo_dir%/../build_vs" -G "%generator%" -A x64 + +:: Компилируем проект +cmake --build "%repo_dir%/../build_vs" --config %build_type% + +:: Ждём нажатие Enter перед закрытием консоли +pause diff --git a/docs/1_basics.md b/docs/1_basics.md new file mode 100644 index 0000000..b353544 --- /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..10c1e8a --- /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 calc_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 = " << calc_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..2087a80 --- /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..33a7ae3 --- /dev/null +++ b/docs/4_long_div.md @@ -0,0 +1,185 @@ +**Главы:** +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). + +### Оптимизация 4 + +Предыдущую оптимизацию можно улучшить, дополнительно изучив ещё по одной цифре куска и знаменателя. +Это приведёт к тому, что `примерная_цифра` почти всегда будет совпадать с `искомой_цифрой`. +И лишь с вероятностью `2 / base` `примерная_цифра` будет больше `искомой_цифры` на 1. +То есть чем больше основание СС, тем меньше вероятность промаха. + +Реализация: [4_long_div_5.cpp](4_long_div_5.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..8f3556e --- /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..e00afa3 --- /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..87b0e82 --- /dev/null +++ b/docs/4_long_div_3.cpp @@ -0,0 +1,347 @@ +// Деление столбиком: уменьшаем правую границу двоичного поиска + +#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 denominator_digit_1 = denominator.back(); + + // Делим две старшие цифры куска на старшую цифру знаменателя + DDigit dd_right = chunk_2_digits / denominator_digit_1; + //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..30d3a4f --- /dev/null +++ b/docs/4_long_div_4.cpp @@ -0,0 +1,351 @@ +// Деление столбиком: нормализация + +#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))); + + DDigit denominator_digit_1 = denominator.back(); + + // Должно быть нормализовано + assert(denominator_digit_1 >= 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_digit_1, 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/4_long_div_5.cpp b/docs/4_long_div_5.cpp new file mode 100644 index 0000000..194a955 --- /dev/null +++ b/docs/4_long_div_5.cpp @@ -0,0 +1,371 @@ +// Деление столбиком: уменьшаем шанс промаха + +#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_4.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))); + + DDigit denominator_digit_1 = denominator.back(); + assert(denominator_digit_1 >= base / 2); + + 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_2 = denominator[denominator.size() - 2]; + + // Делим две старшие цифры куска на старшую цифру знаменателя + DDigit digit = chunk_2_digits / denominator_digit_1; + + // Остаток + DDigit remainder = chunk_2_digits - digit * denominator_digit_1; // chunk_2_digits % 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; + +#ifndef NDEBUG + remainder += denominator_digit_1; +#endif + } + } + + assert(digit < base); + assert(digit * denominator_digit_2 <= remainder * base + chunk_digit_3); + + // Теперь проверяем, нет ли промаха на 1 + if (first_is_less(chunk, mul(vector{(Digit)digit}, denominator))) + --digit; + + assert(!first_is_less(chunk, mul(vector{(Digit)digit}, denominator))); + + return (Digit)digit; +} + +// Возвращает неполное частное и остаток. +// Если знаменатель == 0, возвращает {0, 0}. +// Функция скопирована из 4_long_div_4.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); + + 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/lib/dv_big_int.cpp b/lib/dv_big_int.cpp new file mode 100644 index 0000000..bafe1a5 --- /dev/null +++ b/lib/dv_big_int.cpp @@ -0,0 +1,808 @@ +// Copyright (c) the Dviglo project +// License: MIT + +#include "dv_big_int.hpp" + +#include +#include +#include +#include + +using namespace std; + + +namespace dviglo +{ + +constexpr uint32_t base = BigInt::base; +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); + + // Старшая цифра знаменателя + DDigit denominator_digit_1 = denominator.back(); + + // Должно быть нормализовано + assert(denominator_digit_1 >= 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_2 = denominator[denominator.size() - 2]; + + // Делим две старшие цифры куска на старшую цифру знаменателя + DDigit digit = chunk_2_digits / denominator_digit_1; + + // Остаток + DDigit remainder = chunk_2_digits - digit * denominator_digit_1; // chunk_2_digits % 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; + +#ifndef NDEBUG + remainder += denominator_digit_1; +#endif + } + } + + assert(digit < base); + assert(digit * denominator_digit_2 <= remainder * base + chunk_digit_3); + + // Теперь проверяем, нет ли промаха на 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] (включительно) +static 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/lib/dv_big_int.hpp b/lib/dv_big_int.hpp new file mode 100644 index 0000000..c955aa7 --- /dev/null +++ b/lib/dv_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/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/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8532e6fd94990658d85fcc841f6699cd369937 GIT binary patch literal 43171 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 literal 0 HcmV?d00001 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..63c1e7d --- /dev/null +++ b/readme.md @@ -0,0 +1,17 @@ +![](logo.png) + +[![](https://github.com/dviglo2d/dv_big_int/actions/workflows/tester.yml/badge.svg)](https://github.com/dviglo2d/dv_big_int/actions) + +# DvBigInt + +Лицензия: MIT. + +Библиотека не имеет зависимостей, можно использовать отдельно от движка. + +## Использование + +Скопируйте файлы dv_big_int.cpp и dv_big_int.hpp в свой проект. + +## Документация + +В папке docs [туториал](docs/1_basics.md) с описанием алгоритмов. diff --git a/remove_spaces.py b/remove_spaces.py new file mode 100644 index 0000000..bf3d301 --- /dev/null +++ b/remove_spaces.py @@ -0,0 +1,40 @@ +# Удаляет пробелы в конце строк + +import os + + +def get_newline(file_path): + with open(file_path, 'rb') as file: + content = file.read() + + if b'\r\n' in content: + return '\r\n' + else: + return '\n' + + +def remove_trailing_spaces(file_path): + with open(file_path, 'r', encoding='utf-8') as file: + lines = file.readlines() + + # Сохраняем формат разделителя строк + nel = get_newline(file_path) + + with open(file_path, 'w', encoding='utf-8', newline=nel) as file: + for line in lines: + file.write(line.rstrip() + '\n') + + +if __name__ == '__main__': + repo_dir = os.path.dirname(os.path.realpath(__file__)) + exts = {'.bat', '.sh', '.md', '.hpp', '.cpp', '.txt', '.py', '.yml', '.editorconfig', '.gitattributes', '.gitignore'} + + for root, dirs, files in os.walk(repo_dir): + if '.git' in dirs: # Не заходим в папку .git + dirs.remove('.git') + + for file in files: + if any(file.endswith(ext) for ext in exts): + file_path = os.path.join(root, file) + remove_trailing_spaces(file_path) + print('Обработан файл: ' + file_path) diff --git a/tester/force_assert.hpp b/tester/force_assert.hpp new file mode 100644 index 0000000..045c6fc --- /dev/null +++ b/tester/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/tester/tester.cpp b/tester/tester.cpp new file mode 100644 index 0000000..dc8f5e8 --- /dev/null +++ b/tester/tester.cpp @@ -0,0 +1,295 @@ +// Copyright (c) the Dviglo project +// License: MIT + +#include "force_assert.hpp" + +#include + +#include +#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) // Warning ожидаем + assert(BigInt(-0x7FFFFFFF - 1).to_string() == "-2147483648"); // BigInt(int32_t value) + assert(BigInt((int32_t)-0x80000000).to_string() == "-2147483648"); // BigInt(int32_t value) // Warning ожидаем + 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) // Warning ожидаем + assert(BigInt(-0x7FFFFFFFFFFFFFFF - 1).to_string() == "-9223372036854775808"); // BigInt(int64_t value) + assert(BigInt((int64_t)-0x8000000000000000).to_string() == "-9223372036854775808"); // BigInt(int64_t value) // Warning ожидаем + 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"); + + auto begin_time = chrono::high_resolution_clock::now(); + + run(); + + auto end_time = chrono::high_resolution_clock::now(); + auto duration = end_time - begin_time; + auto duration_ms = chrono::duration_cast(duration).count(); + + cout << "Все тесты пройдены успешно" << endl; + cout << "Время: " << duration_ms << " ms" << endl; + + return 0; +}