libnumerixpp
0.1.3
A Powerful C++ Library for High-Performance Numerical Computing
|
A Powerful C++ Library for High-Performance Numerical Computing. More...
A Powerful C++ Library for High-Performance Numerical Computing.
Quadratic utils for mathematics.
Math utils for quadratic.
Core utils for mathematics.
Physics utils for kinematics.
Core utils for physics.
Core common utils for libnumerixpp.
И его заголовочного файла libnumerixpp.hpp:
```cpp #ifndef LIBNUMERIXPP_HPP #define LIBNUMERIXPP_HPP
#include <string>
#pragma once
/**
[in] | string | The string */ void println(std::string string); |
Комментарии-документацию я вывожу именно в include-файлах.
# Блок ядра библиотеки Директория core/ - это базовый модуль, в котором будут содержаться некоторые основные функции нашей библиотеки.
Итак, core/common.cpp:
```cpp /**
А теперь его заголовочный файл:
```cpp /**
/**
#endif // LIBNUMERIXPP_COMMON_HPP
```
## Физика Т.к. модуль физики на данный момент меньше чем модуль математики, начнем именно с него.
Создадим базовый файл physics/core.cpp:
```cpp /**
Здесь ничего на данный момент нету, просто пустой namespace physics. Позже мы его дополним.
И по традиции, physics/core.hpp:
```cpp /**
/**
#endif // LIBNUMERIXPP_PHYSICS_CORE_HPP ```
Следующий шаг - уже настоящий функционал модуля. Для примера я взял базовую кинематику. Рассмотрим файл physics/kinematics.cpp:
```cpp /**
*
namespace physics::kinematics {
double calculatePath(double speed, double time) { return speed * time; }
double calculateSpeed(double path, double time) { return path / time; }
double calculateTime(double path, double speed) { return path / speed; }
double calculateFinalVelocity(double initial_velocity, double acceleration, double time) { return initial_velocity + acceleration * time; }
double calculateFinalPosition(double initial_position, double initial_velocity, double acceleration, double time) { return initial_position + initial_velocity * time + 0.5 * acceleration * time * time; } } ```
Здесь вы видите 5 функций. Первые три являются разными подвидами одной формулы: S = v * t (путь равен скорости умноженной на время). А вот две оставшиеся поинтереснее:
calculateFinalVelocity высчитывает конечную скорость объекта, используя формулу: v = u + at, где:
calculateFinalPosition высчитывает конечную позицию объекта, используя формулу: s = s0 + v0 * t + 0.5 * a * t^2, где:
Код простой, но база сделана. Если вам понравится данная статья, я могу сделать 2 часть, где мы добавим больше функционала.
И не забудем про файл physics/kinematics.hpp:
```cpp /**
/**
/**
[in] | speed | The speed |
[in] | time | The time |
[in] | path | The path |
[in] | time | The time |
[in] | path | The path |
[in] | speed | The speed |
[in] | initial_velocity | The initial velocity |
[in] | acceleration | The acceleration |
[in] | time | The time |
[in] | initial_position | The initial position |
[in] | initial_velocity | The initial velocity |
[in] | acceleration | The acceleration |
[in] | time | The time |
Перейдем к следующему модулю.
## Математика Итак, начнем с mathematics/core.cpp:
```cpp /**
*
namespace mathematics {
double oldApproximatePower(double base, double exponent) { union { double d; long long i; } u = { base };
long long magicNum = 4606853616395542500L;
u.i = (long long)(magicNum + exponent * (u.i - magicNum));
return u.d; }
double binaryPower(double base, unsigned long long exponent) { double v = 1.0;
while(exponent != 0) { if((exponent & 1) != 0) v *= base;
base *= base; exponent >>= 1; }
return v; }
double fastPowerDividing(double base, double exponent) { if(base == 1.0 || exponent == 0.0) return 1.0;
double eAbs = fabs(exponent); double el = ceil(eAbs); double basePart = oldApproximatePower(base, eAbs / el); double result = binaryPower(basePart, (unsigned long long)el);
if(exponent < 0.0) { return 1.0 / result; }
return result; }
double anotherApproximatePower(double base, double exponent) { union { double d; int x[2]; } u = { base };
u.x[1] = (int)(exponent * (u.x[1] - 1072632447) + 1072632447); u.x[0] = 0;
return u.d; }
double fastPowerFractional(double base, double exponent) { if(base == 1.0 || exponent == 0.0) return 1.0;
double absExp = fabs(exponent); unsigned long long eIntPart = (long long)absExp; double eFractPart = absExp - eIntPart; double result = oldApproximatePower(base, eFractPart) * binaryPower(base, eIntPart);
if(exponent < 0.0) return 1.0 / result;
return result; }
double add_percent_to_number(double number, double percentage) { double oneperc = number / 100; double result = number + (oneperc * percentage);
return result; }
double square_it_up(double num) { return num * num; }
double get_square_root(double num) { if (num <= 0) return 0;
int exp = 0;
num = frexp(num, &exp);
if (exp & 1) { exp–; num *= 2; }
double y = (1 + num) / 2; double z = 0;
while (y != z) { z = y; y = (y + num / y) / 2; }
return ldexp(y, exp / 2); } } ```
В начале вы можете увидеть разные виды возведения числа в степень, мы их рассмотрим ниже. А вот в конце находятся более простые функции - для добавления процента к числу, возведения в квадрат, и получения квадратного корня. Функция получения квадратного корня работает так: если входное число меньше или ровно 0, возвращаем 0. Далее используем frexp() для выделения мантиссы и порядка числа num. Мантиссу помещаем в переменную num, а порядок числа - в exp. Если порядок числа exp является нечетным, то его уменьшают на 1 и мантиссу num умножают на 2. Это необходимо для того, чтобы привести число к виду, когда мантисса находится в диапазоне [0.5, 1) (от 0.5 включая до 1 не включая). Затем инициализируются две перемнные: y=начальное значение, равно среднему между 1 и мантиссой num, и z=0. Далее выполняется итеративный цикл вычисления квадратного корня. После возвращаем результат, умноженный но соответствующую степень 2, вычисленную ранее на основе порядке exp.
А теперь, чтобы перейти к методам возведения в степень, создадим mathematics/core.hpp:
```cpp /**
/**
[in] | base | The base |
[in] | exponent | The exponent |
[in] | base | The base |
[in] | exponent | The exponent |
[in] | base | base |
[in] | exponent | exponent |
[in] | base | The base |
[in] | exponent | The exponent |
[in] | base | The base |
[in] | exponent | The exponent |
[in] | number | The number |
[in] | percentage | The percentage |
[in] | num | The number |
[in] | num | The number |
### Заново изобретаем std::pow В этом разделе я покажу реализации нескольких нестандартных алгоритмов для возведения числа в степень. Реализация и описание способ взяты из этой статьи на хабре.
Скорость алгоритмов сравнивается с std::pow.
#### Алгоритм: "Старая аппроксимация" Этот метод основан на алгоритме, использованном в игре Quake III Arena 2005 года. Он возводил число x в степень -0.5.
```cpp double oldApproximatePower(double base, double exponent) { union { double d; long long i; } u = { base };
long long magicNum = 4606853616395542500L;
u.i = (long long)(magicNum + exponent * (u.i - magicNum));
return u.d; } ```
magicNum = это магическое число. Вы можете узнать о нем подробнее в этой статье.
#### Алгоритм: Бинарное возведение в степень Увеличение скорости: в среднем в ~7.5 раз, преимущество сохраняется до возведения чисел в степень 134217728.
Погрешность: нет, но стоит отметить, что операция умножения не ассоциативна для чисел с плавающей точкой, т.е. 1.21 * 1.21 не то же самое, что 1.1 * 1.1 * 1.1 * 1.1, однако при сравнении со стандартными функциями погрешности, как уже сказано ранее, не возникает.
Ограничения: степень должна быть целым числом не меньше 0
```cpp double binaryPower(double base, unsigned long long exponent) { double v = 1.0;
while(exponent != 0) { if((exponent & 1) != 0) v *= base;
base *= base; exponent >>= 1; }
return v; } ```
Широко известный алгоритм для возведения любого числа в целую степень с абсолютной точностью. Принцип действия прост: есть целая степень e, чтобы получить число b в этой степени нужно возвести это число во все степени 1, 2, 4, … 2n (в коде этому соответствует base *= base
), каждый раз сдвигая биты e вправо (e >>= 1) пока оно не равно 0 и тогда, когда последний бит e не равен нулю ((exponent & 1) != 0), домножать результат v на полученное base.
#### Алгоритм: "Делящая быстрая степень" Увеличение скорости: в ~3.5 раз
Погрешность: ~13%
Примечание: в коде ниже присутствуют проверки для особых входных данных. Без них код работает всего на 10% быстрее, но погрешность возрастает в десятки раз (особенно при использовании отрицательных степеней).
```cpp double fastPowerDividing(double base, double exponent) { if(base == 1.0 || exponent == 0.0) return 1.0;
double eAbs = fabs(exponent); double el = ceil(eAbs); double basePart = oldApproximatePower(base, eAbs / el); double result = binaryPower(basePart, (unsigned long long)el);
if(exponent < 0.0) { return 1.0 / result; }
return result; } ```
#### Алгоритм: "Дробная быстрая степень" Увеличение скорости: в ~4.4 раза
Погрешность: ~0.7%
```cpp double fastPowerFractional(double base, double exponent) { if(base == 1.0 || exponent == 0.0) return 1.0;
double absExp = fabs(exponent); unsigned long long eIntPart = (long long)absExp; double eFractPart = absExp - eIntPart; double result = oldApproximatePower(base, eFractPart) * binaryPower(base, eIntPart);
if(exponent < 0.0) return 1.0 / result;
return result; } ```
По сути, любое число состоит из суммы двух частей: целой и дробной. Целую можно использовать для возведения основания в степень при помощи бинарного возведения, а дробную - при помощи “старой” аппроксимации.
#### Алгоритм: "Другая аппроксимация" Увеличение скорости: в ~9 раз
Погрешность: <1.5%
Ограничения: точность стремительно падает при повышении абсолютного значения степени и остается приемлемой в промежутке [-10, 10]
```cpp double anotherApproximatePower(double base, double exponent) { union { double d; int x[2]; } u = { base };
u.x[1] = (int)(exponent * (u.x[1] - 1072632447) + 1072632447); u.x[0] = 0;
return u.d; } ```
#### Сравнение
Каждый из перечисленных методов дает различную точность и скорость. Поэтому, прежде чем использовать стандартную функцию возведения в степень, стоит проанализировать какие у вас будут входные данные, и насколько вам важна точность. Выбор правильного метода может существенно ускорить работу вашей программы.
### Квадратные уравнения Продолжим улучшение модуля математики - на этот раз займемся квадратными уравнениями.
Создадим файл mathematics/quadratic_equations.cpp:
```cpp /**
*
namespace mathematics::quadratic {
double calculateDiscriminant(double a, double b, double c) { double d = mathematics::square_it_up(b) - 4 * a * c;
return d; }
std::vector<double> calculateRootsByDiscriminant(double discriminant, double a, double b) { std::vector<double> roots;
if (discriminant > 0) { double x1 = (-b + get_square_root(discriminant)) / (2 * a); double x2 = (-b - get_square_root(discriminant)) / (2 * a);
roots.push_back(x1); roots.push_back(x2); } else if (discriminant == 0) { double x1 = -b + get_square_root(discriminant) / (2 * a); roots.push_back(x1); }
return roots; }
std::vector<double> getRootsByVietaTheorem(double a, double b, double c) { std::vector<double> roots;
if (a == 0) return roots;
double roots_sum = -(b / a); double roots_mul = c / a;
roots.push_back(roots_sum); roots.push_back(roots_mul);
return roots; } } ```
Функция calculateDiscriminant высчитывает дискриминант по формуле D = b^2 - 4ac.
Функция calculateRootsByDiscriminant высчитывает корни уравнения. На вход принимает дискриминант, переменные a и b. На выходе возвращает vector (массив с неясным количеством элементов, проще говоря) типа double. Если дискриминант больше 0, то корня два, высчитываем их по формуле: -b +- квадратный корень из дискриминанта / 2a. Если дискриминант равен 0, то корень 1. А если дискриминант равен нулю - то корней нет.
Функция getRootsByVietaTheorem "получает корни" по теореме Виета. Теорема Виета гласит, если a != 0, значит: x1 + x2 = - b / a И x1 * x2 = c / a. В данной функции мы как раз и получаем их. А вот реализацию поиска корней я решил пока не делать.
И следующим шагом мы создаем файл mathematics/quadratic_equations.hpp:
```cpp /**
#include <vector> #include "libnumerixpp/mathematics/core.hpp"
/**
[in] | a | a |
[in] | b | b |
[in] | c | c |
[in] | discriminant | The discriminant |
[in] | a | a |
[in] | b | b |
[in] | a | a |
[in] | b | b |
[in] | c | c |
Мы закончили разработку модулей. Пора перейти к примерам.
## Примеры В данной части статьи мы создадим файлы example-1.cpp и example-2.cpp в директории examples. В этих файлах будут содержаться примеры работы библиотеки.
Напишем example-1.cpp, где мы будем тестировать модуль физики:
```cpp #include <iostream> #include "libnumerixpp/libnumerixpp.hpp" #include "libnumerixpp/core/common.hpp" #include "libnumerixpp/physics/core.hpp" #include "libnumerixpp/physics/kinematics.hpp"
int main() { credits(); say_hello("LIBNUMERIXPP");
double speed = 10.0; double time = 5.0;
double path = physics::kinematics::calculatePath(speed, time); speed = physics::kinematics::calculateSpeed(path, time); time = physics::kinematics::calculateSpeed(path, speed);
std::cout << "Calculate: speed=" << speed << "m/s" << "; time=" << time << "s" << "; path=" << path << "m" << std::endl;
double finalVelocity = physics::kinematics::calculateFinalVelocity(10.0, 10.0, 10.0); std::cout << "final velocity (10.0, 10.0, 10.0) = " << finalVelocity << std::endl;
double finalPosition = physics::kinematics::calculateFinalPosition(10.0, 10.0, 10.0, 10.0); std::cout << "final position (10.0, 10.0, 10.0, 10.0) = " << finalVelocity << std::endl;
return 0; } ```
При сборке данный файл выведет:
``` libnumerixpp v0.1.0 - A powerful C++ Library for High-Performance Numerical Computing Licensed by Apache License Developed&maintained by @alxvdev
LIBNUMERIXPP Calculate: speed=10m/s; time=5s; path=50m final velocity (10.0, 10.0, 10.0) = 110 final position (10.0, 10.0, 10.0, 10.0) = 110 ```
А теперь второй файл - example-2.cpp, где будут примеры использования модуля математики:
```cpp #include <iostream> #include <vector> #include "libnumerixpp/libnumerixpp.hpp" #include "libnumerixpp/core/common.hpp" #include "libnumerixpp/mathematics/core.hpp" #include "libnumerixpp/mathematics/quadratic_equations.hpp"
int main() { credits(); println("LIBNUMERIXPP");
// SQUARE AND SQR //
double num = 100.0; double num_sq = mathematics::square_it_up(num); double num_sqr = mathematics::get_square_root(num); std::cout << "Square " << num << ": " << num_sq << std::endl; std::cout << "Square root " << num << ": " << num_sqr << std::endl;
std::cout << std::endl;
// CALCULATE QUADRATIC EQUATION BY DISCRIMINANT //
double a = -2; double b = 5; double c = 5;
double d = mathematics::quadratic::calculateDiscriminant(a, b, c); std::vector<double> roots = mathematics::quadratic::calculateRootsByDiscriminant(d, a, b);
std::cout << "Quadratic Equation: a=" << a << "; b=" << b << "; c=" << c << std::endl; std::cout << "D=" << d << std::endl; std::cout << "Roots:" << std::endl;
for (double root : roots) { std::cout << root << std::endl; }
std::cout << std::endl;
// PERCENTAGE //
double nump = mathematics::add_percent_to_number(100.0, 10.0); std::cout << "100+10%: " << nump << std::endl;
std::cout << std::endl;
// POWER / Algorithms for fast exponentiation //
double bestPowVal = 100; double pow_results[5] = { mathematics::oldApproximatePower(10.0, 2.0), mathematics::anotherApproximatePower(10.0, 2.0), mathematics::binaryPower(10.0, 2), mathematics::fastPowerDividing(10.0, 2.0), mathematics::fastPowerFractional(10.0, 2.0) };
std::cout << "0 oldApproximatePower : base 10 exponent 2: " << pow_results[0] << std::endl; std::cout << "1 anotherApproximatePower: base 10 exponent 2: " << pow_results[1] << std::endl; std::cout << "2 binaryPower : base 10 exponent 2: " << pow_results[2] << std::endl; std::cout << "3 fastPowerDividing : base 10 exponent 2: " << pow_results[3] << std::endl; std::cout << "4 fastPowerFractional : base 10 exponent 2: " << pow_results[4] << std::endl;
for (int i = 0; i < sizeof(pow_results) / sizeof(pow_results[0]); i++) { double error = bestPowVal - pow_results[i];
std::cout << "POW Algorithm #" << i << ": error=" << error << std::endl; }
return 0; } ```
При компиляции данный файл выведет:
``` libnumerixpp v0.1.0 - A powerful C++ Library for High-Performance Numerical Computing Licensed by Apache License Developed&maintained by @alxvdev
LIBNUMERIXPP Square 100: 10000 Square root 100: 10
Quadratic Equation: a=-2; b=5; c=5 D=65 Roots: -0.765564 3.26556
100+10%: 110
0 oldApproximatePower : base 10 exponent 2: 100.673 1 anotherApproximatePower: base 10 exponent 2: 99.711 2 binaryPower : base 10 exponent 2: 100 3 fastPowerDividing : base 10 exponent 2: 100 4 fastPowerFractional : base 10 exponent 2: 96.3496 POW Algorithm #0: error=-0.672563 POW Algorithm #1: error=0.289001 POW Algorithm #2: error=0 POW Algorithm #3: error=0 POW Algorithm #4: error=3.65044 ```
Вот и все. С программированием мы закончили - настало очередь украшательств.
# Украшаем репозиторий Итак, любой проект будет привлекательнее с грамотно оформленным README. У меня в проекте он выглядит так.
Документация поможет пользователям и разработчикам. Да, если им сильно нужно - они могут и просто скачать или почитать код, но многих отсутствие нормальной документации или хотя-бы нормального README файла отпугивает. Что вы бы выбрали - хорошо заполненный и документированный репозиторий или такой же репозиторий, но без какой либо информации? Я думаю, ответ очевиден.
Давайте разберем README поэтапно. Начнем с шапки:
```md # libnumerixpp
A Powerful C++ Library for High-Performance Numerical Computing
> [!CAUTION] > At the moment, libnumerixpp is under active development (alpha), many things may not work, and this version is not recommended for use (all at your own risk).
libnumerixpp is a powerful, cross-platofrm C++ library designed for high-performance numerical computing in the domains of physics, mathematics, and computer science.
You can join to our small russian telegram blog.
You can view docs for libnumerixpp here. ```
Вверху мы указываем заголовок, название проекта. Ниже описание и бейджи. Бейджи нужны для общей статистики проекта - лицензия, язык, звезды и т.д. Замените /alxvdev/libnumerixpp
на свой юзернейм и репозиторий.
После мы вставляем предупреждение в виде цитаты, что проект в стадии разработки и может быть не стабилен.
Ниже также детали, и другие важные вещи, на которые сразу стоит обратить внимания. У меня это ссылка на телеграм-канал и на документацию.
Идем дальше:
```md ## Key Features
Основные возможности. Здесь можно перечислить, что может ваш проект, какие у него преимущества.
Далее вы можете также создавать разные блоки, рассказывающие, например, о структуре.
Желательно еще дать инструкции по установке и сборке. Посмотрите, как сделано в моем репозитории.
Следующим шагом я бы сделал ссылки на примеры использование, зависимости и инструменты, а также раздел документации и поддержки:
```md ## Examples Below you can see examples of using libnumerixpp in your project. Also, you can view examples dir.
### example-1 Speed, Time, Path Source code: example-1.cpp
This example shows how to calculate the speed, path, and time using the physics::kinematics
.
### example-2 Math, Quadratic and math Source code: example-2.cpp
This example shows how to calculate the quadratic equations, discriminant, squares using the mathematics
and mathematics::quadratic
.
## Tools and Dependencies linumerixpp utilizes the following tools and libraries:
## Documentation Detailed documentation, including user guides, API reference, and code examples, is available in the docs. Or you can see articles or additional info in en docs dir or ru docs dir.
If you have any questions, suggestions, or encounter issues, please create a new issue in the repository. We'll be happy to assist you and improve the library.
You can also write to me on Telegram: @alexeev_dev
libnumerixpp is an Open Source project, and it only survives due to your feedback and support!
Project releases are available at this link. ```
И в самом конце можно указать копирайты:
```md ## Copyright libnumerixpp is released under the [Apache License 2.0](LICENSE).
Copyright © 2024 Alexeev Bronislav. All rights reversed. ```
Я хочу сказать, что это не требования, а мои рекомендации. Вы можете сделать по другому.
И небольшое наставление по коммитам. По некоторым правилам, коммит должен быть в повелительном наклонении, потому что это, как бы указание разработчику, что надо делать. Но это не указание, это также рекомендации, вы вольны использовать что хотите (или что захочет ваша команда).
## Описание коммитов | Название | Описание | |-------—|--------------------------------------------------------------—| | build | Сборка проекта или изменения внешних зависимостей | | sec | Безопасность, уязвимости | | ci | Настройка CI и работа со скриптами | | docs | Обновление документации | | feat | Добавление нового функционала | | fix | Исправление ошибок | | perf | Изменения направленные на улучшение производительности | | refactor | Правки кода без исправления ошибок или добавления новых функций | | revert | Откат на предыдущие коммиты | | style | Правки по кодстайлу (табы, отступы, точки, запятые и т.д.) | | test | Добавление тестов |
Пример:
```bash git commit -m "feat: add line output" # en, добавьте вывод строки git commit -m "feat: добавьте вывод строки" # ru ```
Больше информации об оформлении репозитория вы можете увидеть в моей старой статье.
—
Вот и все. Минимальный комплект минимальной библиотеки. Надеюсь, я сподвиг вас к чему-то большему. Пишите, творите, публикуйте!
# Заключение Спасибо за внимание! Это был довольно интересный опыт для меня, т.к. это мой первый большой проект на языке C++, где я попытался его изучить более подробно.
Если у вас есть замечания по статье или по коду - пишите, наверняка есть более опытный и профессиональный программист на C++, который может помочь как и читателям статьи, так и мне.
Ссылка на мой репозиторий реализации командного интерпретатора здесь.
Буду рад, если вы присоединитесь к моему небольшому телеграм-блогу. Анонсы статей, новости из мира IT и полезные материалы для изучения программирования и смежных областей.
P.S. вы можете прорекламировать свою библиотеку в комментариях, если она интересная и может кому-то пригодиться.
## Источники