diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml
index 4ed505ba5..20c2028d8 100644
--- a/.github/workflows/benchmark.yml
+++ b/.github/workflows/benchmark.yml
@@ -6,9 +6,6 @@ on:
push:
branches: ["develop"]
- pull_request:
- branches: ["main"]
-
workflow_dispatch:
jobs:
diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml
index d0d901173..fbb131cda 100644
--- a/.github/workflows/cmake-multi-platform.yml
+++ b/.github/workflows/cmake-multi-platform.yml
@@ -10,9 +10,6 @@ on:
- "**.hpp"
- "**.py"
- "**.yml"
-
- pull_request:
- branches: ["main"]
jobs:
build:
runs-on: ${{ matrix.os }}
@@ -26,12 +23,16 @@ jobs:
#
# To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list.
matrix:
- os: [ubuntu-latest, windows-2019, macos-13, macos-14, macos-15]
- build_type: [Release]
+ os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, macos-13, macos-14, macos-15]
+ build_type: [Debug, Release]
c_compiler: [clang, cl]
python_version: ['3.11', '3.12', '3.13']
include:
- - os: windows-2019
+ - os: windows-latest
+ c_compiler: cl
+ cpp_compiler: cl
+ cmake_extra_options: ''
+ - os: windows-11-arm
c_compiler: cl
cpp_compiler: cl
cmake_extra_options: ''
@@ -39,9 +40,10 @@ jobs:
c_compiler: clang
cpp_compiler: clang++
cmake_extra_options: '-GNinja'
- # - os: macos-latest
- # c_compiler: gcc
- # cpp_compiler: g++
+ - os: ubuntu-24.04-arm
+ c_compiler: clang
+ cpp_compiler: clang++
+ cmake_extra_options: ''
- os: macos-13
c_compiler: clang
cpp_compiler: clang++
@@ -55,10 +57,14 @@ jobs:
cpp_compiler: clang++
cmake_extra_options: '-GNinja'
exclude:
- - os: windows-2019
+ - os: windows-latest
+ c_compiler: clang
+ - os: windows-11-arm
c_compiler: clang
- os: ubuntu-latest
c_compiler: cl
+ - os: ubuntu-24.04-arm
+ c_compiler: cl
- os: macos-13
c_compiler: cl
- os: macos-14
@@ -79,17 +85,6 @@ jobs:
echo "CMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}" >> "$GITHUB_ENV"
echo "CMAKE_OPTIONS=${{ matrix.cmake_extra_options }}" >> "$GITHUB_ENV"
- - name: Set up Homebrew
- if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' || matrix.os == 'macos-15' || matrix.os == 'ubuntu-latest'}}
- id: set-up-homebrew
- uses: Homebrew/actions/setup-homebrew@master
-
- - name: Install LLVM
- if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' || matrix.os == 'macos-15' || matrix.os == 'ubuntu-latest' }}
- run: |
- brew install llvm ninja
- echo "PATH=$(brew --prefix llvm)/bin:$PATH" >> "$GITHUB_ENV"
-
- name: Set up Python
uses: actions/setup-python@v5
with:
@@ -108,6 +103,6 @@ jobs:
uses: actions/upload-artifact@v4.3.1
with:
# Artifact name
- name: PyBuild ${{ matrix.os }}-cp${{ matrix.python_version }}
+ name: steppable-${{ matrix.os }}-cp${{ matrix.python_version }}-${{ matrix.build_type }}
# A file, directory or wildcard pattern that describes what to upload
path: ${{ steps.strings.outputs.py-build-output-dir }}
diff --git a/.idea/.name b/.idea/.name
index 27a177823..3d3b5b67d 100644
--- a/.idea/.name
+++ b/.idea/.name
@@ -1 +1 @@
-Steppable
\ No newline at end of file
+Steppable
diff --git a/.idea/Steppable.iml b/.idea/Steppable.iml
index ec24658e0..4fe0f9e44 100644
--- a/.idea/Steppable.iml
+++ b/.idea/Steppable.iml
@@ -5,11 +5,4 @@
-
-
-
\ No newline at end of file
diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
new file mode 100644
index 000000000..5cd0ffb2d
--- /dev/null
+++ b/.idea/dictionaries/project.xml
@@ -0,0 +1,7 @@
+
+
+
+ nwsoft
+
+
+
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 253b7e58f..cf993c792 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,10 +1,35 @@
{
"configurations": [
+ {
+ "name": "Matrix::rref",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/build/src/matrix_ref",
+ "args": [],
+ "cwd": "${workspaceFolder}",
+ "environment": [],
+ "externalConsole": true,
+ "MIMode": "lldb",
+ "internalConsoleOptions": "openOnSessionStart",
+ "preLaunchTask": "CMake: build",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ },
+ {
+ "description": "Set Disassembly Flavor to Intel",
+ "text": "-gdb-set disassembly-flavor intel",
+ "ignoreFailures": true
+ }
+ ]
+ },
{
"name": "Division",
"type": "cppdbg",
"request": "launch",
- "program": "${workspaceFolder}/build/src/division",
+ "program": "${workspaceFolder}/build/src/calc_division",
"args": [
"1",
"2"
@@ -14,6 +39,7 @@
"environment": [],
"externalConsole": false,
"MIMode": "lldb",
+ "internalConsoleOptions": "openOnSessionStart",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
@@ -31,13 +57,14 @@
"name": "Add",
"type": "cppdbg",
"request": "launch",
- "program": "${workspaceFolder}/build/src/add",
+ "program": "${workspaceFolder}/build/src/calc_add",
"args": [
"-5",
"6"
],
"cwd": "${workspaceFolder}/build",
"preLaunchTask": "CMake: build",
+ "internalConsoleOptions": "openOnSessionStart",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
@@ -50,14 +77,15 @@
"type": "cppdbg",
"request": "launch",
"name": "Multiply",
- "program": "${workspaceFolder}/build/src/multiply",
+ "program": "${workspaceFolder}/build/src/calc_multiply",
"args": [
"56",
"76",
"+profile"
],
"cwd": "${workspaceFolder}",
- "preLaunchTask": "CMake: build"
+ "preLaunchTask": "CMake: build",
+ "internalConsoleOptions": "openOnSessionStart",
}
]
}
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 957beba96..d49f8fc60 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,6 +20,8 @@
# SOFTWARE. #
#####################################################################################################
+INCLUDE(cmake/build_special_hacks.cmake)
+
CMAKE_MINIMUM_REQUIRED(VERSION 3.20)
PROJECT(Steppable)
@@ -109,11 +111,11 @@ SET(COMPONENTS
calc::root
calc::subtract
calc::trig
-)
+ matrix::ref)
# NEW_COMPONENT: PATCH Do NOT remove the previous comment.
SET(TARGETS ${COMPONENTS} util)
-SET(TEST_TARGETS_TEMP util fraction number factors format ${COMPONENTS})
+SET(TEST_TARGETS_TEMP util fraction number mat2d factors format ${COMPONENTS})
FOREACH(TEST_TARGET IN LISTS TEST_TARGETS_TEMP)
SET(TARGET_NAME "test")
@@ -131,7 +133,6 @@ ENDFOREACH()
ADD_SUBDIRECTORY(src/)
ADD_SUBDIRECTORY(lib/)
ADD_SUBDIRECTORY(tests/)
-ADD_SUBDIRECTORY(gui/)
ADD_SUBDIRECTORY(include/) # The CMakeLists file there adds the include/ directory to everything
FILE(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
deleted file mode 100644
index 3c7a58dcf..000000000
--- a/azure-pipelines.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-trigger:
- branches:
- include:
- - main
-
-jobs:
- - job: BuildJobLinux
- displayName: 'Build C++ Project (Linux)'
- pool:
- vmImage: 'ubuntu-latest'
- steps:
- - checkout: self
-
- - task: CMake@1
- displayName: 'Configure CMake (Linux)'
- inputs:
- cmakeArgs: '-B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc-13 -D CMAKE_CXX_COMPILER=g++-13'
- workingDirectory: '$(Build.SourcesDirectory)'
-
- - task: CMake@1
- displayName: 'Build C++ Project (Linux)'
- inputs:
- cmakeArgs: '--build .'
- workingDirectory: '$(Build.SourcesDirectory)/build'
-
- - task: CmdLine@2
- displayName: 'Run Tests (Linux)'
- inputs:
- script: 'ctest --output-junit out-linux.xml'
- workingDirectory: '$(Build.SourcesDirectory)/build/tests'
- failOnStderr: true
-
- - task: PublishTestResults@2
- displayName: 'Publish Test Results (Linux)'
- inputs:
- testResultsFormat: 'JUnit'
- testResultsFiles: '*.xml'
- searchFolder: '$(Build.SourcesDirectory)/build/tests'
- mergeTestResults: true
- failTaskOnFailedTests: true
- failTaskOnMissingResultsFile: true
- testRunTitle: 'Test on Linux'
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Build Artifact (Linux)'
- inputs:
- targetPath: '$(Build.SourcesDirectory)/build'
- artifact: 'Build-Linux'
- publishLocation: 'pipeline'
-
- - job: BuildJobWindows
- displayName: 'Build C++ Project (Windows)'
- pool:
- vmImage: 'windows-latest'
- steps:
- - checkout: self
-
- - task: CMake@1
- displayName: 'Configure CMake (Windows)'
- inputs:
- cmakeArgs: '-B build -DCMAKE_BUILD_TYPE=Release'
- workingDirectory: '$(Build.SourcesDirectory)'
-
- - task: CMake@1
- displayName: 'Build C++ Project (Windows)'
- inputs:
- cmakeArgs: '--build . --config Release'
- workingDirectory: '$(Build.SourcesDirectory)/build'
-
- - task: CmdLine@2
- displayName: 'Run Tests (Windows)'
- inputs:
- script: 'ctest --output-junit out-windows.xml -C Release'
- workingDirectory: '$(Build.SourcesDirectory)/build/tests'
- failOnStderr: true
-
- - task: PublishTestResults@2
- displayName: 'Publish Test Results (Windows)'
- inputs:
- testResultsFormat: 'JUnit'
- testResultsFiles: '*.xml'
- searchFolder: '$(Build.SourcesDirectory)/build/tests'
- mergeTestResults: true
- failTaskOnFailedTests: true
- failTaskOnMissingResultsFile: true
- testRunTitle: 'Test on Windows'
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Build Artifact (Windows)'
- inputs:
- targetPath: '$(Build.SourcesDirectory)/build'
- artifact: 'Build-Windows'
- publishLocation: 'pipeline'
diff --git a/cmake/build_special_hacks.cmake b/cmake/build_special_hacks.cmake
new file mode 100644
index 000000000..65fe19325
--- /dev/null
+++ b/cmake/build_special_hacks.cmake
@@ -0,0 +1,11 @@
+IF(STP_DEB_CALC_DIVISION_RESULT_INSPECT)
+ ADD_COMPILE_DEFINITIONS(STP_DEB_CALC_DIVISION_RESULT_INSPECT)
+ENDIF()
+
+IF(STP_DEB_CALC_MULTIPLY_RESULT_INSPECT)
+ ADD_COMPILE_DEFINITIONS(STP_DEB_CALC_MULTIPLY_RESULT_INSPECT)
+ENDIF()
+
+IF(STP_DEB_MATRIX_REF_RESULT_INSPECT)
+ ADD_COMPILE_DEFINITIONS(STP_DEB_MATRIX_REF_RESULT_INSPECT)
+ENDIF()
diff --git a/doxygen-awesome-css b/doxygen-awesome-css
index 28ed396de..9760c3001 160000
--- a/doxygen-awesome-css
+++ b/doxygen-awesome-css
@@ -1 +1 @@
-Subproject commit 28ed396de19cd3d803bcb483dceefdb6d03b1b2b
+Subproject commit 9760c30014131f4eacb8e96f15f3869c7bc5dd8c
diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt
deleted file mode 100644
index 047c1e65a..000000000
--- a/gui/CMakeLists.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-FUNCTION(ADD_GUI SOURCE)
- GET_FILENAME_COMPONENT(NAME ${SOURCE} NAME_WE)
- SET(NAME "gui_${NAME}")
-
- ADD_EXECUTABLE(${NAME} ${SOURCE})
- TARGET_LINK_LIBRARIES(${NAME} PRIVATE ${SDL2_LIBRARIES} ${OPENGL_LIBRARIES})
- TARGET_LINK_LIBRARIES(${NAME} PRIVATE imgui)
- TARGET_INCLUDE_DIRECTORIES(${NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
- TARGET_INCLUDE_DIRECTORIES(${NAME} PRIVATE "${STP_BASE_DIRECTORY}/include/imgui")
-
- # Link Steppable stuff
- TARGET_LINK_LIBRARIES(${NAME} PRIVATE func impl)
- TARGET_INCLUDE_DIRECTORIES(${NAME} PRIVATE ${STP_BASE_DIRECTORY}/include)
-
- if(FREETYPE_FOUND)
- TARGET_LINK_LIBRARIES(${NAME} PRIVATE ${FREETYPE_LIBRARIES})
- TARGET_INCLUDE_DIRECTORIES(${NAME} PRIVATE ${FREETYPE_INCLUDE_DIRS})
- TARGET_COMPILE_DEFINITIONS(${NAME} PRIVATE IMGUI_ENABLE_FREETYPE)
- TARGET_LINK_LIBRARIES(${NAME} PRIVATE imgui_freetype)
- endif()
-ENDFUNCTION()
-
-IF (STP_BUILD_GUI EQUAL 1)
- # Find and link SDL2 and OpenGL
- FIND_PACKAGE(SDL2 REQUIRED)
- FIND_PACKAGE(OpenGL REQUIRED)
- ADD_LIBRARY(impl STATIC impl/gui.cpp impl/window.cpp)
- TARGET_LINK_LIBRARIES(impl PRIVATE ${SDL2_LIBRARIES} ${OPENGL_LIBRARIES} imgui)
- TARGET_INCLUDE_DIRECTORIES(impl PRIVATE
- ${SDL2_INCLUDE_DIRS}
- "${STP_BASE_DIRECTORY}/include/imgui"
- "${STP_BASE_DIRECTORY}/include")
-
- ADD_GUI(test.cpp)
-ENDIF()
diff --git a/gui/impl/gui.cpp b/gui/impl/gui.cpp
deleted file mode 100644
index a37db77ab..000000000
--- a/gui/impl/gui.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-#include "gui.hpp"
-
-#include "output.hpp"
-
-#ifdef MACOSX
- #include
-#endif
-
-namespace steppable::gui::__internals
-{
-
- bool isDarkModeEnabled()
- {
-#ifdef MACOSX
- bool isDarkMode = false;
- CFPreferencesAppSynchronize(CFSTR("AppleInterfaceStyle"));
- CFPropertyListRef value = CFPreferencesCopyAppValue(CFSTR("AppleInterfaceStyle"), kCFPreferencesAnyApplication);
- if (value != nullptr)
- {
- const auto* interfaceStyle = static_cast(value);
- if (CFStringCompare(interfaceStyle, CFSTR("Dark"), 0) == kCFCompareEqualTo)
- isDarkMode = true;
- CFRelease(value);
- }
- return isDarkMode;
-#elif defined(LINUX)
- return std::filesystem::exists("/usr/share/themes/Adwaita-dark/gtk-3.0");
-#elif defined(WINDOWS)
- HKEY key;
- if (RegOpenKeyExA(HKEY_CURRENT_USER,
- "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
- 0,
- KEY_QUERY_VALUE,
- &key) == ERROR_SUCCESS)
- {
- DWORD value = 0;
- DWORD size = sizeof(DWORD);
- if (RegQueryValueExA(key, "AppsUseLightTheme", nullptr, nullptr, reinterpret_cast(&value), &size) ==
- ERROR_SUCCESS)
- return value == 0;
- }
- return false;
-#else
- return false;
-#endif
- }
-
- void addFontIfExistent(const ImGuiIO* io,
- const std::filesystem::path& path,
- const ImFontConfig* config,
- const ImWchar* ranges) noexcept
- {
- if (io->Fonts->Fonts.empty() and config->MergeMode)
- config = nullptr;
- if (std::filesystem::exists(path))
- {
-#ifdef DEBUG
- output::info("addIfExistent"s, "Added font {0}"s, { path });
-#endif
- io->Fonts->AddFontFromFileTTF(path.c_str(), 15.0F, config, ranges);
- }
- }
-
- void loadFonts(const ImGuiIO* io) noexcept
- {
- ImFontConfig config;
- config.MergeMode = true;
-#ifdef WINDOWS
- // WINDOWS fonts
- // -------------
- // Chinese -> Microsoft YaHei
- // Cyrillic -> Segoe UI -----------------+
- // Greek -> Segoe UI -----------------|
- // Japanese -> Meiryo |
- // Korean -> Malgun Gothic +--> Top-priority
- // Thai -> Leelawadee |
- // Vietnamese -> Segoe UI -----------------+
-
- // Load top-priority fonts
- addIfExistent(io, "C:/Windows/Fonts/segoeui.ttf", &config, io->Fonts->GetGlyphRangesCyrillic());
- addIfExistent(io, "C:/Windows/Fonts/segoeui.ttf", &config, io->Fonts->GetGlyphRangesDefault());
- addIfExistent(io, "C:/Windows/Fonts/segoeui.ttf", &config, io->Fonts->GetGlyphRangesGreek());
- addIfExistent(io, "C:/Windows/Fonts/segoeui.ttf", &config, io->Fonts->GetGlyphRangesVietnamese());
-
- // Load Chinese fonts
- addIfExistent(io, "C:/Windows/Fonts/msyh.ttc", &config, io->Fonts->GetGlyphRangesChineseFull());
-
- // Load Japanese fonts
- addIfExistent(io, "C:/Windows/Fonts/meiryo.ttc", &config, io->Fonts->GetGlyphRangesJapanese());
-
- // Load Korean fonts
- addIfExistent(io, "C:/Windows/Fonts/malgun.ttf", &config, io->Fonts->GetGlyphRangesKorean());
-
- // Load Thai fonts
- addIfExistent(io, "C:/Windows/Fonts/leelawad.ttf", &config, io->Fonts->GetGlyphRangesThai());
-#elif defined(MACOSX)
- // MACOS fonts
- // -------------
- // Chinese -> PingFang SC (*)
- // Cyrillic -> SF Pro -----------------+
- // Greek -> SF Pro -----------------|
- // Japanese -> Hiragino Sans |
- // Korean -> Apple SD Gothic Neo +--> Top-priority
- // Thai -> Thonburi |
- // Vietnamese -> SF Pro -----------------+
- //
- // (*) NOTE: PingFang may not be available on all systems, but STHeiti Medium is a good alternative.
-
- // Load top-priority fonts
- addFontIfExistent(io, "/Library/Fonts/SF-Pro.ttf", &config, io->Fonts->GetGlyphRangesCyrillic());
- addFontIfExistent(io, "/Library/Fonts/SF-Pro.ttf", &config, io->Fonts->GetGlyphRangesDefault());
- addFontIfExistent(io, "/Library/Fonts/SF-Pro.ttf", &config, io->Fonts->GetGlyphRangesGreek());
- addFontIfExistent(io, "/Library/Fonts/SF-Pro.ttf", &config, io->Fonts->GetGlyphRangesVietnamese());
-
- // Load Chinese fonts
- addFontIfExistent(io, "/System/Library/Fonts/PingFang.ttc", &config, io->Fonts->GetGlyphRangesChineseFull());
- addFontIfExistent(
- io, "/System/Library/Fonts/STHeiti Medium.ttc", &config, io->Fonts->GetGlyphRangesChineseFull());
-
- // Load Japanese fonts
- addFontIfExistent(io, "/System/Library/Fonts/Hiragino.ttc", &config, io->Fonts->GetGlyphRangesJapanese());
-
- // Load Korean fonts
- addFontIfExistent(io, "/System/Library/Fonts/AppleSDGothicNeo.ttc", &config, io->Fonts->GetGlyphRangesKorean());
-
- // Load Thai fonts
- addFontIfExistent(io, "/System/Library/Fonts/Thonburi.ttf", &config, io->Fonts->GetGlyphRangesThai());
- addFontIfExistent(
- io, "/System/Library/Fonts/Supplemental/Ayuthaya.ttf", &config, io->Fonts->GetGlyphRangesThai());
-#elif defined(LINUX)
- // LINUX fonts
- // -------------
- // Chinese -> WenQuanYi Zen Hei
- // Cyrillic -> DejaVu Sans -----------------+
- // Greek -> DejaVu Sans -----------------|
- // Japanese -> Takao Gothic |
- // Korean -> Nanum Gothic +--> Top-priority
- // Thai -> Garuda |
- // Vietnamese -> DejaVu Sans -----------------+
-
- // Load top-priority fonts
- addIfExistent(io, "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", &config, io->Fonts->GetGlyphRangesCyrillic());
- addIfExistent(io, "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", &config, io->Fonts->GetGlyphRangesDefault());
- addIfExistent(io, "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", &config, io->Fonts->GetGlyphRangesGreek());
- addIfExistent(io, "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", &config, io->Fonts->GetGlyphRangesVietnamese());
-
- // Load Chinese fonts
- addIfExistent(io, "/usr/share/fonts/TTF/wqy-zenhei.ttc", &config, io->Fonts->GetGlyphRangesChineseFull());
-
- // Load Japanese fonts
- addIfExistent(io, "/usr/share/fonts/TTF/takao-mincho.ttf", &config, io->Fonts->GetGlyphRangesJapanese());
-
- // Load Korean fonts
- addIfExistent(io, "/usr/share/fonts/TTF/NanumGothic.ttf", &config, io->Fonts->GetGlyphRangesKorean());
-
- // Load Thai fonts
- addIfExistent(io, "/usr/share/fonts/TTF/garuda.ttf", &config, io->Fonts->GetGlyphRangesThai());
-#endif
- // Add the default font as well.
- io->Fonts->AddFontDefault(&config);
- io->Fonts->Build();
- }
-} // namespace steppable::gui::__internals
diff --git a/gui/impl/window.cpp b/gui/impl/window.cpp
deleted file mode 100644
index 389ca9ad1..000000000
--- a/gui/impl/window.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-#include "backends/imgui_impl_opengl3.h"
-#include "backends/imgui_impl_sdl2.h"
-#include "gui.hpp"
-#include "imgui.h"
-#include "output.hpp"
-#include "platform.hpp"
-
-#include
-#include
-#include
-
-using namespace steppable;
-using namespace steppable::gui::__internals;
-using namespace steppable::__internals::utils;
-
-namespace steppable::gui
-{
- void runWindow(const std::string& name, const std::function& predicate)
- {
- // Setup SDL
- if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
- {
- steppable::output::error("SDL_Init", std::string(SDL_GetError()));
- programSafeExit(-1);
- }
-
- // Decide GL+GLSL versions
-#if defined(IMGUI_IMPL_OPENGL_ES2)
- // GL ES 2.0 + GLSL 100
- const char* glsl_version = "#version 100";
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-#elif defined(__APPLE__)
- // GL 3.2 Core + GLSL 150
- const char* glsl_version = "#version 150";
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
-#else
- // GL 3.0 + GLSL 130
- const char* glsl_version = "#version 130";
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
-#endif
-
- // From 2.0.18: Enable native IME.
- SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
-
- // Create window with graphics context
- SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
- SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
- SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
- auto window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
- SDL_Window* window =
- SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags);
- if (window == nullptr)
- {
- steppable::output::error("SDL_CreateWindow"s, std::string(SDL_GetError()));
- programSafeExit(-1);
- }
-
- SDL_GLContext gl_context = SDL_GL_CreateContext(window);
- SDL_GL_MakeCurrent(window, gl_context);
- SDL_GL_SetSwapInterval(1); // Enable vsync
-
- // Setup Dear ImGui context
- IMGUI_CHECKVERSION();
- ImGui::CreateContext();
- ImGuiIO& io = ImGui::GetIO();
- io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
- io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
-
- // Setup Platform/Renderer backends
- ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
- ImGui_ImplOpenGL3_Init(glsl_version);
-
- bool done = false;
- ImVec4 clear_color = ImVec4(0.22, 0.22, 0.22, 1.00);
- std::array buf{};
- loadFonts(&io);
-
- while (not done)
- {
- // Poll and handle events (inputs, window resize, etc.)
- // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use
- // your inputs.
- // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or
- // clear/overwrite your copy of the mouse data.
- // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or
- // clear/overwrite your copy of the keyboard data. Generally you may always pass all inputs to dear imgui,
- // and hide them from your application based on those two flags.
- SDL_Event event;
- while (SDL_PollEvent(&event) != 0)
- {
- ImGui_ImplSDL2_ProcessEvent(&event);
- if (event.type == SDL_QUIT)
- done = true;
- if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE &&
- event.window.windowID == SDL_GetWindowID(window))
- done = true;
- }
-
- // Start the Dear ImGui frame
- ImGui_ImplOpenGL3_NewFrame();
- ImGui_ImplSDL2_NewFrame();
- ImGui::NewFrame();
-
- predicate();
-
- if (isDarkModeEnabled())
- {
- glClearColor(0.22, 0.22, 0.22, 1.0);
- ImGui::StyleColorsDark();
- }
- else
- {
- glClearColor(0.95, 0.95, 0.95, 1.0);
- ImGui::StyleColorsLight();
- }
-
- // Rendering
- ImGui::Render();
- glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
- glClear(GL_COLOR_BUFFER_BIT);
- ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
- SDL_GL_SwapWindow(window);
- }
-
- // Cleanup
- ImGui_ImplOpenGL3_Shutdown();
- ImGui_ImplSDL2_Shutdown();
- ImGui::DestroyContext();
-
- SDL_GL_DeleteContext(gl_context);
- SDL_DestroyWindow(window);
- SDL_Quit();
- }
-} // namespace steppable::gui
diff --git a/gui/test.cpp b/gui/test.cpp
deleted file mode 100644
index 8a816ba60..000000000
--- a/gui/test.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "gui.hpp"
-#include "imgui.h"
-
-#include
-#include
-
-// NOTE TO DEVELOPERS: This file is a test file for the GUI module of the Steppable library.
-// When building the GUI part for the first time, this can come in handy.
-// Run the executable to see if the GUI window works.
-
-using namespace steppable;
-using namespace steppable::gui::__internals;
-
-void setUpContents()
-{
- ImGui::Begin("Hello, world!");
- ImGui::Text("This is some useful text.");
- ImGui::End();
-}
-
-int main() { gui::runWindow("", &setUpContents); }
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 6082f9837..630de4980 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -24,35 +24,3 @@
FOREACH(TARGET IN LISTS TEST_TARGETS)
TARGET_INCLUDE_DIRECTORIES(${TARGET} PUBLIC ${STP_BASE_DIRECTORY}/include/)
ENDFOREACH(TARGET)
-
-IF (STP_BUILD_GUI EQUAL 1)
- FIND_PACKAGE(SDL2 REQUIRED)
- ADD_LIBRARY(imgui
- imgui/imgui.cpp
- imgui/imgui_draw.cpp
- imgui/imgui_widgets.cpp
- imgui/imgui_tables.cpp
- imgui/imgui_demo.cpp
- imgui/backends/imgui_impl_sdl2.cpp
- imgui/backends/imgui_impl_opengl3.cpp)
-
- # If we have freetype, we can use it to render text
- FIND_PACKAGE(Freetype QUIET)
- IF (FREETYPE_FOUND)
- MESSAGE(STATUS "Found Freetype")
- ADD_LIBRARY(imgui_freetype STATIC "${STP_BASE_DIRECTORY}/include/imgui/misc/freetype/imgui_freetype.cpp")
- TARGET_INCLUDE_DIRECTORIES(imgui_freetype PRIVATE "${STP_BASE_DIRECTORY}/include/imgui")
- TARGET_LINK_LIBRARIES(imgui_freetype PRIVATE ${FREETYPE_LIBRARIES})
- TARGET_LINK_LIBRARIES(imgui PRIVATE imgui_freetype)
- TARGET_INCLUDE_DIRECTORIES(imgui_freetype PRIVATE ${FREETYPE_INCLUDE_DIRS})
- TARGET_COMPILE_DEFINITIONS(imgui PRIVATE IMGUI_ENABLE_FREETYPE)
- ENDIF()
-
- IF (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
- TARGET_LINK_LIBRARIES(imgui PRIVATE "-framework CoreFoundation")
- ENDIF()
-
- TARGET_LINK_LIBRARIES(imgui PRIVATE SDL2::SDL2)
- TARGET_INCLUDE_DIRECTORIES(imgui PRIVATE ${SDL2_INCLUDE_DIRS})
- TARGET_INCLUDE_DIRECTORIES(imgui PRIVATE "${STP_BASE_DIRECTORY}/include/imgui")
-ENDIF()
diff --git a/include/fn/calc.hpp b/include/fn/calc.hpp
index 3d0302bcb..0b4916d72 100644
--- a/include/fn/calc.hpp
+++ b/include/fn/calc.hpp
@@ -37,6 +37,8 @@
#include "fn/root.hpp"
#include "output.hpp"
+#include "steppable/number.hpp"
+#include "types/result.hpp"
#include
#include
@@ -44,8 +46,8 @@
using namespace std::literals;
/**
- * @namespace steppable::__internals
- * @brief The namespace containing internal functions for the Steppable library.
+ * @namespace steppable::__internals::calc
+ * @brief The namespace containing number calculating functions for the Steppable library.
*/
namespace steppable::__internals::calc
{
@@ -188,6 +190,17 @@ namespace steppable::__internals::calc
*/
std::string subtract(const std::string& a, const std::string& b, int steps = 2, bool noMinus = false);
+ /**
+ * @brief Calculate the error between values a and b.
+ * @details Gets the absolute difference between a and b.
+ *
+ * @param a Value 1
+ * @param b Value 2
+ *
+ * @return The absolute value of the differnece between the values.
+ */
+ types::Result error(const std::string& a, const std::string& b);
+
/**
* @brief Takes the n-th root of a numer.
*
diff --git a/include/fn/root.hpp b/include/fn/root.hpp
index 062be5e49..e4add3192 100644
--- a/include/fn/root.hpp
+++ b/include/fn/root.hpp
@@ -24,6 +24,10 @@
#include
+/**
+ * @namespace steppable::__internals
+ * @brief The namespace containing internal functions for the Steppable library.
+ */
namespace steppable::__internals::calc
{
/**
diff --git a/include/gui.hpp b/include/gui.hpp
deleted file mode 100644
index 21a2cb34e..000000000
--- a/include/gui.hpp
+++ /dev/null
@@ -1,91 +0,0 @@
-/**************************************************************************************************
- * Copyright (c) 2023-2025 NWSOFT *
- * *
- * 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. *
- **************************************************************************************************/
-
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-using namespace std::literals;
-
-/**
- * @brief The namespace for the internal components of the GUI module of the Steppable library.
- * @details This namespace contains the internal components of the GUI module of the Steppable library.
- * For example, methods that gets the system fonts, checks if the dark mode is enabled, and loads the fonts.
- * @note This namespace is not intended for use by the end user.
- */
-namespace steppable::gui::__internals
-{
- /**
- * @brief Checks if the dark mode is enabled.
- * @return True if the dark mode is enabled, false otherwise.
- */
- bool isDarkModeEnabled();
-
- /**
- * Attempts to add a font to the application if it exists in the system.
- *
- * This method checks if a specified font is available on the system. If the font is found,
- * it is added to the application's font resources, making it available for use within the application.
- * If the font does not exist, the method will not perform any action or may log an error or warning,
- * depending on implementation details.
- *
- * @param io A pointer to an ImGuiIO object to enable configuration.
- * @param path The absolute path to the font file.
- * @param config A pointer to an ImFontConfig object to enable font configuration.
- * @param ranges A pointer to an array of ImWchar objects to enable character range configuration.
- */
- void addFontIfExistent(const ImGuiIO* io,
- const std::filesystem::path& path,
- const ImFontConfig* config,
- const ImWchar* ranges) noexcept;
-
- /**
- * @brief Loads the fonts for the application.
- * @details This method tries to find the system fonts that can display most character sets.
- * @param io A pointer to an ImGuiIO object to enable configuration
- */
- void loadFonts(const ImGuiIO* io) noexcept;
-} // namespace steppable::gui::__internals
-
-/**
- * @brief The namespace for the GUI components of the Steppable library. This is the main namespace for the GUI
- * components.
- */
-namespace steppable::gui
-{
- /**
- * @brief Runs the main window of the application.
- * @details This method replaces the long ininitalization process of the application with a simple
- * call to the runWindow method. This method initializes the SDL2 and OpenGL backends for the application,
- * then runs the main window loop. The main window loop is responsible for rendering the application's
- * contents and handling user input.
- *
- * @param name The name of the window.
- * @param predicate The function that will be called to render the window.
- */
- void runWindow(const std::string& name, const std::function& predicate);
-} // namespace steppable::gui
diff --git a/include/imgui b/include/imgui
deleted file mode 160000
index dad58f2f6..000000000
--- a/include/imgui
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit dad58f2f6839e5fcadaf95b57cf6174ef0274e52
diff --git a/include/platform.hpp b/include/platform.hpp
index aa8a63c73..36d6890f1 100644
--- a/include/platform.hpp
+++ b/include/platform.hpp
@@ -38,6 +38,8 @@
#include
#include
+#undef timeval
+
using namespace std::literals;
/**
@@ -58,6 +60,10 @@ namespace steppable::__internals::utils
*/
inline void programSafeExit(const int status)
{
+#ifdef STP_BINDINGS
+ return;
+#endif
+
#ifdef MACOSX
exit(status); // NOLINT(concurrency-mt-unsafe)
#else
diff --git a/include/rounding.hpp b/include/rounding.hpp
index fb21088fc..21c7bcfde 100644
--- a/include/rounding.hpp
+++ b/include/rounding.hpp
@@ -21,6 +21,7 @@
**************************************************************************************************/
#pragma once
+#include
#include
namespace steppable::__internals::numUtils
@@ -46,9 +47,10 @@ namespace steppable::__internals::numUtils
*
* @param[in] _number The number to round.
* @param[in] digits The number of decimal places to round to.
+ * @param[in] mode The mode of rounding. Defaults to rounding off.
* @return The rounded number.
*/
- std::string roundOff(const std::string& _number, size_t digits = 0);
+ std::string roundOff(const std::string& _number, size_t digits = 0, Rounding mode = Rounding::ROUND_OFF);
/**
* @brief Move the decimal places of a number.
diff --git a/include/fraction.hpp b/include/steppable/fraction.hpp
similarity index 93%
rename from include/fraction.hpp
rename to include/steppable/fraction.hpp
index 3b1c4a6a9..255ebab58 100644
--- a/include/fraction.hpp
+++ b/include/steppable/fraction.hpp
@@ -21,7 +21,7 @@
**************************************************************************************************/
/**
- * @file fraction.hpp
+ * @file steppable/fraction.hpp
* @brief This file contains the definition of the Fraction class, which represents a fraction in math.
*
* @author Andy Zhang
@@ -30,7 +30,7 @@
#pragma once
-#include "number.hpp"
+#include "steppable/number.hpp"
#include
#include
@@ -95,6 +95,13 @@ namespace steppable
*/
Fraction operator+(const Fraction& rhs) const;
+ /**
+ * @brief Unary plus operator.
+ * @details Does nothing. Simply returns a new instance of the fraction.
+ * @return A new instance of the fraction, with euqal value and equal in sign.
+ */
+ Fraction operator+() const;
+
/**
* @brief Subtracts a fraction from another fraction.
* This function does it by doing a simple fraction subtraction and returns the difference.
@@ -104,6 +111,13 @@ namespace steppable
*/
Fraction operator-(const Fraction& rhs) const;
+ /**
+ * @brief Unary minus operator.
+ * @details Converts the fraction to itself with the opposite sign. Returns a new instance of the fraction.
+ * @return A fraction equal in value but opposite in sign.
+ */
+ Fraction operator-() const;
+
/**
* @brief Multiplies two fractions together.
* This function does it by doing a simple fraction multiplication and returns the sum.
diff --git a/include/steppable/mat2d.hpp b/include/steppable/mat2d.hpp
new file mode 100644
index 000000000..9d98a4b30
--- /dev/null
+++ b/include/steppable/mat2d.hpp
@@ -0,0 +1,460 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+/**
+ * @file mat2d.hpp
+ * @brief Defines methods and classes for 2D matrix manipulation.
+ * @author Andy Zhang
+ * @date 31 May 2025
+ */
+
+#pragma once
+
+#include "steppable/number.hpp"
+#include "testing.hpp"
+#include "types/point.hpp"
+
+#include
+#include
+#include
+
+namespace steppable
+{
+ /**
+ * @brief Alias for a 2D matrix represented as a vector of vectors.
+ * @tparam NumberT The type of the numbers in the matrix.
+ */
+ template
+ using MatVec2D = std::vector>;
+
+ namespace prettyPrint::printers
+ {
+ /**
+ * @brief Pretty prints a matrix.
+ * @param matrix The matrix to be pretty printed.
+ * @return A string representation of the matrix.
+ */
+ std::string ppMatrix(const MatVec2D& matrix, int endRows = 0);
+ } // namespace prettyPrint::printers
+
+ namespace __internals::symbols
+ {
+ constexpr std::string_view MATRIX_LEFT_TOP = "\u23A1";
+ constexpr std::string_view MATRIX_LEFT_MIDDLE = "\u23A2";
+ constexpr std::string_view MATRIX_LEFT_BOTTOM = "\u23A3";
+ constexpr std::string_view MATRIX_RIGHT_TOP = "\u23A4";
+ constexpr std::string_view MATRIX_RIGHT_MIDDLE = "\u23A5";
+ constexpr std::string_view MATRIX_RIGHT_BOTTOM = "\u23A6";
+ } // namespace __internals::symbols
+
+ /**
+ * @class Matrix
+ * @brief Represents a mathematical matrix.
+ */
+ class Matrix
+ {
+ size_t _cols; ///< The number of columns in the matrix.
+ size_t _rows; ///< The number of rows in the matrix.
+ size_t prec = 10; ///< Precision of numbers in the matrix.
+ MatVec2D data; ///< The data of the matrix.
+
+ /**
+ * @brief Checks whether a point is inside the matrix. Errors and exits the program if not.
+ * @param point The point to check.
+ */
+ void _checkIdxSanity(const YXPoint* point) const;
+
+ /**
+ * @brief Checks whether the matrix data is correct in format.
+ * @details This method checks for non-uniform rows inside the matrix.
+ *
+ * @param data The matrix data vector.
+ */
+ static void _checkDataSanity(const MatVec2D& data);
+
+ /**
+ * @brief Rounds off all data inside a vector to a specified precision.
+ * @param data A double `std::vector` object containing matrix data.
+ * @param prec Precision of the matrix.
+ */
+ static MatVec2D roundOffValues(const MatVec2D& data, size_t prec);
+
+ public:
+ /**
+ * @brief Round off all values to a specified precision.
+ * @param prec Precision of the new matrix.
+ * @return A new instance of the current matrix, with values rounded to the desired precision.
+ */
+ [[nodiscard]] Matrix roundOffValues(size_t prec) const;
+
+ /**
+ * @brief Default constructor for the Matrix class.
+ */
+ Matrix();
+
+ /**
+ * @brief Constructs a matrix with specified dimensions and an optional fill value.
+ * @param rows The number of rows.
+ * @param cols The number of columns.
+ * @param fill The value to fill the matrix with (default is "0").
+ */
+ Matrix(size_t rows, size_t cols, const Number& fill = Number("0"));
+
+ /**
+ * @brief Constructs a matrix from a 2D vector of data.
+ * @param data The 2D vector representing the matrix data.
+ * @param prec Precision of the numbers.
+ */
+ Matrix(const MatVec2D& data, size_t prec = 5);
+
+ /**
+ * @brief Constructs a matrix from a 2D vector of C++ numbers.
+ * @param data The 2D vector representing the matrix data.
+ * @param prec Precision of the numbers.
+ * @tparam ValueT Type of value in the `data` parameter.
+ */
+ template
+ Matrix(const MatVec2D& data, const size_t prec) :
+ _cols(data.front().size()), _rows(data.size()), prec(prec)
+ {
+ this->data = std::vector(_rows, std::vector(_cols, Number("0")));
+ for (size_t i = 0; i < _rows; i++)
+ for (size_t j = 0; j < _cols; j++)
+ this->data[i][j] = Number(data[i][j], prec);
+ }
+
+ /**
+ * @brief Converts the matrix to its reduced row echelon form.
+ * @return A new Matrix in reduced row echelon form.
+ */
+ [[nodiscard]] Matrix rref() const;
+
+ /**
+ * @brief Converts a matrix to row echelon form.
+ * @details Converts a matrix to row echelon form, with row elimation and swapping.
+ * @return A new Matrix in row echelon form.
+ */
+ [[nodiscard]] Matrix ref() const;
+
+ /**
+ * @brief Find the determinant of a matrix.
+ * @details Calculates the reduced echelon form of the matrix.
+ * @return A Number object representing the determinant.
+ */
+ [[nodiscard]] Number det() const;
+
+ /**
+ * @brief Presents the matrix as a string.
+ * @return A string representation of the matrix.
+ */
+ [[nodiscard]] std::string present(int endRows = 0) const;
+
+ /**
+ * @brief Creates a matrix filled with ones.
+ * @param rows The number of rows.
+ * @param cols The number of columns.
+ * @return A Matrix filled with ones.
+ */
+ static Matrix ones(size_t rows, size_t cols);
+
+ /**
+ * @brief Creates a matrix filled with zeros.
+ * @param rows The number of rows.
+ * @param cols The number of columns.
+ * @return A Matrix filled with zeros.
+ */
+ static Matrix zeros(size_t rows, size_t cols);
+
+ /**
+ * @brief Creates a diagnal matrix
+ *
+ * @param colsRows Number of columns and rows.
+ * @param fill Number to fill into the matrix.
+ * @return A diagnal matrix filled with the specified values.
+ */
+ static Matrix diag(size_t colsRows, const Number& fill = 1);
+
+ /**
+ * @brief Creates a diagnal matrix
+ *
+ * @param colsRows Number of columns and rows.
+ * @param fill Number to fill into the matrix.
+ * @tparam NumberT Type of the number.
+ * @return A diagnal matrix filled with the specified values.
+ */
+ template
+ static Matrix diag(const size_t colsRows, const NumberT& fill = 0)
+ {
+ return diag(colsRows, Number(fill));
+ }
+
+ /**
+ * @brief Alias for the data `begin()` method to allow iterating over the matrix rows,
+ * @return The beginning of the matrix rows.
+ */
+ auto begin() { return data.begin(); }
+
+ /**
+ * @brief Alias for the data `end()` method to allow iterating over the matrix rows,
+ * @return The end of the matrix rows.
+ */
+ auto end() { return data.end(); }
+
+ /**
+ * @brief Calculates the rank of a matrix.
+ * @details Calculates the number of non-zero rows when the matrix is converted to row-echelon form.
+ * @return The rank of the matrix.
+ */
+ [[nodiscard]] Number rank() const;
+
+ /**
+ * @brief Transposes a matrix.
+ * @details Flips the rows and columns of the matrix and returns a new instance of the transposed matrix.
+ * @return An instance of the transposed matrix.
+ */
+ [[nodiscard]] Matrix transpose() const;
+
+ /**
+ * @brief Add a matrix to another matrix.
+ * @details Performs matrix addition, where corresponding elements in both matrices are added.
+ *
+ * @param rhs The other matrix.
+ * @return A new matrix with the addition result.
+ */
+ Matrix operator+(const Matrix& rhs) const;
+
+ /**
+ * @brief Adds the other matrix to current matrix and assigns result to this matrix.
+ *
+ * @details This operator performs in-place matrix addition, updating each element of the matrix by
+ * addition.
+ *
+ * @param rhs The other matrix.
+ * @return Instance of a new matrix after addition.
+ */
+ Matrix operator+=(const Matrix& rhs);
+
+ /**
+ * @brief Unary plus operator.
+ * @details Does nothing. Simply returns a new instance of the current matrix.
+ * @return A new instance of the current matrix.
+ */
+ Matrix operator+() const;
+
+ /**
+ * @brief Subtract a matrix from another matrix.
+ * @details Performs matrix subtraction, where corresponding elements in both matrices are subtracted.
+ *
+ * @param rhs The other matrix.
+ * @return A new matrix with the subtraction result.
+ */
+ Matrix operator-(const Matrix& rhs) const;
+
+ /**
+ * @brief Subtracts the other matrix from current matrix and assigns result to this matrix.
+ *
+ * @details This operator performs in-place matrix subtraction, updating each element of the matrix by
+ * subtraction.
+ *
+ * @param rhs The other matrix.
+ * @return Instance of a new matrix after subtraction.
+ */
+ Matrix operator-=(const Matrix& rhs);
+
+ /**
+ * @brief Unary minus operator.
+ * @details Converts the matrix to itself with all values being converted to the opposite sign. Returns a new
+ * instance of the matrix.
+ * @return A matrix with equal values in the opposite sign.
+ */
+ Matrix operator-() const;
+
+ /**
+ * @brief Scalar multiplication.
+ * @details Multiplies each element of the matrix by a scalar.
+ *
+ * @param rhs The scalar to multiply.
+ * @return A new matrix after the scalar multiplication
+ */
+ Matrix operator*(const Number& rhs) const;
+
+ /**
+ * @brief Multiplies the current matrix by a scalar value and assigns the result to this matrix.
+ *
+ * @details This operator performs in-place scalar multiplication, updating each element of the matrix
+ * by multiplying it with the provided scalar value.
+ *
+ * @param rhs The scalar value to multiply each element of the matrix by.
+ * @return Instance of a modified matrix after multiplication.
+ */
+ Matrix operator*=(const Number& rhs);
+
+ /**
+ * @brief Matrix multiplication.
+ * @details Multiplies a matrix by another matrix, returning the resulting matrix.
+ *
+ * @param rhs The other matrix to multiply.
+ * @return The new matrix after multiplying.
+ */
+ Matrix operator*(const Matrix& rhs) const;
+
+ /**
+ * @brief Multiplies this matrix by another matrix and assigns the result to this matrix.
+ *
+ * @details Performs in-place matrix multiplication with the given right-hand side (rhs) matrix.
+ * The current matrix is updated to be the product of itself and rhs.
+ *
+ * @param rhs The matrix to multiply with this matrix.
+ * @return The updated matrix after multiplication.
+ */
+ Matrix operator*=(const Matrix& rhs);
+
+ /**
+ * @brief Raises the current matrix to a certain power.
+ * @details Only positive integers (including zero) and the negative number -1 are allowed. Only square matrices
+ * are supported. The matrix is multiplied by itself for a specified number of times.
+ *
+ * @param times Times to raise the matrix to.
+ * @return A new matrix of the power result.
+ */
+ Matrix operator^(const Number& times) const;
+
+ /**
+ * @brief Raises the current matrix to a certain power, and assigns result to the current matrix.
+ * @details Only positive integers (including zero) and the negative number -1 are allowed. Only square matrices
+ * are supported.
+ *
+ * @param times Times to raise the matrix to.
+ * @return The current matrix.
+ */
+ Matrix operator^=(const Number& times);
+
+ /**
+ * @brief Join a matrix to the right of the current matrix.
+ * @details Joins a matrix to the right of the current matrix. Requires two matrices to have to same number of
+ * rows.
+ *
+ * @param rhs The other matrix to join.
+ * @return A new matrix where the two matrices are joined.
+ */
+ Matrix operator<<(const Matrix& rhs) const;
+
+ /**
+ * @brief Join a matrix to the right of the current matrix, then assign the result to the current one.
+ * @details Joins a matrix to the right of the current matrix. Requires two matrices to have to same number of
+ * rows. The result is assigned to the current matrix.
+ *
+ * @param rhs The other matrix to join.
+ * @return A new matrix where the two matrices are joined.
+ */
+ Matrix operator<<=(const Matrix& rhs);
+
+ /**
+ * @brief Join a matrix to the left of the current matrix.
+ * @details Joins a matrix to the left of the current matrix. Requires two matrices to have to same number of
+ * rows.
+ *
+ * @param rhs The other matrix to join.
+ * @return A new matrix where the two matrices are joined.
+ */
+ Matrix operator>>(const Matrix& rhs) const;
+
+ /**
+ * @brief Join a matrix to the left of the current matrix, then assign the result to the current one.
+ * @details Joins a matrix to the left of the current matrix. Requires two matrices to have to same number of
+ * rows. The result is assigned to the current matrix.
+ *
+ * @param rhs The other matrix to join.
+ * @return A new matrix where the two matrices are joined.
+ */
+ Matrix operator>>=(const Matrix& rhs);
+
+ /**
+ * @brief Test for equal matrices.
+ * @details If the current matrix is equal to the other matrix, i.e., equal in all of its values, returns True.
+ *
+ * @param rhs The other matrix.
+ * @return Whether the current matrix is equal to the other one.
+ */
+ bool operator==(const Matrix& rhs) const;
+
+ /**
+ * @brief Test for unequal matrices.
+ * @details If the current matrix is not equal to the other matrix, i.e., equal in all of its values, returns
+ * True.
+ *
+ * @param rhs The other matrix.
+ * @return Whether the current matrix is not equal to the other one.
+ */
+ bool operator!=(const Matrix& rhs) const;
+
+ /**
+ * @brief Gets the element at a point in the matrix.
+ * @details Tries to find an element at the index specified by the point. Prints out error and exits when the
+ * index does not exist.
+ *
+ * @param point The position of the element to find.
+ * @return A reference to the element at that position.
+ */
+ Number& operator[](const YXPoint& point);
+
+ /**
+ * @brief Accesses the matrix element at the specified YXPoint.
+ *
+ * This operator allows read-only access to the matrix element located at the given
+ * YXPoint coordinates.
+ *
+ * @param point The YXPoint specifying the (y, x) coordinates of the element.
+ * @return The value of the matrix element at the specified coordinates.
+ */
+ Number operator[](const YXPoint& point) const;
+
+ /**
+ * @brief Accesses the matrix element at the specified YXPoint.
+ *
+ * This operator allows read-only access to the matrix element located at the given
+ * YXPoint coordinates.
+ *
+ * @param point The YXPoint specifying the (y, x) coordinates of the element.
+ * @return The value of the matrix element at the specified coordinates.
+ */
+ Matrix operator[](const YX2Points& point) const;
+
+ /**
+ * @brief Get the number of rows in the matrix.
+ * @return The number of rows in the matrix.
+ */
+ [[nodiscard]] size_t getRows() const { return _rows; }
+
+ /**
+ * @brief Get the number of columns in the matrix.
+ * @return The number of columns in the matrix.
+ */
+ [[nodiscard]] size_t getCols() const { return _cols; }
+
+ /**
+ * @brief Get the data std::vector object from the matrix.
+ * @return A std::vector object containing all the matrix data.
+ */
+ [[nodiscard]] MatVec2D getData() const { return data; }
+ };
+} // namespace steppable
diff --git a/include/number.hpp b/include/steppable/number.hpp
similarity index 72%
rename from include/number.hpp
rename to include/steppable/number.hpp
index 2b3ef22b7..f2756e251 100644
--- a/include/number.hpp
+++ b/include/steppable/number.hpp
@@ -21,7 +21,7 @@
**************************************************************************************************/
/**
- * @file number.hpp
+ * @file steppable/number.hpp
* @brief Contains the definition of the Number class, which offers an API for arbitrary precision arithmetic.
*
* @author Andy Zhang
@@ -30,7 +30,12 @@
#pragma once
+#include "testing.hpp"
+#include "util.hpp"
+
+#include
#include
+#include
/**
* @namespace steppable
@@ -42,7 +47,7 @@ namespace steppable
* @enum RoundingMode
* @brief Specifies how Steppable should round the number in operations.
*/
- enum RoundingMode
+ enum class RoundingMode : std::uint8_t
{
/// @brief Use the higher precision whenever possible.
USE_MAXIMUM_PREC = 0xFF,
@@ -60,13 +65,19 @@ namespace steppable
DISCARD_ALL_DECIMALS = 0x00
};
+ enum class Rounding : std::uint8_t
+ {
+ ROUND_DOWN = 0x00, ///< Rounds the number down.
+ ROUND_UP = 0x01, ///< Rounds the number up.
+ ROUND_OFF = 0x02, ///< Rounds the number off.
+ };
+
/**
* @class Number
* @brief Represents a number with arbitrary precision. It basically stores the value as a string.
*/
class Number
{
- private:
/// @brief The value of the number.
std::string value;
@@ -74,17 +85,60 @@ namespace steppable
size_t prec;
/// @brief The rounding mode of the number.
- RoundingMode mode = USE_CURRENT_PREC;
+ RoundingMode mode = RoundingMode::USE_CURRENT_PREC;
+
+ template<__internals::utils::StringLiteral fnName>
+ [[nodiscard]] size_t determinePrec(const Number& rhs) const
+ {
+ size_t usePrec = 0;
+ if (mode == RoundingMode::USE_MAXIMUM_PREC)
+ usePrec = std::max(prec, rhs.prec);
+ else if (mode == RoundingMode::USE_MINIMUM_PREC)
+ usePrec = std::min(prec, rhs.prec);
+ else if (mode == RoundingMode::USE_CURRENT_PREC)
+ usePrec = prec;
+ else if (mode == RoundingMode::USE_OTHER_PREC)
+ usePrec = rhs.prec;
+ else if (mode == RoundingMode::DISCARD_ALL_DECIMALS)
+ usePrec = 0;
+ else
+ {
+ usePrec = 0;
+ output::warning(std::string(fnName.value), "Invalid precision specified"s);
+ }
+
+ return usePrec;
+ }
public:
/// @brief The default constructor. Initializes the number with a value of 0.
Number();
+ Number(const Number& rhs);
+
/**
* @brief Initializes a number with a specified value.
* @note By default, the value is 0.
*/
- Number(std::string value = "0", size_t prec = 0, RoundingMode mode = USE_CURRENT_PREC);
+ Number(std::string value = "0", size_t prec = 10, RoundingMode mode = RoundingMode::USE_CURRENT_PREC);
+
+ /**
+ * @brief Initializes a number with a C/C++ long double value.
+ * @note No matter how the number is specified, it will always be converted to a string for storage.
+ */
+ template
+ Number(ValueT value, size_t prec = 10, RoundingMode mode = RoundingMode::USE_CURRENT_PREC) :
+ value(std::to_string(value)), prec(prec), mode(mode)
+ {
+ }
+
+ void set(std::string newVal) { value = std::move(newVal); }
+
+ void setPrec(size_t newPrec, RoundingMode mode = RoundingMode::USE_CURRENT_PREC)
+ {
+ this->mode = mode;
+ prec = newPrec;
+ }
/**
* @brief Adds two numbers together.
@@ -114,6 +168,15 @@ namespace steppable
*/
Number operator/(const Number& rhs) const;
+ /**
+ * @brief Takes a modulus operation.
+ * @details Divides and takes the nearest quotient.
+ *
+ * @param rhs The other number.
+ * @return The modulus of the two numbers.
+ */
+ [[nodiscard]] Number mod(const Number& rhs) const;
+
/**
* @brief Calculates the remainder of two numbers. (Modulus)
* @param rhs The number to divide.
@@ -126,7 +189,7 @@ namespace steppable
* @param rhs The power to raise the number to.
* @return The result of the power operation.
*/
- Number operator^(const Number& rhs) const;
+ Number operator^(const Number& rhs);
/**
* @brief Adds the number to another number and assigns the result to the current number.
@@ -224,10 +287,35 @@ namespace steppable
*/
Number operator--();
+ /**
+ * @brief Unary minus operator.
+ * @details Converts the number to itself with the opposite sign. Returns a new instance of the number.
+ * @return A number equal in value but opposite in sign.
+ */
+ Number operator-() const;
+
+ /**
+ * @brief Unary plus operator.
+ * @details Does nothing. Simply returns a new instance of the number.
+ * @return A number equal in value and equal in sign.
+ */
+ Number operator+() const;
+
/**
* @brief Presents the number in a human-readable format.
* @return The number as a string.
*/
[[nodiscard]] std::string present() const;
};
+
+ /**
+ * @namespace steppable::literals
+ * @brief Literal suffixes for literals to be converted to Steppable objects.
+ */
+ namespace literals
+ {
+ inline Number operator""_n(long double value) { return Number(value); }
+
+ inline Number operator""_n(unsigned long long value) { return Number(value); }
+ } // namespace literals
} // namespace steppable
diff --git a/include/testing.hpp b/include/testing.hpp
index e85934fee..8db028171 100644
--- a/include/testing.hpp
+++ b/include/testing.hpp
@@ -30,6 +30,9 @@
#pragma once
+#include "format.hpp"
+#include "types/concepts.hpp"
+
#include
using namespace std::literals;
@@ -101,7 +104,7 @@ namespace steppable::testing
* @param[in] condition The condition to be checked.
* @param[in] conditionName The name of the condition.
*/
- void assert(bool condition, const std::string& conditionName);
+ void _assertCondition(bool condition, const std::string& conditionName);
std::string testCaseName;
@@ -129,18 +132,70 @@ namespace steppable::testing
void assertIsNotEqual(const std::string& a, const std::string& b);
/**
- * @brief Asserts that two integers are equal.
- * @param[in] a The first integer.
- * @param[in] b The second integer.
+ * @brief Asserts that two numeric values are equal.
+ * @param[in] a The first object.
+ * @param[in] b The second object.
+ */
+ template
+ void assertIsEqual(ValueT a, ValueT b)
+ {
+ const std::string& conditionName =
+ __internals::format::format("Value {0} == {1}", { std::to_string(a), std::to_string(b) });
+ _assertCondition(a == b, conditionName);
+ }
+
+ /**
+ * @brief Asserts that two numeric values are nearly equal.
+ * @param[in] a The first object.
+ * @param[in] b The second object.
+ */
+ template
+ void assertIsNearlyEqual(ValueT a, ValueT b)
+ {
+ const std::string& conditionName =
+ __internals::format::format("Value {0} ≈ {1}", { std::to_string(a), std::to_string(b) });
+ // Take less than 10% error as equal
+ _assertCondition(abs(a - b) / a < 0.1, conditionName);
+ }
+
+ /**
+ * @brief Asserts that two numeric values are not equal.
+ * @param[in] a The first object.
+ * @param[in] b The second object.
+ */
+ template
+ void assertIsNotEqual(ValueT a, ValueT b)
+ {
+ const std::string& conditionName =
+ __internals::format::format("Value {0} != {1}", { std::to_string(a), std::to_string(b) });
+ _assertCondition(a != b, conditionName);
+ }
+
+ /**
+ * @brief Asserts that two objects with .present() method are equal.
+ * @param[in] a The first object.
+ * @param[in] b The second object.
*/
- void assertIsEqual(int a, int b);
+ template
+ void assertIsEqual(ValueT a, ValueT b)
+ {
+ const std::string& conditionName =
+ __internals::format::format("Object {0} == {1}", { a.present(), b.present() });
+ _assertCondition(a == b, conditionName);
+ }
/**
- * @brief Asserts that two integers are not equal.
- * @param[in] a The first integer.
- * @param[in] b The second integer.
+ * @brief Asserts that two objects with .present() method are not equal.
+ * @param[in] a The first object.
+ * @param[in] b The second object.
*/
- void assertIsNotEqual(int a, int b);
+ template
+ void assertIsNotEqual(ValueT a, ValueT b)
+ {
+ const std::string& conditionName =
+ __internals::format::format("Object {0} != {1}", { a.present(), b.present() });
+ _assertCondition(a != b, conditionName);
+ }
/**
* @brief Asserts that a boolean value is true.
diff --git a/include/types/concepts.hpp b/include/types/concepts.hpp
new file mode 100644
index 000000000..058c183fe
--- /dev/null
+++ b/include/types/concepts.hpp
@@ -0,0 +1,46 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+/**
+ * @brief Defines concepts to be used for distinguishing types in the project.
+ * @author Andy Zhang
+ * @date 23 Jun 2025
+ */
+
+#include
+#include
+
+/**
+ * @namespace steppable::concepts
+ * @brief Defines concepts to be used for distinguishing types in the project.
+ */
+namespace steppable::concepts
+{
+ template
+ concept Numeric = std::integral || std::floating_point; ///< Represents any C++ numeral literals.
+
+ template
+ /// @brief Represents any object with a `.present()` method.
+ concept Presentable = requires(ObjectT object) {
+ { object.present() } -> std::same_as;
+ };
+} // namespace steppable::concepts
diff --git a/include/types/data.hpp b/include/types/data.hpp
index 724656aee..bfa7decf5 100644
--- a/include/types/data.hpp
+++ b/include/types/data.hpp
@@ -22,15 +22,24 @@
#pragma once
-#include
-
#include "util.hpp"
+#include
+#include
+
using namespace std::literals;
using namespace steppable::__internals::utils;
namespace steppable
{
+ /**
+ * @class Data
+ * @brief Represents data that is being passed through Steppable.
+ * @details This class contains a data value, and a name of the data.
+ *
+ * @tparam BaseT The type of the data.
+ * @tparam BaseTName A StringLiteral describing the type of the data.
+ */
template
class Data
{
@@ -51,14 +60,14 @@ namespace steppable
enum class _Weekday : std::uint8_t
{
- Sunday = 0,
- Monday = 1,
- Tuesday = 2,
- Wednesday = 3,
- Thursday = 4,
- Friday = 5,
- Saturday = 6,
+ Sunday = 0, ///< Sunday
+ Monday = 1, ///< Monday
+ Tuesday = 2, ///< Tuesday
+ Wednesday = 3, ///< Wednesday
+ Thursday = 4, ///< Thursday
+ Friday = 5, ///< Friday
+ Saturday = 6, ///< Saturday
};
- using Weekday = Data<_Weekday, StringLiteral{"Weekday"}>;
-} // namespace steppable
\ No newline at end of file
+ using Weekday = Data<_Weekday, StringLiteral{ "Weekday" }>;
+} // namespace steppable
diff --git a/include/types/point.hpp b/include/types/point.hpp
new file mode 100644
index 000000000..809e928b6
--- /dev/null
+++ b/include/types/point.hpp
@@ -0,0 +1,61 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+#include
+
+namespace steppable
+{
+ /**
+ * @struct XYPoint
+ * @brief A point object
+ * @details Represents a 2-D point on a 2D plane.
+ */
+ struct XYPoint
+ {
+ size_t x = 0; ///< Coordinate x.
+ size_t y = 0; ///< Coordinate y.
+ };
+
+ /**
+ * @struct YXPoint
+ * @brief A point object
+ * @details Represents a 2-D point on a 2D plane.
+ */
+ struct YXPoint
+ {
+ size_t y = 0; ///< Coordinate y.
+ size_t x = 0; ///< Coordinate x.
+ };
+
+ /**
+ * @struct YX2Points
+ * @brief A points object that represents two points
+ * @details Represents two 2-D points on a 2D plane.
+ */
+ struct YX2Points
+ {
+ size_t y1 = 0; ///< Point 1 coordinate y.
+ size_t x1 = 0; ///< Point 1 coordinate x.
+ size_t y2 = 0; ///< Point 2 coordinate y.
+ size_t x2 = 0; ///< Point 2 coordinate x.
+ };
+} // namespace steppable
diff --git a/include/types/result.hpp b/include/types/result.hpp
index 28112c430..cec224b3e 100644
--- a/include/types/result.hpp
+++ b/include/types/result.hpp
@@ -22,6 +22,7 @@
#pragma once
+#include
#include
#include
#include
@@ -43,9 +44,9 @@ namespace steppable::types
*/
enum class Status : std::uint8_t
{
- CALCULATED_SIMPLIFIED,
- CALCULATED_UNSIMPLIFIED,
- MATH_ERROR
+ CALCULATED_SIMPLIFIED, ///< The calculation is done by taking simplified steps.
+ CALCULATED_UNSIMPLIFIED, ///< The calculation is done.
+ MATH_ERROR ///< The calculation is not done because of an error.
};
/**
@@ -53,11 +54,11 @@ namespace steppable::types
*/
enum class StatusBool : std::uint8_t
{
- CALCULATED_SIMPLIFIED_YES,
- CALCULATED_SIMPLIFIED_NO,
- CALCULATED_UNSIMPLIFIED_YES,
- CALCULATED_UNSIMPLIFIED_NO,
- MATH_ERROR
+ CALCULATED_SIMPLIFIED_YES, ///< The calculation is done by taking simplified steps, the result it True.
+ CALCULATED_SIMPLIFIED_NO, ///< The calculation is done by taking simplified steps, the result it False.
+ CALCULATED_UNSIMPLIFIED_YES, ///< The calculation is done, the result it True.
+ CALCULATED_UNSIMPLIFIED_NO, ///< The calculation is done, the result it False.
+ MATH_ERROR ///< The calculation is not done because of an error.
};
/**
diff --git a/include/util.hpp b/include/util.hpp
index b3bb8226f..b553f2da7 100644
--- a/include/util.hpp
+++ b/include/util.hpp
@@ -34,6 +34,10 @@
#pragma once
+#ifdef WINDOWS
+ #include
+#endif
+
#include "colors.hpp"
#include "output.hpp"
#include "platform.hpp"
@@ -99,7 +103,6 @@ namespace steppable::__internals::utils
#ifdef WINDOWS
#include
- #include
/**
* @brief Enables VT mode.
@@ -293,6 +296,8 @@ namespace steppable::__internals::numUtils
*/
constexpr bool isZeroString(const std::string& string)
{
+ if (string.empty())
+ return true;
return std::ranges::all_of(string, [](const char c) { return c == '0' or c == '.' or c == '-'; });
}
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 53b737e06..2c731b357 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -41,6 +41,6 @@ if(NOT ${STP_NO_BINDINGS}) # Only create the bindings if needed
endif()
set_target_properties(steppyble PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(steppyble PRIVATE func steppable)
- target_compile_definitions(steppyble PRIVATE NO_MAIN)
+ target_compile_definitions(steppyble PRIVATE NO_MAIN STP_BINDINGS)
target_include_directories(steppyble PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
endif()
diff --git a/lib/bindings.cpp b/lib/bindings.cpp
index 3525f844e..9ae5948fb 100644
--- a/lib/bindings.cpp
+++ b/lib/bindings.cpp
@@ -20,9 +20,10 @@
* SOFTWARE. *
**************************************************************************************************/
-#include "fn/calc.hpp"
-#include "fraction.hpp"
-#include "number.hpp"
+#include "bindings/bindingsFraction.hpp"
+#include "bindings/bindingsMat2d.hpp"
+#include "bindings/bindingsNumber.hpp"
+#include "bindings/fn/bindingsCalc.hpp"
#include
#include
@@ -31,112 +32,15 @@
#include
namespace nb = nanobind;
-using namespace steppable::__internals::calc;
using namespace nb::literals;
NB_MODULE(steppyble, mod) // NOLINT
{
- auto internals = mod.def_submodule("_internals", "Internal functions.");
-
- nb::enum_(mod, "RoundingMode")
- .value("USE_MAXIMUM_PREC", steppable::RoundingMode::USE_MAXIMUM_PREC)
- .value("USE_MINIMUM_PREC", steppable::RoundingMode::USE_MINIMUM_PREC)
- .value("USE_CURRENT_PREC", steppable::RoundingMode::USE_CURRENT_PREC)
- .value("USE_OTHER_PREC", steppable::RoundingMode::USE_OTHER_PREC)
- .value("DISCARD_ALL_DECIMALS", steppable::RoundingMode::DISCARD_ALL_DECIMALS);
-
- nb::class_(mod, "Number")
- .def(nb::init(),
- "value"_a = "0",
- "prec"_a = 5,
- "roundingMode"_a = steppable::USE_CURRENT_PREC)
- .def(nb::self + nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self += nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self - nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self -= nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self * nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self *= nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self / nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self /= nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self % nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self %= nb::self, nb::rv_policy::automatic_reference)
- .def("__pow__", &steppable::Number::operator^)
- .def(nb::self == nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self != nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self < nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self > nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self <= nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self >= nb::self, nb::rv_policy::automatic_reference)
- .def("__repr__", &steppable::Number::present);
-
- nb::class_(mod, "Fraction")
- .def(nb::init(), "top"_a = "1", "bottom"_a = "1")
- .def(nb::self + nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self += nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self - nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self -= nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self * nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self *= nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self / nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self /= nb::self, nb::rv_policy::automatic_reference)
- .def("__pow__", &steppable::Fraction::operator^)
- .def(nb::self == nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self != nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self < nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self > nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self <= nb::self, nb::rv_policy::automatic_reference)
- .def(nb::self >= nb::self, nb::rv_policy::automatic_reference)
- .def("__repr__", &steppable::Fraction::present);
-
mod.doc() = "The Python bindings for Steppable.";
+ steppable::__internals::bindings::bindingsNumber(mod);
+ steppable::__internals::bindings::bindingsFraction(mod);
+ steppable::__internals::bindings::bindingsMatrix(mod);
- internals.def("abs",
- &steppable::__internals::calc::abs,
- "a"_a,
- "steps"_a = 2,
- "Internal function that takes the absolute value of a number.");
- internals.def(
- "add",
- [](const std::string& a, const std::string& b, const int steps) { return add(a, b, steps); },
- "a"_a,
- "b"_a,
- "steps"_a = 2,
- "Internal function that adds two numbers.");
- internals.def(
- "subtract",
- [](const std::string& a, const std::string& b, const int steps) { return subtract(a, b, steps); },
- "a"_a,
- "b"_a,
- "steps"_a = 2,
- "Internal function that subtracts two numbers.");
- internals.def(
- "multiply",
- [](const std::string& a, const std::string& b, const int steps, const int decimals) {
- return multiply(a, b, steps, decimals);
- },
- "a"_a,
- "b"_a,
- "steps"_a = 2,
- "decimals"_a = 8,
- "Internal function that multiplies two numbers.");
- internals.def(
- "divide",
- [](const std::string& a, const std::string& b, const int steps, const int decimals) {
- return divide(a, b, steps, decimals);
- },
- "a"_a,
- "b"_a,
- "steps"_a = 2,
- "decimals"_a = 5,
- "Internal function that divides two numbers.");
- internals.def("divideWithQuotient",
- ÷WithQuotient,
- "Internal function that divides two numbers, but separating the quotient and remainder.");
- internals.def("power",
- &power,
- "a"_a,
- "raiseTo"_a,
- "steps"_a = 2,
- "decimals"_a = 8,
- "Internal function raises a number to a power.");
+ // Internal functions
+ steppable::__internals::bindings::bindingsCalc(mod);
}
diff --git a/lib/bindings/bindingsFraction.hpp b/lib/bindings/bindingsFraction.hpp
new file mode 100644
index 000000000..1e354c0a7
--- /dev/null
+++ b/lib/bindings/bindingsFraction.hpp
@@ -0,0 +1,61 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include "steppable/fraction.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace nb = nanobind;
+using namespace nb::literals;
+
+namespace steppable::__internals::bindings
+{
+ void bindingsFraction(nb::module_& mod)
+ {
+ nb::class_(mod, "Fraction")
+ .def(nb::init(), "top"_a = "1", "bottom"_a = "1")
+ .def(nb::self + nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self += nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self - nb::self, nb::rv_policy::automatic_reference)
+ .def("__neg__", [](steppable::Fraction& fraction) { return -fraction; })
+ .def("__pos__", [](steppable::Fraction& fraction) { return +fraction; })
+ .def(nb::self -= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self * nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self *= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self / nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self /= nb::self, nb::rv_policy::automatic_reference)
+ .def("__pow__", &steppable::Fraction::operator^)
+ .def(nb::self == nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self != nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self < nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self > nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self <= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self >= nb::self, nb::rv_policy::automatic_reference)
+ .def("__repr__", &steppable::Fraction::present);
+ }
+} // namespace steppable::__internals::bindings
diff --git a/lib/bindings/bindingsMat2d.hpp b/lib/bindings/bindingsMat2d.hpp
new file mode 100644
index 000000000..32933a4e8
--- /dev/null
+++ b/lib/bindings/bindingsMat2d.hpp
@@ -0,0 +1,88 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include "steppable/mat2d.hpp"
+#include "steppable/number.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace nb = nanobind;
+using namespace nb::literals;
+
+namespace steppable::__internals::bindings
+{
+ void bindingsMatrix(nanobind::module_& mod)
+ {
+ nb::class_(mod, "Matrix")
+ .def(nb::init, size_t>(), "values"_a, "prec"_a = 5)
+ .def(nb::init, size_t>(), "values"_a, "prec"_a = 5)
+ .def("zeros", [](size_t rows, size_t cols) { return Matrix::zeros(rows, cols); })
+ .def("diag", [](size_t rows_cols, const Number& fill = 1) { return Matrix::diag(rows_cols, fill); })
+ .def("diag", [](size_t rows_cols, const double fill = 1) { return Matrix::diag(rows_cols, fill); })
+ .def("ones", [](size_t rows, size_t cols) { return Matrix::ones(rows, cols); })
+ .def("rref", &Matrix::rref)
+ .def("ref", &Matrix::ref)
+ .def("rank", &Matrix::rank)
+ .def("det", &Matrix::det)
+ .def("transpose", &Matrix::transpose)
+ .def(nb::self + nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self - nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self += nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self -= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self << nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self >> nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self <<= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self >>= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self * nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self *= nb::self, nb::rv_policy::automatic_reference)
+ .def("__neg__", [](Matrix& matrix) { return -matrix; })
+ .def("__pos__", [](Matrix& matrix) { return +matrix; })
+ .def("__repr__", [](Matrix& matrix) { return matrix.present(0); })
+ .def("__getitem__",
+ [](Matrix& matrix, std::array ij) {
+ auto [i, j] = ij;
+ return matrix[{ .y = i, .x = j }];
+ })
+ .def_prop_ro("rows", &Matrix::getRows, nb::rv_policy::copy)
+ .def_prop_ro("cols", &Matrix::getCols, nb::rv_policy::copy)
+ .def_prop_ro(
+ "dims",
+ [](Matrix& matrix) { return std::array{ matrix.getRows(), matrix.getCols() }; },
+ nb::rv_policy::copy)
+ .def(
+ "__iter__",
+ [](Matrix& self) {
+ return nb::make_iterator(nb::type(), "MatrixIterator", self.begin(), self.end());
+ },
+ nb::keep_alive<0, 1>()); // Important for lifetime management
+ ;
+ }
+} // namespace steppable::__internals::bindings
diff --git a/lib/bindings/bindingsNumber.hpp b/lib/bindings/bindingsNumber.hpp
new file mode 100644
index 000000000..09c15f330
--- /dev/null
+++ b/lib/bindings/bindingsNumber.hpp
@@ -0,0 +1,76 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include "steppable/number.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace nb = nanobind;
+using namespace nb::literals;
+
+namespace steppable::__internals::bindings
+{
+ void bindingsNumber(nanobind::module_& mod)
+ {
+ nb::enum_(mod, "RoundingMode")
+ .value("USE_MAXIMUM_PREC", steppable::RoundingMode::USE_MAXIMUM_PREC)
+ .value("USE_MINIMUM_PREC", steppable::RoundingMode::USE_MINIMUM_PREC)
+ .value("USE_CURRENT_PREC", steppable::RoundingMode::USE_CURRENT_PREC)
+ .value("USE_OTHER_PREC", steppable::RoundingMode::USE_OTHER_PREC)
+ .value("DISCARD_ALL_DECIMALS", steppable::RoundingMode::DISCARD_ALL_DECIMALS);
+
+ nb::class_(mod, "Number")
+ .def(nb::init(),
+ "value"_a = "0",
+ "prec"_a = 5,
+ "roundingMode"_a = steppable::RoundingMode::USE_CURRENT_PREC)
+ .def(nb::init(),
+ "value"_a = 0,
+ "prec"_a = 5,
+ "roundingMode"_a = steppable::RoundingMode::USE_CURRENT_PREC)
+ .def(nb::self + nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self += nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self - nb::self, nb::rv_policy::automatic_reference)
+ .def("__neg__", [](steppable::Number& number) { return -number; })
+ .def("__pos__", [](steppable::Number& number) { return +number; })
+ .def(nb::self -= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self * nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self *= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self / nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self /= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self % nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self %= nb::self, nb::rv_policy::automatic_reference)
+ .def("__pow__", &steppable::Number::operator^)
+ .def(nb::self == nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self != nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self < nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self > nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self <= nb::self, nb::rv_policy::automatic_reference)
+ .def(nb::self >= nb::self, nb::rv_policy::automatic_reference)
+ .def("__repr__", &steppable::Number::present);
+ }
+} // namespace steppable::__internals::bindings
diff --git a/lib/bindings/fn/bindingsCalc.hpp b/lib/bindings/fn/bindingsCalc.hpp
new file mode 100644
index 000000000..5732424bc
--- /dev/null
+++ b/lib/bindings/fn/bindingsCalc.hpp
@@ -0,0 +1,93 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+#pragma once
+
+#include "fn/calc.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace nb = nanobind;
+using namespace nb::literals;
+
+namespace steppable::__internals::bindings
+{
+ void bindingsCalc(nb::module_& mod)
+ {
+ using namespace steppable::__internals::calc;
+
+ auto internals = mod.def_submodule("_internals", "Internal functions.");
+ internals.def("abs",
+ &steppable::__internals::calc::abs,
+ "a"_a,
+ "steps"_a = 2,
+ "Internal function that takes the absolute value of a number.");
+ internals.def(
+ "add",
+ [](const std::string& a, const std::string& b, const int steps) { return add(a, b, steps); },
+ "a"_a,
+ "b"_a,
+ "steps"_a = 2,
+ "Internal function that adds two numbers.");
+ internals.def(
+ "subtract",
+ [](const std::string& a, const std::string& b, const int steps) { return subtract(a, b, steps); },
+ "a"_a,
+ "b"_a,
+ "steps"_a = 2,
+ "Internal function that subtracts two numbers.");
+ internals.def(
+ "multiply",
+ [](const std::string& a, const std::string& b, const int steps, const int decimals) {
+ return multiply(a, b, steps, decimals);
+ },
+ "a"_a,
+ "b"_a,
+ "steps"_a = 2,
+ "decimals"_a = 8,
+ "Internal function that multiplies two numbers.");
+ internals.def(
+ "divide",
+ [](const std::string& a, const std::string& b, const int steps, const int decimals) {
+ return divide(a, b, steps, decimals);
+ },
+ "a"_a,
+ "b"_a,
+ "steps"_a = 2,
+ "decimals"_a = 5,
+ "Internal function that divides two numbers.");
+ internals.def("divide_with_quotient",
+ ÷WithQuotient,
+ "Internal function that divides two numbers, but separating the quotient and remainder.");
+ internals.def("power",
+ &power,
+ "a"_a,
+ "raiseTo"_a,
+ "steps"_a = 2,
+ "decimals"_a = 8,
+ "Internal function raises a number to a power.");
+ }
+} // namespace steppable::__internals::bindings
diff --git a/lib/paths.py b/lib/paths.py
index e5cfa72cf..5522f61b3 100644
--- a/lib/paths.py
+++ b/lib/paths.py
@@ -25,6 +25,7 @@
"""
from pathlib import Path
+
from lib.constants import WINDOWS
# section Project paths
diff --git a/lib/printing.py b/lib/printing.py
index b0903977a..87fa93b14 100644
--- a/lib/printing.py
+++ b/lib/printing.py
@@ -20,8 +20,8 @@
# SOFTWARE. #
#####################################################################################################
-import sys
import os
+import sys
from lib.constants import WINDOWS
diff --git a/pyproject.toml b/pyproject.toml
index e2728bbd5..d95a9c4a8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,21 +3,21 @@ name = "steppable"
version = "0.0.1"
description = "A CAS built from scratch."
authors = [
- { name = "Andy Zhang", email = "andy@nwsoft.tech" }
+ { name = "Andy Zhang", email = "z-c-ge@outlook.com" }
]
license = {file = "LICENSE"}
readme = "README.md"
-requires-python = ">= 3.10"
+requires-python = ">= 3.11"
dynamic = ["classifiers", "optional-dependencies"]
dependencies = [
- "matplotlib >= 3.9.0",
- "black >= 24.4.2",
- "isort >= 5.13.2",
"setuptools >= 61.0",
"nanobind >= 1.9.2",
"pyyaml >= 6.0.1",
+ "matplotlib >= 3.9.0; 'Windows' not in platform_system",
+ "black >= 24.4.2",
+ "isort >= 5.13.2",
]
[build-system]
diff --git a/setup.py b/setup.py
index 462309b18..77fab6c6e 100644
--- a/setup.py
+++ b/setup.py
@@ -161,38 +161,42 @@ def build_extension(self, ext: CMakeExtension) -> None:
# The information here can also be placed in setup.cfg - better separation of
# logic and declaration, and simpler if you include description/version in a file.
-setup(
- name="steppable",
- version="0.0.1",
- license="MIT",
- author="Andy Zhang",
- author_email="z-c-ge@outlook.com",
- classifiers=[
- "Development Status :: 2 - Pre-Alpha",
- "Environment :: Console",
- "Intended Audience :: Science/Research",
- "License :: OSI Approved :: MIT License",
- "Natural Language :: English",
- "Operating System :: OS Independent",
- "Programming Language :: C++",
- "Programming Language :: Python",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Programming Language :: Python :: Implementation :: CPython",
- "Topic :: Education",
- "Topic :: Education :: Computer Aided Instruction (CAI)",
- "Topic :: Scientific/Engineering :: Mathematics",
- "Topic :: Software Development :: Libraries",
- ],
- description="Python bindings for Steppable.",
- long_description="Python bindings for Steppable, written using nanobind.",
- ext_modules=[CMakeExtension("steppyble")],
- include_package_data=True,
- packages=["steppyble"],
- package_data={"steppyble": ["__init__.pyi", "fraction.pyi", "number.pyi"]},
- cmdclass={"build_ext": CMakeBuild},
- zip_safe=False,
- extras_require={"test": ["pytest>=6.0"]},
- python_requires=">=3.10",
-)
+try:
+ setup(
+ name="steppable",
+ version="0.0.1",
+ author="Andy Zhang",
+ author_email="z-c-ge@outlook.com",
+ classifiers=[
+ "Development Status :: 2 - Pre-Alpha",
+ "Environment :: Console",
+ "Intended Audience :: Science/Research",
+ "Natural Language :: English",
+ "Operating System :: OS Independent",
+ "Programming Language :: C++",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Topic :: Education",
+ "Topic :: Education :: Computer Aided Instruction (CAI)",
+ "Topic :: Scientific/Engineering :: Mathematics",
+ "Topic :: Software Development :: Libraries",
+ ],
+ description="Python bindings for Steppable.",
+ long_description="Python bindings for Steppable, written using nanobind.",
+ ext_modules=[CMakeExtension("steppyble")],
+ include_package_data=True,
+ packages=["steppyble"],
+ package_data={"steppyble": ["__init__.pyi", "fraction.pyi", "number.pyi"]},
+ cmdclass={"build_ext": CMakeBuild},
+ zip_safe=False,
+ extras_require={"test": ["pytest>=6.0"]},
+ license="MIT",
+ license_files=("LICENSE",),
+ python_requires=">=3.10",
+ )
+except Exception as e:
+ print(e)
+ exit(1)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 83496c2f6..112145fc5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -35,7 +35,7 @@ ADD_LIBRARY(
)
SET_TARGET_PROPERTIES(util PROPERTIES POSITION_INDEPENDENT_CODE ON)
-ADD_LIBRARY(steppable STATIC number.cpp fraction.cpp)
+ADD_LIBRARY(steppable STATIC steppable/number.cpp steppable/fraction.cpp steppable/mat2d.cpp)
SET_TARGET_PROPERTIES(steppable PROPERTIES POSITION_INDEPENDENT_CODE ON)
SET(CALCULATOR_FILES)
@@ -62,7 +62,12 @@ FOREACH(COMPONENT IN LISTS COMPONENTS)
ENDFOREACH()
# FUNC: Library containing all Steppable functions.
-ADD_LIBRARY(func STATIC ${CALCULATOR_FILES} fraction.cpp rounding.cpp factors.cpp number.cpp)
+ADD_LIBRARY(func STATIC ${CALCULATOR_FILES}
+ steppable/fraction.cpp
+ steppable/mat2d.cpp
+ steppable/number.cpp
+ rounding.cpp
+ factors.cpp)
SET_TARGET_PROPERTIES(func PROPERTIES POSITION_INDEPENDENT_CODE ON)
TARGET_INCLUDE_DIRECTORIES(steppable PRIVATE ${STP_BASE_DIRECTORY}/include/)
diff --git a/src/calc/abs/abs.cpp b/src/calc/abs/abs.cpp
index d230271e5..31c2155a5 100644
--- a/src/calc/abs/abs.cpp
+++ b/src/calc/abs/abs.cpp
@@ -30,14 +30,14 @@
#include "absReport.hpp"
#include "argParse.hpp"
+#include "fn/calc.hpp"
#include "getString.hpp"
+#include "steppable/number.hpp"
+#include "types/result.hpp"
#include "util.hpp"
-#include
#include
-#include
#include
-#include
using namespace steppable::__internals::utils;
using namespace steppable::__internals::calc;
diff --git a/src/calc/comparison/comparison.cpp b/src/calc/comparison/comparison.cpp
index 959687a77..7cdde1a0a 100644
--- a/src/calc/comparison/comparison.cpp
+++ b/src/calc/comparison/comparison.cpp
@@ -31,6 +31,8 @@
#include "comparisonReport.hpp"
#include "fn/calc.hpp"
#include "getString.hpp"
+#include "steppable/number.hpp"
+#include "types/result.hpp"
#include "util.hpp"
#include
diff --git a/src/calc/division/division.cpp b/src/calc/division/division.cpp
index 0a5f75444..1df54e640 100644
--- a/src/calc/division/division.cpp
+++ b/src/calc/division/division.cpp
@@ -107,8 +107,8 @@ namespace steppable::__internals::calc
return 1;
// Step 2: Squeeze!
- // Method: If number >= divisor -> return numberScale.
- // Else -> return numberScale - 1.
+ // Method: If number >= divisor -> return diffScale + 1.
+ // Else -> return diffScale.
if (compare(number, divisor, 0) != "0")
return diffScale + 1;
return diffScale;
diff --git a/src/calc/division/divisionReport.cpp b/src/calc/division/divisionReport.cpp
index 34ff77ff3..28cb473c2 100644
--- a/src/calc/division/divisionReport.cpp
+++ b/src/calc/division/divisionReport.cpp
@@ -74,7 +74,18 @@ std::string reportDivision(std::stringstream& tempFormattedAns,
return formattedAns.str();
}
if (resultIsNegative)
+ {
+#if defined(STP_DEB_CALC_DIVISION_RESULT_INSPECT) && DEBUG
+ steppable::output::info("calc::division"s,
+ _number + " " + static_cast(DIVIDED_BY) + " " + _divisor + " = -" + ans);
+#endif
return "-"s + static_cast(ans);
+ }
+
+#if defined(STP_DEB_CALC_DIVISION_RESULT_INSPECT) && DEBUG
+ steppable::output::info("calc::division"s,
+ _number + " " + static_cast(DIVIDED_BY) + " " + _divisor + " = " + ans);
+#endif
return static_cast(ans);
}
diff --git a/src/calc/hyp/hyp.cpp b/src/calc/hyp/hyp.cpp
index b9571088f..5ec72a013 100644
--- a/src/calc/hyp/hyp.cpp
+++ b/src/calc/hyp/hyp.cpp
@@ -54,8 +54,8 @@ namespace steppable::__internals::calc
checkDecimalArg(&decimals);
const auto& twoX = multiply(x, "2", 0, decimals + 2);
- const auto& eTwoX = roundOff(power(static_cast(constants::E), twoX, 0), decimals + 2);
- const auto& eX = roundOff(power(static_cast(constants::E), x, 0), decimals + 2);
+ const auto& eTwoX = exp(twoX, decimals + 2);
+ const auto& eX = exp(x, decimals + 2);
const auto& numerator = subtract(eTwoX, "1", 0);
const auto& denominator = multiply("2", eX, 0, decimals + 2);
diff --git a/src/calc/multiply/multiply.cpp b/src/calc/multiply/multiply.cpp
index cb79a98c1..567dc06d8 100644
--- a/src/calc/multiply/multiply.cpp
+++ b/src/calc/multiply/multiply.cpp
@@ -60,8 +60,6 @@ namespace steppable::__internals::calc
resultIsNegative = false; // NOLINT(bugprone-branch-clone)
else if (aIsNegative or bIsNegative)
resultIsNegative = true;
- else
- resultIsNegative = false;
auto [aInteger, aDecimal, bInteger, bDecimal] = splitNumberArray;
std::stringstream out;
@@ -74,20 +72,34 @@ namespace steppable::__internals::calc
}
// Multiplying by 1 gives the other number.
- if (a == "1")
+ if (compare(a, "1", 0) == "2")
{
if (steps == 2)
out << $("multiply", "36adc27f-07e8-4118-88ed-f148164044da", { a, b }) << "\n";
out << b;
return out.str();
}
- if (b == "1")
+ if (compare(b, "1", 0) == "2")
{
if (steps == 2)
out << $("multiply", "36adc27f-07e8-4118-88ed-f148164044da", { b, a }) << "\n";
out << a;
return out.str();
}
+ if (compare(a, "-1", 0) == "2")
+ {
+ if (steps == 2)
+ out << $("multiply", "36adc27f-07e8-4118-88ed-f148164044da", { a, b }) << "\n";
+ out << standardizeNumber("-" + b);
+ return out.str();
+ }
+ if (compare(b, "-1", 0) == "2")
+ {
+ if (steps == 2)
+ out << $("multiply", "36adc27f-07e8-4118-88ed-f148164044da", { b, a }) << "\n";
+ out << standardizeNumber("-" + a);
+ return out.str();
+ }
// Multiplying by a power of ten means moving the decimal places.
if (isPowerOfTen(a))
@@ -196,7 +208,8 @@ namespace steppable::__internals::calc
prodDigitsOut,
carries,
resultIsNegative,
- steps);
+ steps,
+ decimals);
}
} // namespace steppable::__internals::calc
diff --git a/src/calc/multiply/multiplyReport.cpp b/src/calc/multiply/multiplyReport.cpp
index 9a1cb3c7f..5b11988a8 100644
--- a/src/calc/multiply/multiplyReport.cpp
+++ b/src/calc/multiply/multiplyReport.cpp
@@ -31,6 +31,7 @@
#include "multiplyReport.hpp"
+#include "output.hpp"
#include "rounding.hpp"
#include "symbols.hpp"
#include "util.hpp"
@@ -55,7 +56,8 @@ std::string reportMultiply(const std::string& a,
const std::vector>& prodDigitsOut,
const std::vector>& carries,
const bool resultIsNegative,
- const int steps)
+ const int steps,
+ const int decimals)
{
std::stringstream ss;
@@ -117,7 +119,18 @@ std::string reportMultiply(const std::string& a,
// Add the decimal point.
auto places = aDecimal.length() + bDecimal.length();
out = moveDecimalPlaces(out, -static_cast(places));
+ out = roundOff(out, decimals);
+ out = standardizeNumber(out);
- ss << standardizeNumber(out);
+#if defined(STP_DEB_CALC_MULTIPLY_RESULT_INSPECT) && DEBUG
+ std::stringstream debOut;
+
+ debOut << a << " " << MULTIPLY << " " << b << " = ";
+ if (resultIsNegative)
+ debOut << "-";
+ debOut << out;
+ steppable::output::info("calc::multiply"s, debOut.str());
+#endif
+ ss << out;
return ss.str();
}
diff --git a/src/calc/multiply/multiplyReport.hpp b/src/calc/multiply/multiplyReport.hpp
index 1c5467d7b..d4c79efca 100644
--- a/src/calc/multiply/multiplyReport.hpp
+++ b/src/calc/multiply/multiplyReport.hpp
@@ -63,4 +63,5 @@ std::string reportMultiply(const std::string& a,
const std::vector>& prodDigitsOut,
const std::vector>& carries,
bool resultIsNegative = false,
- int steps = 2);
+ int steps = 2,
+ int decimals = 1);
diff --git a/src/calc/power/power.cpp b/src/calc/power/power.cpp
index b67f58ca2..b245e3cb4 100644
--- a/src/calc/power/power.cpp
+++ b/src/calc/power/power.cpp
@@ -30,7 +30,7 @@
#include "argParse.hpp"
#include "constants.hpp"
#include "fn/calc.hpp"
-#include "fraction.hpp"
+#include "steppable/fraction.hpp"
#include "getString.hpp"
#include "powerReport.hpp"
#include "rounding.hpp"
@@ -118,19 +118,35 @@ namespace steppable::__internals::calc
return reportPower(number, raiseTo, numberTrailingZeros, negativePower, steps, decimals);
}
- std::string exp(const std::string& x, const size_t decimals)
+ std::string _exp(const std::string& x, const size_t decimals) // NOLINT(*-no-recursion)
{
- constexpr size_t iter = 50;
+ if (compare(x, "4", 0) == "1")
+ {
+ std::string halfX = divide(x, "2", 0, static_cast(decimals) + 2);
+ std::string result = _exp(halfX, decimals + 2);
+ return multiply(result, result, 0, static_cast(decimals + 2));
+ }
+
std::string sum = "1";
std::string term = "1";
- for (size_t i = 1; i < iter; i++)
+ const auto errorStr = "0." + std::string(decimals + 1, '0') + "1";
+ for (int i = 1;; i++)
{
std::string frac = divide(x, std::to_string(i), 0, static_cast(decimals) + 2);
term = multiply(term, frac, 0, static_cast(decimals) + 2);
+ if (compare(term, errorStr, 0) != "1")
+ break;
+
sum = add(sum, term, 0);
}
return roundOff(sum, decimals);
}
+
+ std::string exp(const std::string& x, const size_t decimals)
+ {
+ const auto result = _exp(x, decimals + 2);
+ return roundOff(result, decimals);
+ }
} // namespace steppable::__internals::calc
#ifndef NO_MAIN
diff --git a/src/calc/power/powerReport.cpp b/src/calc/power/powerReport.cpp
index 53dc20dbb..84f0e7401 100644
--- a/src/calc/power/powerReport.cpp
+++ b/src/calc/power/powerReport.cpp
@@ -32,9 +32,9 @@
#include "powerReport.hpp"
#include "fn/calc.hpp"
-#include "fraction.hpp"
#include "getString.hpp"
#include "rounding.hpp"
+#include "steppable/fraction.hpp"
#include "symbols.hpp"
#include "util.hpp"
@@ -93,16 +93,16 @@ std::string reportPower(const std::string& _number,
loop(raiseTo, [&](const auto& i) {
if (not negativePower)
- result = multiply(result, _number, 0, decimals + 1);
+ result = multiply(result, _number, 0, decimals + 2);
else
- result = divide("1", result, 0, decimals + 1);
+ result = divide("1", result, 0, decimals + 2);
auto currentPower = add(i, "1", 0);
if (steps == 2)
{
if (not negativePower)
- ss << BECAUSE << " " << multiply(result, _number, 1) << '\n';
+ ss << BECAUSE << " " << multiply(result, _number, 1, decimals + 2) << '\n';
else
- ss << BECAUSE << " " << divide("1", result, 1) << '\n';
+ ss << BECAUSE << " " << divide("1", result, 1, decimals + 2) << '\n';
if (negativePower)
currentPower = "-" + currentPower;
diff --git a/src/calc/power/powerReport.hpp b/src/calc/power/powerReport.hpp
index b2dc42a23..860fb7c1f 100644
--- a/src/calc/power/powerReport.hpp
+++ b/src/calc/power/powerReport.hpp
@@ -27,7 +27,7 @@
*/
#pragma once
-#include "fraction.hpp"
+#include "steppable/fraction.hpp"
#include
diff --git a/src/calc/root/root.cpp b/src/calc/root/root.cpp
index 8887758e2..726fb5f92 100644
--- a/src/calc/root/root.cpp
+++ b/src/calc/root/root.cpp
@@ -33,7 +33,7 @@
#include "argParse.hpp"
#include "factors.hpp"
#include "fn/calc.hpp"
-#include "fraction.hpp"
+#include "steppable/fraction.hpp"
#include "getString.hpp"
#include "rootReport.hpp"
#include "rounding.hpp"
diff --git a/src/calc/root/rootReport.hpp b/src/calc/root/rootReport.hpp
index ba1a07331..bfa3fe671 100644
--- a/src/calc/root/rootReport.hpp
+++ b/src/calc/root/rootReport.hpp
@@ -22,7 +22,7 @@
#pragma once
-#include "fraction.hpp"
+#include "steppable/fraction.hpp"
#include
diff --git a/src/calc/subtract/subtract.cpp b/src/calc/subtract/subtract.cpp
index 910028423..c073a6707 100644
--- a/src/calc/subtract/subtract.cpp
+++ b/src/calc/subtract/subtract.cpp
@@ -32,8 +32,10 @@
#include "argParse.hpp"
#include "fn/calc.hpp"
#include "getString.hpp"
+#include "steppable/number.hpp"
#include "subtractReport.hpp"
#include "symbols.hpp"
+#include "types/result.hpp"
#include "util.hpp"
#include
@@ -96,8 +98,8 @@ namespace steppable::__internals::calc
if (bIsNegative)
{
if (steps == 2)
- // Adding {0} and {1} since {1} is negative
- std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a, b.substr(1) }) << "\n";
+ // Adding {0} and {1} since {2} is negative
+ std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a, b.substr(1), b }) << "\n";
resultIsNegative = false;
return add(a, b.substr(1), steps);
}
@@ -128,8 +130,8 @@ namespace steppable::__internals::calc
std::ranges::reverse(aStr);
std::ranges::reverse(bStr);
- std::vector diffDigits(aStr.length(), 0);
- std::vector borrows(aStr.length());
+ std::vector diffDigits(aStr.length(), 0);
+ std::vector borrows(aStr.length(), 0);
std::ranges::copy(aStr, borrows.begin());
std::ranges::for_each(borrows, [](int& c) { c -= '0'; });
for (int index = 0; index < aStr.length(); index++)
@@ -185,6 +187,13 @@ namespace steppable::__internals::calc
resultIsNegative,
noMinus);
}
+
+ types::Result error(const std::string& a, const std::string& b)
+ {
+ auto difference = subtract(a, b, 0);
+ difference = abs(difference, 0);
+ return { { a, b }, { difference }, Number(difference), types::Status::CALCULATED_UNSIMPLIFIED };
+ }
} // namespace steppable::__internals::calc
#ifndef NO_MAIN
diff --git a/src/calc/subtract/subtractReport.cpp b/src/calc/subtract/subtractReport.cpp
index d9472b0a7..983f1ea19 100644
--- a/src/calc/subtract/subtractReport.cpp
+++ b/src/calc/subtract/subtractReport.cpp
@@ -127,6 +127,11 @@ std::string reportSubtract(const std::string& aInteger,
ansStream << outputChar; // No spaces
}
+#if defined(STP_DEB_CALC_SUBTRACT_RESULT_INSPECT) && DEBUG
+ auto bStr = bInteger + '.' + bDecimal;
+ steppable::output::info("calc::subtract"s, aStr + " - " + bStr + " = " + ansStream.str());
+#endif
+
ss << standardizeNumber(ansStream.str());
- return std::move(ss.str());
+ return ss.str();
}
diff --git a/src/getString.cpp b/src/getString.cpp
index a09ee5656..d6ff236b0 100644
--- a/src/getString.cpp
+++ b/src/getString.cpp
@@ -87,24 +87,30 @@ namespace steppable::localization
R"(^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}) >> \"([^\"]+?)\"$)");
const auto& confDir = getConfDirectory();
- const auto& lang = getLanguage();
+ std::string lang = getLanguage();
- const auto& langDir = confDir / "translations" / lang;
- const auto& originFile = langDir / (origin + ".stp_localized");
+ auto langDir = confDir / "translations" / lang;
+ auto originFile = langDir / (origin + ".stp_localized");
- if (not exists(originFile))
+ // Since en-US is the default language, not having this language means the package is not properly installed
+ if (not exists(originFile) and lang == "en-US")
{
// If the file does not exist, we return the key as the string. Also, we log an error.
- output::error("getString"s, "Cannot find the localization file: " + originFile.string());
+ output::error("localization::getString"s, "Cannot find the localization file: " + originFile.string());
return "<"s + key + ">"s; // Since the key is in UUID format, we need to make it look like a placeholder.
}
+ if (not exists(originFile))
+ {
+ langDir = confDir / "translations" / "en-US";
+ originFile = langDir / (origin + ".stp_localized");
+ }
// Read the file and get the string
std::ifstream file(originFile);
if (not file.is_open())
{
// If we cannot open the file, we return the key as the string. Also, we log an error.
- output::error("getString"s, "Cannot open the localization file: " + originFile.string());
+ output::error("localization::getString"s, "Cannot open the localization file: " + originFile.string());
return "<"s + key + ">"s; // Since the key is in UUID format, we need to make it look like a placeholder.
}
@@ -134,7 +140,7 @@ namespace steppable::localization
}
else
{
- output::error("getString"s,
+ output::error("localization::getString"s,
"Malformed line in localization file: " + originFile.string() + " -> " + line);
break;
}
@@ -143,7 +149,7 @@ namespace steppable::localization
// If we cannot find the string, we return the key as the string. Also, we log an error.
if (translation.empty())
{
- output::error("getString"s, "Cannot find the string for the key: " + key);
+ output::error("localization::getString"s, "Cannot find the string for the key: " + key);
return "<" + key + ">"; // Since the key is in UUID format, we need to make it look like a placeholder.
}
return translation;
diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp
new file mode 100644
index 000000000..205435ff7
--- /dev/null
+++ b/src/matrix/ref/ref.cpp
@@ -0,0 +1,55 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+/**
+ * @file ref.cpp
+ * @brief Desciption
+ *
+ * @author Andy Zhang
+ * @date 31st May 2025
+ */
+
+#include "refReport.hpp"
+#include "steppable/mat2d.hpp"
+#include "steppable/number.hpp"
+
+#include
+#include
+
+namespace steppable::__internals::matrix
+{
+ std::string ref(/* Arguments... */) { return ""; }
+} // namespace steppable::__internals::matrix
+
+int main()
+{
+ using namespace steppable;
+ std::vector> matrix = { { 2, 1, -1, 3, 2, 8 },
+ { 1, -2, 1, 0, 2, -4 },
+ { 3, 1, -3, 4, 1, 6 },
+ { 1, 1, 1, 1, 1, 5 },
+ { 2, -1, 2, -1, 3, 3 } };
+ auto mat = steppable::Matrix(matrix);
+ mat = mat.rref();
+
+ std::cout << mat.present(1) << "\n";
+}
diff --git a/src/matrix/ref/refReport.cpp b/src/matrix/ref/refReport.cpp
new file mode 100644
index 000000000..e8defb66e
--- /dev/null
+++ b/src/matrix/ref/refReport.cpp
@@ -0,0 +1,35 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+/**
+ * @file refReport.cpp
+ * @brief Desciption
+ *
+ * @author Andy Zhang
+ * @date 31st May 2025
+ */
+
+#include "refReport.hpp"
+
+#include
+
+std::string reportRef() { return ""; }
diff --git a/src/matrix/ref/refReport.hpp b/src/matrix/ref/refReport.hpp
new file mode 100644
index 000000000..f6099478b
--- /dev/null
+++ b/src/matrix/ref/refReport.hpp
@@ -0,0 +1,33 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+/**
+ * @file refReport.hpp
+ * @brief Desciption
+ *
+ * @author Andy Zhang
+ * @date 31st May 2025
+ */
+
+#include
+
+std::string reportRef();
diff --git a/src/platform.cpp b/src/platform.cpp
index 748f60a05..f4fe76740 100644
--- a/src/platform.cpp
+++ b/src/platform.cpp
@@ -61,9 +61,7 @@ namespace steppable::__internals::utils
std::array errBuffer{};
const char* homeEnv = nullptr;
int error = 0;
- struct passwd pw
- {
- };
+ struct passwd pw{};
struct passwd* result = nullptr;
uid_t userId = getuid();
diff --git a/src/rounding.cpp b/src/rounding.cpp
index 421f7dc2d..994b90b3e 100644
--- a/src/rounding.cpp
+++ b/src/rounding.cpp
@@ -57,14 +57,14 @@ namespace steppable::__internals::numUtils
return integer;
}
- std::string roundOff(const std::string& _number, const size_t digits)
+ std::string roundOff(const std::string& _number, const size_t digits, Rounding mode)
{
auto number = _number;
if (number.empty())
return "0";
if (number.find('.') == std::string::npos)
return number;
- auto splitNumberResult = splitNumber(number, "0", true, true, false, false);
+ auto splitNumberResult = splitNumber(number, "0", false, false, false, false);
// Round off the number
auto splitNumberArray = splitNumberResult.splitNumberArray;
@@ -73,7 +73,7 @@ namespace steppable::__internals::numUtils
bool isNegative = splitNumberResult.aIsNegative;
if (decimal.length() < digits)
- return _number;
+ decimal += std::string(digits - decimal.length(), '0');
// Preserve one digit after the rounded digit
decimal = decimal.substr(0, digits + 1);
@@ -85,8 +85,22 @@ namespace steppable::__internals::numUtils
}
auto newDecimal = decimal.substr(0, digits);
std::ranges::reverse(newDecimal.begin(), newDecimal.end());
+ bool condition = false;
+ switch (mode)
+ {
+ case Rounding::ROUND_OFF:
+ condition = decimal.back() >= '5';
+ break;
+ case Rounding::ROUND_DOWN:
+ condition = false;
+ break;
+ case Rounding::ROUND_UP:
+ condition = true;
+ default:
+ break;
+ }
- if (decimal.back() >= '5')
+ if (condition)
{
// Need to round up the digit
for (size_t i = 0; i < newDecimal.length(); i++)
@@ -107,18 +121,26 @@ namespace steppable::__internals::numUtils
}
}
std::ranges::reverse(newDecimal.begin(), newDecimal.end());
+
+ std::string result;
decimal = newDecimal.substr(0, digits);
if (isNegative)
integer = '-' + integer;
if (decimal.empty() and digits > 0)
- return integer + "." + std::string(digits, '0');
- if (decimal.empty())
- return integer;
- return integer + "." + decimal;
+ result = integer + "." + std::string(digits, '0');
+ else if (decimal.empty())
+ result = integer;
+ else
+ result = integer + "." + decimal;
+
+ result = simplifyPolarity(result);
+ return result;
}
std::string moveDecimalPlaces(const std::string& _number, const long places)
{
+ if (_number.empty())
+ return "0";
auto number = _number;
// No change
if (places == 0)
diff --git a/src/fraction.cpp b/src/steppable/fraction.cpp
similarity index 95%
rename from src/fraction.cpp
rename to src/steppable/fraction.cpp
index 018b1f248..b329774c6 100644
--- a/src/fraction.cpp
+++ b/src/steppable/fraction.cpp
@@ -28,11 +28,11 @@
* @date 13th March 2024
*/
-#include "fraction.hpp"
+#include "steppable/fraction.hpp"
#include "exceptions.hpp"
#include "fn/calc.hpp"
-#include "number.hpp"
+#include "steppable/number.hpp"
#include "symbols.hpp"
#include "util.hpp"
@@ -230,6 +230,16 @@ namespace steppable
return compare(thisNewTop, otherNewTop, 0) != "0";
}
+ Fraction Fraction::operator+() const { return *this; }
+
+ Fraction Fraction::operator-() const
+ {
+ auto newTop = "-" + top;
+ auto newFrac = Fraction(newTop, bottom);
+ newFrac.simplify();
+ return newFrac;
+ }
+
void Fraction::reciprocal()
{
std::ranges::swap(top, bottom);
@@ -239,6 +249,8 @@ namespace steppable
void Fraction::simplify()
{
// Make sure the fraction does not contain decimal points.
+ top = standardizeNumber(top);
+ bottom = standardizeNumber(bottom);
while (isDecimal(top) or isDecimal(bottom))
{
top = multiply(top, "10", 0);
diff --git a/src/steppable/mat2d.cpp b/src/steppable/mat2d.cpp
new file mode 100644
index 000000000..a29dc0d4c
--- /dev/null
+++ b/src/steppable/mat2d.cpp
@@ -0,0 +1,595 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+/**
+ * @file mat2d.cpp
+ * @brief Implements methods for matrix manipulation.
+ * @author Andy Zhang
+ * @date 31 May 2025
+ */
+
+#include "steppable/mat2d.hpp"
+
+#include "output.hpp"
+#include "platform.hpp"
+#include "rounding.hpp"
+#include "steppable/number.hpp"
+#include "symbols.hpp"
+#include "util.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace steppable
+{
+ using namespace __internals;
+ using namespace __internals::numUtils;
+
+ namespace prettyPrint::printers
+ {
+ std::string ppMatrix(const MatVec2D& matrix, const int endRows)
+ {
+ int maxLen = 0;
+ std::stringstream ss;
+ for (const auto& row : matrix)
+ {
+ for (const auto& val : row)
+ {
+ auto length = static_cast(val.present().length());
+ maxLen = std::max(length, maxLen);
+ }
+ }
+
+ size_t matrixRows = matrix.size();
+ for (size_t rowIdx = 0; rowIdx < matrixRows; rowIdx++)
+ {
+ const auto& row = matrix[rowIdx];
+ if (matrixRows == 1)
+ ss << "[";
+ else if (rowIdx == 0)
+ ss << symbols::MATRIX_LEFT_TOP;
+ else if (rowIdx == matrixRows - 1)
+ ss << symbols::MATRIX_LEFT_BOTTOM;
+ else
+ ss << symbols::MATRIX_LEFT_MIDDLE;
+ for (size_t valIdx = 0; valIdx < row.size(); valIdx++)
+ {
+ const auto& val = row[valIdx];
+ if (valIdx + endRows == row.size())
+ ss << symbols::MATRIX_LEFT_MIDDLE;
+ ss << std::right << std::setw(maxLen + 1) << val.present();
+ ss << " ";
+ }
+
+ if (matrixRows == 1)
+ ss << "]";
+ else if (rowIdx == 0)
+ ss << symbols::MATRIX_RIGHT_TOP;
+ else if (rowIdx == matrixRows - 1)
+ ss << symbols::MATRIX_RIGHT_BOTTOM;
+ else
+ ss << symbols::MATRIX_RIGHT_MIDDLE;
+ ss << "\n";
+ }
+ return ss.str();
+ }
+ } // namespace prettyPrint::printers
+
+ void Matrix::_checkDataSanity(const MatVec2D& data)
+ {
+ size_t size = data.front().size();
+ for (const auto& row : data)
+ {
+ const size_t rowSize = row.size();
+ if (rowSize != size)
+ {
+ output::error("Matrix::_checkDataSanity"s, "Matrix has non-uniform dimensions."s);
+ utils::programSafeExit(1);
+ }
+ size = rowSize;
+ }
+ }
+
+ Matrix::Matrix() : data({ {} }) { _cols = _rows = 0; }
+
+ Matrix::Matrix(const size_t rows, const size_t cols, const Number& fill) :
+ data(std::vector(rows, std::vector(cols, fill))), _cols(cols), _rows(rows)
+ {
+ data = roundOffValues(data, static_cast(prec));
+ _checkDataSanity(data);
+ }
+
+ Matrix::Matrix(const MatVec2D& data, const size_t prec) :
+ data(data), _cols(data.front().size()), _rows(data.size()), prec(prec)
+ {
+ for (auto& row : this->data)
+ for (auto& value : row)
+ value.setPrec(prec + 3, RoundingMode::USE_MAXIMUM_PREC);
+ }
+
+ void Matrix::_checkIdxSanity(const YXPoint* point) const
+ {
+ const auto x = point->x;
+ const auto y = point->y;
+
+ if (x > _cols)
+ {
+ output::error("Matrix::operator[]"s,
+ "Incorrect x parameter. {0} exceeds the number of columns in this matrix ({1})."s,
+ { std::to_string(x), std::to_string(_cols) });
+ utils::programSafeExit(1);
+ }
+ if (y > _rows)
+ {
+ output::error("Matrix::operator[]"s,
+ "Incorrect y parameter. {0} exceeds the number of rows in this matrix ({1})."s,
+ { std::to_string(y), std::to_string(_rows) });
+ utils::programSafeExit(1);
+ }
+ }
+
+ MatVec2D Matrix::roundOffValues(const MatVec2D& _data, const size_t prec)
+ {
+ auto data = _data;
+ for (auto& row : data)
+ for (auto& val : row)
+ {
+ auto valueString = val.present();
+
+ if (valueString == "Indeterminate")
+ valueString = "0";
+ else
+ {
+ valueString = roundOff(val.present(), prec);
+ valueString = standardizeNumber(valueString);
+ }
+ val.set(valueString);
+ }
+ return data;
+ }
+
+ Matrix Matrix::roundOffValues(const size_t prec) const { return roundOffValues(data, prec); }
+
+ Matrix Matrix::rref() const
+ {
+ // Adapted from https://stackoverflow.com/a/31761026/14868780
+ auto matrix = data;
+ matrix = roundOffValues(matrix, static_cast(prec));
+#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG
+ std::cout << prettyPrint::printers::ppMatrix(matrix, 1) << "\n";
+#endif
+
+ for (size_t lead = 0; lead < _rows; lead++)
+ {
+ Number divisor("0", 30, RoundingMode::USE_MAXIMUM_PREC);
+ Number multiplier("0", 30, RoundingMode::USE_MAXIMUM_PREC);
+ for (size_t r = 0; r < _rows; r++)
+ {
+ divisor = matrix[lead][lead];
+ multiplier = matrix[r][lead] / matrix[lead][lead];
+ for (size_t c = 0; c < _cols; c++)
+ if (r == lead)
+ {
+#if defined(STP_DEB_CALC_DIVISION_RESULT_INSPECT) && DEBUG
+ auto oldMatrixRC = matrix[r][c];
+#endif
+
+ matrix[r][c] /= divisor;
+
+#if defined(STP_DEB_CALC_DIVISION_RESULT_INSPECT) && DEBUG
+ output::info("Matrix::rref"s,
+ oldMatrixRC.present() + " " + std::string(__internals::symbols::DIVIDED_BY) + " " +
+ divisor.present() + " = " + matrix[r][c].present());
+#endif
+ }
+ else
+ {
+#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG
+ auto oldMatrixRC = matrix[r][c];
+ auto oldMatrixLeadC = matrix[lead][c];
+#endif
+
+ auto multiplyResult = matrix[lead][c] * multiplier;
+ matrix[r][c] -= multiplyResult;
+
+#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG
+ output::info("Matrix::rref"s,
+ oldMatrixRC.present() + " - " + oldMatrixLeadC.present() + " " +
+ std::string(__internals::symbols::MULTIPLY) + " " + multiplier.present());
+ output::info("Matrix::rref"s,
+ " = " + oldMatrixRC.present() + " - " + multiplyResult.present());
+ output::info("Matrix::rref"s, " = " + matrix[r][c].present());
+#endif
+ }
+
+ matrix = roundOffValues(matrix, static_cast(prec) + 3);
+ }
+#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG
+ std::cout << prettyPrint::printers::ppMatrix(matrix, 1) << "\n";
+#endif
+ }
+
+ matrix = roundOffValues(matrix, static_cast(prec));
+ return { matrix, prec };
+ }
+
+ Matrix Matrix::ref() const
+ {
+ MatVec2D mat = data;
+ mat = roundOffValues(mat, static_cast(prec) + 3);
+
+ for (int col = 0, row = 0; col < _cols && row < _rows; ++col)
+ {
+ // Find first non-zero in column col, at or below row
+ int sel = -1;
+ for (int i = row; i < _rows; ++i)
+ if (mat[i][col] != 0.0)
+ {
+ sel = i;
+ break;
+ }
+ if (sel == -1)
+ continue; // All zeros in this column
+
+ if (sel != row)
+ std::swap(mat[row], mat[sel]); // Swap if needed
+
+ // Eliminate below
+ for (int i = row + 1; i < _rows; ++i)
+ {
+ Number factor = mat[i][col] / mat[row][col];
+ for (int j = col; j < _cols; ++j)
+ mat[i][j] -= factor * mat[row][j];
+ }
+ ++row;
+ }
+ mat = roundOffValues(mat, static_cast(prec));
+ return { mat, prec };
+ }
+
+ Number Matrix::det() const
+ {
+ if (_rows != _cols)
+ {
+ output::error("Matrix::det"s, "Matrix is not a square."s);
+ utils::programSafeExit(1);
+ }
+ int sign = 1;
+ Number determinant = 1;
+ MatVec2D mat = data;
+ mat = roundOffValues(mat, static_cast(prec) + 3);
+
+ for (size_t col = 0, row = 0; col < _cols && row < _rows; ++col)
+ {
+ // Find first non-zero in column col, at or below row
+ size_t sel = -1;
+ for (size_t i = row; i < _rows; ++i)
+ if (mat[i][col] != 0.0)
+ {
+ sel = i;
+ break;
+ }
+ if (sel == -1)
+ continue; // All zeros in this column
+
+ if (sel != row)
+ {
+ // Swap rows - determinant becomes negative
+ sign = -sign;
+ std::swap(mat[row], mat[sel]);
+ }
+
+ // Eliminate below
+ for (size_t i = row + 1; i < _rows; ++i)
+ {
+ Number factor = mat[i][col] / mat[row][col];
+ for (size_t j = col; j < _cols; ++j)
+ mat[i][j] -= factor * mat[row][j];
+ }
+ ++row;
+ }
+ determinant *= sign;
+ mat = roundOffValues(mat, static_cast(prec));
+ for (size_t i = 0; i < _cols; i++)
+ determinant *= mat[i][i];
+ return determinant;
+ }
+
+ Matrix Matrix::operator+(const Matrix& rhs) const
+ {
+ if (rhs._cols != _cols)
+ {
+ output::error("Matrix::operator+"s,
+ "Matrix dimensions mismatch. Expect {0} columns. Got {1} columns."s,
+ { std::to_string(_cols), std::to_string(rhs._cols) });
+ utils::programSafeExit(1);
+ }
+ if (rhs._rows != _rows)
+ {
+ output::error("Matrix::operator+"s,
+ "Matrix dimensions mismatch. Expect {0} rows. Got {1} rows."s,
+ { std::to_string(_rows), std::to_string(rhs._rows) });
+ utils::programSafeExit(1);
+ }
+
+ Matrix output = Matrix::zeros(_cols, _rows);
+
+ for (size_t i = 0; i < _rows; i++)
+ for (size_t j = 0; j < _cols; j++)
+ output.data[i][j] += rhs.data[i][j];
+ return output;
+ }
+
+ Matrix Matrix::operator+() const { return *this; }
+
+ Matrix Matrix::operator+=(const Matrix& rhs)
+ {
+ *this = *this + rhs;
+ return *this;
+ }
+
+ Matrix Matrix::operator-(const Matrix& rhs) const { return *this + -rhs; }
+
+ Matrix Matrix::operator-() const
+ {
+ Matrix newMatrix = *this;
+ for (auto& row : newMatrix.data)
+ for (auto& value : row)
+ value = -value;
+ return newMatrix;
+ }
+
+ Matrix Matrix::operator-=(const Matrix& rhs)
+ {
+ *this = *this - rhs;
+ return *this;
+ }
+
+ Matrix Matrix::operator*(const Number& rhs) const
+ {
+ Matrix newMatrix = *this;
+ for (auto& row : newMatrix.data)
+ for (auto& value : row)
+ value *= rhs;
+ return newMatrix;
+ }
+
+ Matrix Matrix::operator*(const Matrix& rhs) const
+ {
+ if (_cols != rhs._rows)
+ {
+ output::error("Matrix::operator*"s, "Incorrect matrix dimensions for multiplication."s);
+ // https://en.wikipedia.org/wiki/Matrix_multiplication
+ output::info("Matrix::operator*"s,
+ "For matrix multiplication, the number of columns in the first matrix must be equal to the "
+ "number of rows in the second matrix"s);
+ utils::programSafeExit(1);
+ }
+ Matrix matrix = Matrix::zeros(_rows, rhs._cols);
+ for (size_t j = 0; j < rhs._rows; j++)
+ for (size_t k = 0; k < _cols; k++)
+ for (size_t i = 0; i < _rows; i++)
+ matrix.data[i][j] += data[i][k] * rhs.data[k][j];
+ return matrix;
+ }
+
+ Matrix Matrix::operator<<(const Matrix& rhs) const
+ {
+ if (rhs._rows != _rows)
+ {
+ output::error("Matrix::operator<<"s,
+ "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}"s,
+ { std::to_string(rhs._rows), std::to_string(rhs._rows) });
+ utils::programSafeExit(1);
+ }
+
+ auto matrix = Matrix(_rows, _cols + rhs._cols);
+
+ // Copy current matrix.
+ for (size_t i = 0; i < _rows; i++)
+ for (size_t j = 0; j < _cols; j++)
+ matrix[{ .y = i, .x = j }] = data[i][j];
+
+ // Copy other matrix.
+ for (size_t i = 0; i < _rows; i++)
+ for (size_t j = 0; j < rhs._cols; j++)
+ matrix[{ .y = i, .x = j + _cols }] = rhs.data[i][j];
+
+ return matrix;
+ }
+
+ Matrix Matrix::operator>>(const Matrix& rhs) const
+ {
+ if (rhs._rows != _rows)
+ {
+ output::error("Matrix::operator>>"s,
+ "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}"s,
+ { std::to_string(rhs._rows), std::to_string(rhs._rows) });
+ utils::programSafeExit(1);
+ }
+
+ auto matrix = Matrix(_rows, _cols + rhs._cols);
+
+ // Copy other matrix.
+ for (size_t i = 0; i < _rows; i++)
+ for (size_t j = 0; j < rhs._cols; j++)
+ matrix[{ .y = i, .x = j }] = rhs.data[i][j];
+
+ // Copy current matrix.
+ for (size_t i = 0; i < _rows; i++)
+ for (size_t j = 0; j < _cols; j++)
+ matrix[{ .y = i, .x = j + _cols }] = data[i][j];
+
+ return matrix;
+ }
+
+ Matrix Matrix::operator<<=(const Matrix& rhs)
+ {
+ *this = *this << rhs;
+ return *this;
+ }
+
+ Matrix Matrix::operator>>=(const Matrix& rhs)
+ {
+ *this = *this >> rhs;
+ return *this;
+ }
+
+ Matrix Matrix::operator*=(const Number& rhs)
+ {
+ *this = *this * rhs;
+ return *this;
+ }
+
+ Matrix Matrix::operator*=(const Matrix& rhs)
+ {
+ *this = *this * rhs;
+ return *this;
+ }
+
+ Matrix Matrix::operator^(const Number& times) const
+ {
+ if (_rows != _cols)
+ {
+ output::error("Matrix::operator^"s, "Matrix is not square."s);
+ utils::programSafeExit(1);
+ }
+
+ auto matrix = *this;
+
+ // Take inverse of matrix
+ if (times == -1)
+ {
+ matrix <<= diag(_rows);
+ matrix = matrix.rref();
+ matrix = matrix[{ .y1 = 0, .x1 = _rows, .y2 = _rows - 1, .x2 = _cols * 2 - 1 }];
+ return matrix;
+ }
+ for (Number i = 0; i < times; ++i)
+ matrix *= matrix;
+ return matrix;
+ }
+
+ std::string Matrix::present(const int endRows) const { return prettyPrint::printers::ppMatrix(data, endRows); }
+
+ Matrix Matrix::ones(const size_t rows, const size_t cols)
+ {
+ auto matrix = Matrix(rows, cols, Number("1"));
+ return matrix;
+ }
+
+ Matrix Matrix::zeros(const size_t rows, const size_t cols)
+ {
+ auto matrix = Matrix(rows, cols);
+ return matrix;
+ }
+
+ Matrix Matrix::diag(const size_t colsRows, const Number& fill)
+ {
+ auto matrix = Matrix(colsRows, colsRows);
+ for (size_t i = 0; i < colsRows; i++)
+ matrix[{ .y = i, .x = i }] = fill;
+ return matrix;
+ }
+
+ Number Matrix::rank() const
+ {
+ auto matrix = *this;
+ matrix = matrix.rref();
+
+#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG
+ std::cout << prettyPrint::printers::ppMatrix(matrix.data) << "\n";
+#endif
+ size_t rank = 0;
+
+ for (const auto& row : matrix)
+ {
+ size_t count = 0;
+ for (const auto& val : row)
+ if (val != 0)
+ count++;
+
+ if (count != 0)
+ rank++;
+ }
+ return { rank };
+ }
+
+ Matrix Matrix::transpose() const
+ {
+ Matrix matrix(_cols, _rows);
+ for (size_t i = 0; i < _cols; i++)
+ for (size_t j = 0; j < _rows; j++)
+ matrix[{ .y = j, .x = i }] = data[i][j];
+ return matrix;
+ }
+
+ bool Matrix::operator==(const Matrix& rhs) const { return data == rhs.data; }
+
+ bool Matrix::operator!=(const Matrix& rhs) const { return not(*this == rhs); }
+
+ Number& Matrix::operator[](const YXPoint& point)
+ {
+ const auto x = point.x;
+ const auto y = point.y;
+
+ _checkIdxSanity(&point);
+ return data[y][x];
+ }
+
+ Number Matrix::operator[](const YXPoint& point) const
+ {
+ const auto x = point.x;
+ const auto y = point.y;
+
+ _checkIdxSanity(&point);
+ return data[y][x];
+ }
+
+ Matrix Matrix::operator[](const YX2Points& point) const
+ {
+ auto [y1, x1, y2, x2] = point;
+ const auto startPoint = YXPoint{ .y = y1, .x = x1 };
+ _checkIdxSanity(&startPoint);
+
+ const auto endPoint = YXPoint{ .y = y2, .x = x2 };
+ _checkIdxSanity(&endPoint);
+
+ // Make sure the end dimensions are always greater
+ if (y2 < y1 or x2 < x1)
+ {
+ std::swap(y2, y1);
+ std::swap(x2, x1);
+ }
+
+ auto matrix = Matrix(y2 - y1 + 1, x2 - x1 + 1);
+
+ for (size_t i = y1; i <= y2; i++)
+ for (size_t j = x1; j <= x2; j++)
+ matrix[{ .y = i - y1, .x = j - x1 }] = data[i][j];
+ return matrix;
+ }
+} // namespace steppable
diff --git a/src/number.cpp b/src/steppable/number.cpp
similarity index 66%
rename from src/number.cpp
rename to src/steppable/number.cpp
index 838f2e061..86f99dd38 100644
--- a/src/number.cpp
+++ b/src/steppable/number.cpp
@@ -28,10 +28,15 @@
* @date 1st April 2024
*/
-#include "number.hpp"
+#include "steppable/number.hpp"
#include "fn/calc.hpp"
#include "output.hpp"
+#include "rounding.hpp"
+#include "util.hpp"
+
+#include
+#include
#ifdef WINDOWS
#undef max
@@ -42,42 +47,48 @@ namespace steppable
{
using namespace steppable::__internals::calc;
- Number::Number() : prec(8), value("0") {}
+ Number::Number() : value("0"), prec(8) {}
+
+ Number::Number(const Number& rhs) : value(rhs.value), prec(rhs.prec) {}
- Number::Number(std::string value, size_t prec, RoundingMode mode) : value(std::move(value)), prec(prec), mode(mode)
+ Number::Number(std::string value, const size_t prec, const RoundingMode mode) :
+ value(std::move(value)), prec(prec), mode(mode)
{
}
- Number Number::operator+(const Number& rhs) const { return add(value, rhs.value, 0); }
+ Number Number::operator+(const Number& rhs) const { return Number(add(value, rhs.value, 0), prec, mode); }
- Number Number::operator-(const Number& rhs) const { return subtract(value, rhs.value, 0); }
+ Number Number::operator-(const Number& rhs) const { return Number(subtract(value, rhs.value, 0), prec, mode); }
- Number Number::operator*(const Number& rhs) const { return multiply(value, rhs.value, 0, static_cast(prec)); }
+ Number Number::operator*(const Number& rhs) const
+ {
+ const size_t usePrec = determinePrec<"operator*">(rhs);
+ const auto result = multiply(value, rhs.value, 0, static_cast(usePrec) + 2);
+ return Number{ __internals::numUtils::roundOff(result, usePrec), usePrec, mode };
+ }
Number Number::operator/(const Number& rhs) const
{
- size_t usePrec = 0;
- if (mode == USE_MAXIMUM_PREC)
- usePrec = std::max(prec, rhs.prec);
- else if (mode == USE_MINIMUM_PREC)
- usePrec = std::min(prec, rhs.prec);
- else if (mode == USE_CURRENT_PREC)
- usePrec = prec;
- else if (mode == USE_OTHER_PREC)
- usePrec = rhs.prec;
- else if (mode == DISCARD_ALL_DECIMALS)
- usePrec = 0;
- else
- {
- usePrec = 0;
- output::warning("Number::operator/"s, "Invalid precision specified"s);
- }
- return divide(value, rhs.value, 0, static_cast(usePrec));
+ const size_t usePrec = determinePrec<"operator/">(rhs);
+ const auto result = divide(value, rhs.value, 0, static_cast(usePrec) + 2);
+ return Number{ __internals::numUtils::roundOff(result, usePrec), usePrec, mode };
}
- Number Number::operator%(const Number& rhs) const { return divideWithQuotient(value, rhs.value).remainder; }
+ Number Number::operator%(const Number& rhs) const
+ {
+ return Number(divideWithQuotient(value, rhs.value).remainder, prec, mode);
+ }
- Number Number::operator^(const Number& rhs) const { return power(value, rhs.value, 0, static_cast(prec)); }
+ Number Number::mod(const Number& rhs) const
+ {
+ return Number(divideWithQuotient(value, rhs.value).quotient, prec, mode);
+ }
+
+ Number Number::operator^(const Number& rhs)
+ {
+ const size_t usePrec = determinePrec<"operator^">(rhs);
+ return Number(power(value, rhs.value, 0, static_cast(usePrec)), usePrec, mode);
+ }
Number& Number::operator+=(const Number& rhs)
{
@@ -87,18 +98,21 @@ namespace steppable
Number& Number::operator-=(const Number& rhs)
{
+ prec = determinePrec<"operator-=">(rhs);
*this = *this - rhs;
return *this;
}
Number& Number::operator*=(const Number& rhs)
{
+ prec = determinePrec<"operator*=">(rhs);
*this = *this * rhs;
return *this;
}
Number& Number::operator/=(const Number& rhs)
{
+ prec = determinePrec<"operator/=">(rhs);
*this = *this / rhs;
return *this;
}
@@ -127,6 +141,16 @@ namespace steppable
bool Number::operator>=(const Number& rhs) const { return compare(value, rhs.value, 0) != "0"; }
+ Number Number::operator-() const
+ {
+ auto newValue = "-" + value;
+ newValue = __internals::numUtils::standardizeNumber(newValue);
+ Number number(newValue, prec, mode);
+ return number;
+ }
+
+ Number Number::operator+() const { return *this; }
+
Number Number::operator++()
{
*this += Number("1");
diff --git a/src/testing.cpp b/src/testing.cpp
index 4ae92c3a1..2f435fbc3 100644
--- a/src/testing.cpp
+++ b/src/testing.cpp
@@ -35,7 +35,7 @@ namespace steppable::testing
{
TestCase::TestCase(std::string testCaseName) : testCaseName(std::move(testCaseName)) {}
- void TestCase::assert(const bool condition, const std::string& conditionName)
+ void TestCase::_assertCondition(const bool condition, const std::string& conditionName)
{
if (condition)
{
@@ -51,39 +51,25 @@ namespace steppable::testing
void TestCase::assertIsEqual(const std::string& a, const std::string& b)
{
const std::string& conditionName = format::format("String {0} == {1}", { a, b });
- assert(a == b, conditionName);
+ _assertCondition(a == b, conditionName);
}
void TestCase::assertIsNotEqual(const std::string& a, const std::string& b)
{
const std::string& conditionName = format::format("String {0} != {1}", { a, b });
- assert(a != b, conditionName);
- }
-
- void TestCase::assertIsEqual(const int a, const int b)
- {
- const std::string& conditionName =
- format::format("Integer {0} == {1}", { std::to_string(a), std::to_string(b) });
- assert(a == b, conditionName);
- }
-
- void TestCase::assertIsNotEqual(const int a, const int b)
- {
- const std::string& conditionName =
- format::format("Integer {0} != {1}", { std::to_string(a), std::to_string(b) });
- assert(a != b, conditionName);
+ _assertCondition(a != b, conditionName);
}
void TestCase::assertTrue(const bool value)
{
const std::string& conditionName = format::format("{0} is True", { std::to_string(static_cast(value)) });
- assert(value, conditionName);
+ _assertCondition(value, conditionName);
}
void TestCase::assertFalse(const bool value)
{
const std::string& conditionName = format::format("{0} is False", { std::to_string(static_cast(value)) });
- assert(not value, conditionName);
+ _assertCondition(not value, conditionName);
}
void TestCase::summarize() const
diff --git a/src/util.cpp b/src/util.cpp
index 1177a1fdf..c874ce77a 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -68,15 +68,21 @@ namespace steppable::__internals::numUtils
std::string simplifyZeroPolarity(const std::string& string)
{
+ if (string.empty())
+ return "0";
// Check if the string is zero
if (isZeroString(string))
return "0";
- return static_cast(string);
+ return string;
}
std::string simplifyPolarity(const std::string& _string)
{
- auto string = simplifyZeroPolarity(_string);
+ if (_string.empty())
+ return "0";
+
+ auto string = _string;
+ string = simplifyZeroPolarity(string);
while (string[0] == '-' and string[1] == '-')
string = string.substr(2);
return string;
@@ -84,6 +90,9 @@ namespace steppable::__internals::numUtils
std::string standardizeNumber(const std::string& _number)
{
+ if (_number.empty())
+ return "0";
+
auto number = simplifyPolarity(_number);
// Remove the trailing decimal point
if (number.back() == '.')
@@ -91,6 +100,8 @@ namespace steppable::__internals::numUtils
if (number.empty())
return "0";
+ if (number == "-")
+ return "-0";
if (number.front() == '+')
number.erase(0, 1);
if (number.front() == '.')
@@ -112,23 +123,17 @@ namespace steppable::__internals::numUtils
auto a = static_cast(_a);
auto b = static_cast(_b);
- if (properlyFormat)
+ if (a.front() == '-')
{
- a = simplifyPolarity(_a), b = simplifyPolarity(_b);
- if (a.front() == '-')
- {
- aIsNegative = true;
- if (not preserveNegative)
- a.erase(a.begin());
- }
- if (b.front() == '-')
- {
- bIsNegative = true;
- if (not preserveNegative)
- b.erase(b.begin());
- }
- a = removeLeadingZeros(a);
- b = removeLeadingZeros(b);
+ aIsNegative = true;
+ if (not preserveNegative)
+ a.erase(a.begin());
+ }
+ if (b.front() == '-')
+ {
+ bIsNegative = true;
+ if (not preserveNegative)
+ b.erase(b.begin());
}
const std::vector aParts = stringUtils::split(a, '.');
const std::vector bParts = stringUtils::split(b, '.');
@@ -276,7 +281,11 @@ namespace steppable::__internals::numUtils
if (number.front() == '-')
number = number.substr(1, number.length() - 1); // Remove negative sign as it does nothing here.
if (isDecimal(number))
- return not std::ranges::any_of(number, [](const auto& c) { return c != '0' and c != '.' and c != '1'; });
+ {
+ return not std::ranges::any_of(number.substr(0, number.length() - 1), [](const auto& c) {
+ return c != '0' and c != '.';
+ }) and number.back() == '1';
+ }
if (number == "1")
return true; // 1 is a power of 10.
if (number.front() != '1')
diff --git a/steppyble/__init__.pyi b/steppyble/__init__.pyi
index a62e1732d..7877de5eb 100644
--- a/steppyble/__init__.pyi
+++ b/steppyble/__init__.pyi
@@ -20,6 +20,18 @@
# SOFTWARE. #
#####################################################################################################
-from .number import *
-from .fraction import *
-from .rounding_mode import *
+"""
+The Steppable library Python bindings
+=====================================
+This library provides the Python bindings to C++ functions of Steppable.
+
+Steppable
+---------
+This project aims to make a Computer Algebra System (CAS) from scratch, and without any external libraries.
+See https://github.com/ZCG-coder/Steppable for the project.
+"""
+
+from ._fraction import *
+from ._matrix import *
+from ._number import *
+from ._rounding_mode import *
diff --git a/steppyble/_fraction.pyi b/steppyble/_fraction.pyi
new file mode 100644
index 000000000..291988337
--- /dev/null
+++ b/steppyble/_fraction.pyi
@@ -0,0 +1,326 @@
+#####################################################################################################
+# Copyright (c) 2023-2025 NWSOFT #
+# #
+# 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. #
+#####################################################################################################
+
+import steppyble
+
+class Fraction:
+ def __init__(self, top: str = "1", bottom: str = "1") -> None:
+ """
+ Initializes a Fraction.
+
+ Parameters
+ ----------
+ top : str, optional
+ Numerator of the fraction (default is "1").
+ bottom : str, optional
+ Denominator of the fraction (default is "1").
+ """
+ ...
+
+ def __repr__(self) -> str:
+ """
+ Returns the string representation of the fraction.
+
+ Returns
+ -------
+ str
+ String representation of the fraction.
+ """
+ ...
+
+ def __add__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Adds another fraction to this fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to add.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The sum of the two fractions.
+ """
+ ...
+
+ def __iadd__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Adds another fraction to this fraction in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to add.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The sum of the two fractions (in-place).
+ """
+ ...
+
+ def __sub__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Subtracts another fraction from this fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to subtract.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The difference of the two fractions.
+ """
+ ...
+
+ def __isub__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Subtracts another fraction from this fraction in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to subtract.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The difference of the two fractions (in-place).
+ """
+ ...
+
+ def __neg__(self, /) -> steppyble.Fraction:
+ """
+ Returns the negation of the fraction.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The negated fraction.
+ """
+ ...
+
+ def __pos__(self, /) -> steppyble.Fraction:
+ """
+ Returns a copy of the fraction (unary plus).
+
+ Returns
+ -------
+ steppyble.Fraction
+ A copy of the fraction.
+ """
+ ...
+
+ def __mul__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Multiplies this fraction by another fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to multiply with.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The product of the two fractions.
+ """
+ ...
+
+ def __imul__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Multiplies this fraction by another fraction in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to multiply with.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The product of the two fractions (in-place).
+ """
+ ...
+
+ def __truediv__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Divides this fraction by another fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to divide by.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The quotient of the two fractions.
+ """
+ ...
+
+ def __itruediv__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Divides this fraction by another fraction in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to divide by.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The quotient of the two fractions (in-place).
+ """
+ ...
+
+ def __pow__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Raises this fraction to the power of another fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The exponent.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The result of exponentiation.
+ """
+ ...
+
+ def __ipow__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction:
+ """
+ Raises this fraction to the power of another fraction in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The exponent.
+
+ Returns
+ -------
+ steppyble.Fraction
+ The result of exponentiation (in-place).
+ """
+ ...
+
+ def __eq__(self, _: object, /) -> bool:
+ """
+ Checks if this fraction is equal to another object.
+
+ Parameters
+ ----------
+ _ : object
+ The object to compare with.
+
+ Returns
+ -------
+ bool
+ True if equal, False otherwise.
+ """
+ ...
+
+ def __ne__(self, _: object, /) -> bool:
+ """
+ Checks if this fraction is not equal to another object.
+
+ Parameters
+ ----------
+ _ : object
+ The object to compare with.
+
+ Returns
+ -------
+ bool
+ True if not equal, False otherwise.
+ """
+ ...
+
+ def __ge__(self, rhs: steppyble.Fraction, /) -> bool:
+ """
+ Checks if this fraction is greater than or equal to another fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to compare with.
+
+ Returns
+ -------
+ bool
+ True if greater than or equal, False otherwise.
+ """
+ ...
+
+ def __gt__(self, rhs: steppyble.Fraction, /) -> bool:
+ """
+ Checks if this fraction is greater than another fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to compare with.
+
+ Returns
+ -------
+ bool
+ True if greater, False otherwise.
+ """
+ ...
+
+ def __le__(self, rhs: steppyble.Fraction, /) -> bool:
+ """
+ Checks if this fraction is less than or equal to another fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to compare with.
+
+ Returns
+ -------
+ bool
+ True if less than or equal, False otherwise.
+ """
+ ...
+
+ def __lt__(self, rhs: steppyble.Fraction, /) -> bool:
+ """
+ Checks if this fraction is less than another fraction.
+
+ Parameters
+ ----------
+ rhs : steppyble.Fraction
+ The fraction to compare with.
+
+ Returns
+ -------
+ bool
+ True if less, False otherwise.
+ """
+ ...
diff --git a/steppyble/_gui/__init__.py b/steppyble/_gui/__init__.py
new file mode 100644
index 000000000..ac262d814
--- /dev/null
+++ b/steppyble/_gui/__init__.py
@@ -0,0 +1,109 @@
+#####################################################################################################
+# Copyright (c) 2023-2025 NWSOFT #
+# #
+# 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. #
+#####################################################################################################
+
+import platform
+import tkinter as tk
+from tkinter import ttk
+
+
+class MenuButton(ttk.Label):
+ def __init__(self, master, name: str):
+ super().__init__(master, takefocus=True, padding=(5, 0))
+ self["text"] = name
+ self.bind("", self._open_popup)
+ self._menu = tk.Menu(self)
+ self._menu.bind("", lambda _: self._menu.grab_release())
+
+ def _open_popup(self, _):
+ try:
+ self._menu.tk_popup(
+ self.winfo_rootx(), self.winfo_rooty() + self.winfo_height()
+ )
+ finally:
+ self._menu.grab_release()
+
+ def add_command(self, *args, **kwargs):
+ self._menu.add_command(*args, **kwargs)
+
+ def add_checkbutton(self, *args, **kwargs):
+ self._menu.add_checkbutton(*args, **kwargs)
+
+ def add_cascade(self, *args, **kwargs):
+ self._menu.add_cascade(*args, **kwargs)
+
+ def add_radiobutton(self, *args, **kwargs):
+ self._menu.add_radiobutton(*args, **kwargs)
+
+ def add_separator(self, *args, **kwargs):
+ self._menu.add_separator(*args, **kwargs)
+
+ def pack(self, *args, **kwargs):
+ kwargs["side"] = tk.LEFT
+ kwargs["ipadx"] = 10
+ kwargs["ipady"] = 2
+ super().pack(*args, **kwargs)
+
+
+class Menu(ttk.Frame):
+ def __init__(self, master):
+ super().__init__(master)
+
+
+class _MainWindow(tk.Tk):
+ def __init__(
+ self,
+ screenName: str | None = None,
+ baseName: str | None = None,
+ className: str = "Tk",
+ useTk: bool = True,
+ sync: bool = False,
+ use: str | None = None,
+ ) -> None:
+ super().__init__(screenName, baseName, className, useTk, sync, use)
+ self.title("Steppable")
+
+ # Fix the title on macOS
+ if platform.system() == "Darwin":
+ _menu = tk.Menu()
+ _python_menu = tk.Menu(_menu, name="apple")
+ _menu.add_cascade(menu=_python_menu)
+ self["menu"] = _menu
+ _python_menu.destroy()
+
+ menu_frame = Menu(self)
+ menu = MenuButton(menu_frame, name="FILE")
+ # Add options to the menu
+ menu.add_command(label="Option 1")
+ menu.add_command(label="Option 2")
+ menu.pack()
+ menu = MenuButton(menu_frame, name="FILE")
+ # Add options to the menu
+ menu.add_command(label="Option 1")
+ menu.add_command(label="Option 2")
+ menu.pack()
+
+ menu_frame.pack(anchor=tk.W)
+
+
+if __name__ == "__main__":
+ win = _MainWindow()
+ win.mainloop()
diff --git a/steppyble/_matrix.pyi b/steppyble/_matrix.pyi
new file mode 100644
index 000000000..ee9f676a9
--- /dev/null
+++ b/steppyble/_matrix.pyi
@@ -0,0 +1,571 @@
+#####################################################################################################
+# Copyright (c) 2023-2025 NWSOFT #
+# #
+# 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. #
+#####################################################################################################
+
+import typing
+
+import steppyble
+import steppyble._types
+
+class Matrix:
+ """
+ A Steppable matrix supporting arithmetic and matrix operations.
+
+ Parameters
+ ----------
+ values : Sequence[Sequence[steppyble.Number]] or Sequence[Sequence[float]]
+ The values of the matrix.
+ prec : int, optional
+ Precision of the values to be copied into the matrix (default is 5).
+
+ Notes
+ -----
+ This class mimics Python's number behavior for matrices, supporting addition, subtraction, multiplication, and more.
+ """
+
+ @typing.overload
+ def __init__(
+ self,
+ values: typing.Sequence[typing.Sequence[steppyble.Number]],
+ prec: int = 5,
+ ) -> None:
+ """
+ Constructs a matrix with specified dimensions and an optional fill value.
+
+ Parameters
+ ----------
+ values
+ The values of the matrix.
+ prec
+ Precision of the values to be copied into the matrix.
+ """
+
+ ...
+
+ @typing.overload
+ def __init__(
+ self,
+ values: typing.Sequence[typing.Sequence[float]],
+ prec: int = 5,
+ ) -> None:
+ """
+ Constructs a matrix with specified dimensions and an optional fill value.
+
+ Parameters
+ ----------
+ values
+ The values of the matrix.
+ prec
+ Precision of the values to be copied into the matrix.
+ """
+ ...
+
+ def __repr__(self) -> str:
+ """
+ Represents the values in the matrix in text form.
+
+ Returns
+ -------
+ str
+ A string representing the matrix.
+ """
+
+ ...
+
+ def __lshift__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Joins a matrix to the right of the current matrix.
+
+ Parameters
+ ----------
+ matrix
+ The matrix to join.
+
+ Return
+ ------
+ steppyble.Matrix
+ The two matrices joined together.
+ """
+
+ ...
+
+ def __ilshift__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Joins a matrix to the right of the current matrix, assigning the result to the current one.
+
+ Parameters
+ ----------
+ matrix
+ The matrix to join.
+
+ Return
+ ------
+ steppyble.Matrix
+ The two matrices joined together.
+ """
+
+ ...
+
+ def __rshift__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Joins a matrix to the left of the current matrix.
+
+ Parameters
+ ----------
+ matrix
+ The matrix to join.
+
+ Return
+ ------
+ steppyble.Matrix
+ The two matrices joined together.
+ """
+
+ ...
+
+ def __irshift__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Joins a matrix to the left of the current matrix, assigning the result to the current one.
+
+ Parameters
+ ----------
+ matrix
+ The matrix to join.
+
+ Return
+ ------
+ steppyble.Matrix
+ The two matrices joined together.
+ """
+ ...
+
+ def __add__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Adds another matrix to the current one.
+
+ Parameters
+ ----------
+ matrix
+ The other matrix.
+
+ Returns
+ -------
+ steppyble.Matrix
+ A new matrix where all elements are added together correspondingly.
+ """
+ ...
+
+ def __iadd__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Adds another matrix to the current one, assigning the result to the current one.
+
+ Parameters
+ ----------
+ matrix
+ The other matrix.
+
+ Returns
+ -------
+ steppyble.Matrix
+ A new matrix where all elements are added together correspondingly.
+ """
+ ...
+
+ def __sub__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Subtracts another matrix from the current one.
+
+ Parameters
+ ----------
+ matrix : steppyble.Matrix
+ The other matrix.
+
+ Returns
+ -------
+ steppyble.Matrix
+ A new matrix where all elements are subtracted correspondingly.
+ """
+ ...
+
+ def __neg__(self, /) -> steppyble.Matrix:
+ """
+ Returns the negation of the matrix (element-wise negation).
+
+ Returns
+ -------
+ steppyble.Matrix
+ A new matrix with all elements negated.
+ """
+ ...
+
+ def __pos__(self, /) -> steppyble.Matrix:
+ """
+ Returns a copy of the matrix (unary plus).
+
+ Returns
+ -------
+ steppyble.Matrix
+ A copy of the matrix.
+ """
+ ...
+
+ def __isub__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Subtracts another matrix from the current one, assigning the result to the current one.
+
+ Parameters
+ ----------
+ matrix : steppyble.Matrix
+ The other matrix.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The updated matrix after subtraction.
+ """
+ ...
+
+ @typing.overload
+ def __mul__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Multiplies the current matrix by another matrix (matrix multiplication).
+
+ Parameters
+ ----------
+ matrix : steppyble.Matrix
+ The matrix to multiply with.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The matrix product.
+ """
+ ...
+
+ @typing.overload
+ def __mul__(self, _: steppyble.Number, /) -> steppyble.Matrix:
+ """
+ Multiplies the current matrix by a scalar.
+
+ Parameters
+ ----------
+ _ : steppyble.Number
+ The scalar to multiply with.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The scaled matrix.
+ """
+ ...
+
+ @typing.overload
+ def __imul__(self, _: steppyble.Number, /) -> steppyble.Matrix:
+ """
+ Multiplies the current matrix by a scalar in-place.
+
+ Parameters
+ ----------
+ _ : steppyble.Number
+ The scalar to multiply with.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The scaled matrix (in-place).
+ """
+ ...
+
+ @typing.overload
+ def __imul__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix:
+ """
+ Multiplies the current matrix by another matrix in-place (matrix multiplication).
+
+ Parameters
+ ----------
+ matrix : steppyble.Matrix
+ The matrix to multiply with.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The matrix product (in-place).
+ """
+ ...
+
+ def __pow__(self, _: steppyble.Number, /) -> steppyble.Matrix:
+ """
+ Raises the matrix to a given power.
+
+ Parameters
+ ----------
+ _ : steppyble.Number
+ The exponent.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The matrix raised to the given power.
+ """
+ ...
+
+ def __ipow__(self, _: steppyble.Number, /) -> steppyble.Matrix:
+ """
+ Raises the matrix to a given power in-place.
+
+ Parameters
+ ----------
+ _ : steppyble.Number
+ The exponent.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The matrix raised to the given power (in-place).
+ """
+ ...
+
+ def __eq__(self, _: object, /) -> bool:
+ """
+ Checks if two matrices are equal.
+
+ Parameters
+ ----------
+ _ : object
+ The object to compare with.
+
+ Returns
+ -------
+ bool
+ True if the matrices are equal, False otherwise.
+ """
+ ...
+
+ def __ne__(self, _: object, /) -> bool:
+ """
+ Checks if two matrices are not equal.
+
+ Parameters
+ ----------
+ _ : object
+ The object to compare with.
+
+ Returns
+ -------
+ bool
+ True if the matrices are not equal, False otherwise.
+ """
+ ...
+
+ @staticmethod
+ def zeros(rows: int, cols: int) -> steppyble.Matrix:
+ """
+ Returns a matrix filled with zeros.
+
+ Parameters
+ ----------
+ rows : int
+ Number of rows.
+ cols : int
+ Number of columns.
+
+ Returns
+ -------
+ steppyble.Matrix
+ A matrix of shape (rows, cols) filled with zeros.
+ """
+ ...
+
+ @typing.overload
+ @staticmethod
+ def diag(
+ rows_cols: int, fill: steppyble.Number = steppyble.Number("1")
+ ) -> steppyble.Matrix:
+ """
+ Returns a diagonal matrix with a specified fill value (steppyble.Number).
+
+ Parameters
+ ----------
+ rows_cols : int
+ Number of rows and columns (matrix is square).
+ fill : steppyble.Number, optional
+ Value to fill the diagonal (default is steppyble.Number("1")).
+
+ Returns
+ -------
+ steppyble.Matrix
+ A diagonal matrix.
+ """
+ ...
+
+ @typing.overload
+ @staticmethod
+ def diag(rows_cols: int, fill: float = 1) -> steppyble.Matrix:
+ """
+ Returns a diagonal matrix with a specified fill value (float).
+
+ Parameters
+ ----------
+ rows_cols : int
+ Number of rows and columns (matrix is square).
+ fill : float, optional
+ Value to fill the diagonal (default is 1).
+
+ Returns
+ -------
+ steppyble.Matrix
+ A diagonal matrix.
+ """
+ ...
+
+ @staticmethod
+ def ones(rows: int, cols: int) -> steppyble.Matrix:
+ """
+ Returns a matrix filled with ones.
+
+ Parameters
+ ----------
+ rows : int
+ Number of rows.
+ cols : int
+ Number of columns.
+
+ Returns
+ -------
+ steppyble.Matrix
+ A matrix of shape (rows, cols) filled with ones.
+ """
+ ...
+
+ def rref(self) -> steppyble.Matrix:
+ """
+ Returns the reduced row echelon form (RREF) of the matrix.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The RREF of the matrix.
+ """
+ ...
+
+ def ref(self) -> steppyble.Matrix:
+ """
+ Returns the row echelon form (REF) of the matrix.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The REF of the matrix.
+ """
+ ...
+
+ def rank(self) -> steppyble.Number:
+ """
+ Returns the rank of the matrix.
+
+ Returns
+ -------
+ steppyble.Number
+ The rank of the matrix.
+ """
+ ...
+
+ def det(self) -> steppyble.Matrix:
+ """
+ Returns the determinant of the matrix.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The determinant of the matrix.
+ """
+ ...
+
+ def transpose(self) -> steppyble.Matrix:
+ """
+ Returns the transpose of the matrix.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The transposed matrix.
+ """
+ ...
+
+ def __getitem__(self, indices: typing.Tuple[int]) -> steppyble.Matrix:
+ """
+ Returns a submatrix or element at the specified indices.
+
+ Parameters
+ ----------
+ indices : Tuple[int]
+ Indices to access.
+
+ Returns
+ -------
+ steppyble.Matrix
+ The submatrix or element at the given indices.
+ """
+ ...
+
+ def rows(self) -> int:
+ """
+ Returns the number of rows of the matrix.
+
+ Returns
+ -------
+ int
+ The number of rows.
+ """
+ ...
+
+ def cols(self) -> int:
+ """
+ Returns the number of columns of the matrix.
+
+ Returns
+ -------
+ int
+ The number of columns.
+ """
+ ...
+
+ def dims(self) -> typing.Tuple[int, int]:
+ """
+ Returns the dimensions of the matrix.
+
+ Returns
+ -------
+ typing.Tuple[int, int]
+ The dimensions of the matrix as a tuple (rows, cols).
+ """
+ ...
+
+ def __iter__(self) -> steppyble._types.MatrixIterator:
+ """
+ Returns an iterator over the matrix rows.
+
+ Returns
+ -------
+ steppyble.Matrix
+ An iterator over the matrix rows.
+ """
+ ...
diff --git a/steppyble/_number.pyi b/steppyble/_number.pyi
new file mode 100644
index 000000000..0c7fa4694
--- /dev/null
+++ b/steppyble/_number.pyi
@@ -0,0 +1,335 @@
+#####################################################################################################
+# Copyright (c) 2023-2025 NWSOFT #
+# #
+# 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. #
+#####################################################################################################
+
+import steppyble
+
+class Number:
+ """A Steppable number, which performs exactly like a Python number, allowing add, subtract, multiply, etc."""
+
+ def __init__(
+ self,
+ value: str = "0",
+ prec: int = 5,
+ roundingMode: steppyble.RoundingMode = steppyble.RoundingMode.USE_CURRENT_PREC,
+ ) -> None:
+ """
+ Initializes a Steppable number.
+
+ Parameters
+ ----------
+ value : str, optional
+ The value to initialize the number with (default is "0").
+ prec : int, optional
+ Precision of the number (default is 5).
+ roundingMode : steppyble.RoundingMode, optional
+ Rounding mode to use (default is steppyble.RoundingMode.USE_CURRENT_PREC).
+ """
+ ...
+
+ def __repr__(self) -> str:
+ """
+ Returns the string representation of the number.
+
+ Returns
+ -------
+ str
+ String representation of the number.
+ """
+ ...
+
+ def __add__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Adds another number to this number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to add.
+
+ Returns
+ -------
+ steppyble.Number
+ The sum of the two numbers.
+ """
+ ...
+
+ def __iadd__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Adds another number to this number in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to add.
+
+ Returns
+ -------
+ steppyble.Number
+ The sum of the two numbers (in-place).
+ """
+ ...
+
+ def __sub__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Subtracts another number from this number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to subtract.
+
+ Returns
+ -------
+ steppyble.Number
+ The difference of the two numbers.
+ """
+ ...
+
+ def __neg__(self, /) -> steppyble.Number:
+ """
+ Returns the negation of the number.
+
+ Returns
+ -------
+ steppyble.Number
+ The negated number.
+ """
+ ...
+
+ def __pos__(self, /) -> steppyble.Number:
+ """
+ Returns a copy of the number (unary plus).
+
+ Returns
+ -------
+ steppyble.Number
+ A copy of the number.
+ """
+ ...
+
+ def __isub__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Subtracts another number from this number in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to subtract.
+
+ Returns
+ -------
+ steppyble.Number
+ The difference of the two numbers (in-place).
+ """
+ ...
+
+ def __mul__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Multiplies this number by another number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to multiply with.
+
+ Returns
+ -------
+ steppyble.Number
+ The product of the two numbers.
+ """
+ ...
+
+ def __imul__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Multiplies this number by another number in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to multiply with.
+
+ Returns
+ -------
+ steppyble.Number
+ The product of the two numbers (in-place).
+ """
+ ...
+
+ def __truediv__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Divides this number by another number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to divide by.
+
+ Returns
+ -------
+ steppyble.Number
+ The quotient of the two numbers.
+ """
+ ...
+
+ def __itruediv__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Divides this number by another number in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to divide by.
+
+ Returns
+ -------
+ steppyble.Number
+ The quotient of the two numbers (in-place).
+ """
+ ...
+
+ def __pow__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Raises this number to the power of another number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The exponent.
+
+ Returns
+ -------
+ steppyble.Number
+ The result of exponentiation.
+ """
+ ...
+
+ def __ipow__(self, rhs: steppyble.Number, /) -> steppyble.Number:
+ """
+ Raises this number to the power of another number in-place.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The exponent.
+
+ Returns
+ -------
+ steppyble.Number
+ The result of exponentiation (in-place).
+ """
+ ...
+
+ def __eq__(self, _: object, /) -> bool:
+ """
+ Checks if this number is equal to another object.
+
+ Parameters
+ ----------
+ _ : object
+ The object to compare with.
+
+ Returns
+ -------
+ bool
+ True if equal, False otherwise.
+ """
+ ...
+
+ def __ne__(self, _: object, /) -> bool:
+ """
+ Checks if this number is not equal to another object.
+
+ Parameters
+ ----------
+ _ : object
+ The object to compare with.
+
+ Returns
+ -------
+ bool
+ True if not equal, False otherwise.
+ """
+ ...
+
+ def __ge__(self, rhs: steppyble.Number, /) -> bool:
+ """
+ Checks if this number is greater than or equal to another number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to compare with.
+
+ Returns
+ -------
+ bool
+ True if greater than or equal, False otherwise.
+ """
+ ...
+
+ def __gt__(self, rhs: steppyble.Number, /) -> bool:
+ """
+ Checks if this number is greater than another number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to compare with.
+
+ Returns
+ -------
+ bool
+ True if greater, False otherwise.
+ """
+ ...
+
+ def __le__(self, rhs: steppyble.Number, /) -> bool:
+ """
+ Checks if this number is less than or equal to another number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to compare with.
+
+ Returns
+ -------
+ bool
+ True if less than or equal, False otherwise.
+ """
+ ...
+
+ def __lt__(self, rhs: steppyble.Number, /) -> bool:
+ """
+ Checks if this number is less than another number.
+
+ Parameters
+ ----------
+ rhs : steppyble.Number
+ The number to compare with.
+
+ Returns
+ -------
+ bool
+ True if less, False otherwise.
+ """
+ ...
diff --git a/steppyble/rounding_mode.pyi b/steppyble/_rounding_mode.pyi
similarity index 99%
rename from steppyble/rounding_mode.pyi
rename to steppyble/_rounding_mode.pyi
index 238532f34..33c7155c6 100644
--- a/steppyble/rounding_mode.pyi
+++ b/steppyble/_rounding_mode.pyi
@@ -22,7 +22,6 @@
import enum
-
class RoundingMode(enum.Enum):
"""Specifies how Steppable should round the number in operations."""
diff --git a/steppyble/fraction.pyi b/steppyble/_types.py
similarity index 61%
rename from steppyble/fraction.pyi
rename to steppyble/_types.py
index 489a420df..895dd2164 100644
--- a/steppyble/fraction.pyi
+++ b/steppyble/_types.py
@@ -20,60 +20,8 @@
# SOFTWARE. #
#####################################################################################################
-import steppyble
-
-
-class Fraction:
- def __init__(self, top: str = "1", bottom: str = "1") -> None:
- ...
-
- def __repr__(self) -> str:
- ...
-
- def __add__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __iadd__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __sub__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __isub__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __mul__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __imul__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
+import typing
- def __truediv__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __itruediv__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __pow__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __ipow__(self, _: steppyble.Fraction, /) -> steppyble.Fraction:
- ...
-
- def __eq__(self, _: object, /) -> bool:
- ...
-
- def __ne__(self, _: object, /) -> bool:
- ...
-
- def __ge__(self, _: steppyble.Fraction, /) -> bool:
- ...
-
- def __gt__(self, _: steppyble.Fraction, /) -> bool:
- ...
-
- def __le__(self, _: steppyble.Fraction, /) -> bool:
- ...
+import steppyble
- def __lt__(self, _: steppyble.Fraction, /) -> bool:
- ...
+MatrixIterator: typing.TypeAlias = typing.Iterator[typing.List[steppyble.Number]]
diff --git a/steppyble/number.pyi b/steppyble/number.pyi
deleted file mode 100644
index 3e2c8b5e9..000000000
--- a/steppyble/number.pyi
+++ /dev/null
@@ -1,86 +0,0 @@
-#####################################################################################################
-# Copyright (c) 2023-2025 NWSOFT #
-# #
-# 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. #
-#####################################################################################################
-
-import steppyble
-
-
-class Number:
- """A Steppable number, which performs exactly like a Python number, allowing add, subtract, multiply..."""
-
- def __init__(
- self,
- value: str = "0",
- prec: int = 5,
- roundingMode: steppyble.RoundingMode = steppyble.RoundingMode.USE_CURRENT_PREC,
- ) -> None:
- ...
-
- def __repr__(self) -> str:
- ...
-
- def __add__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __iadd__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __sub__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __isub__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __mul__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __imul__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __truediv__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __itruediv__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __pow__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __ipow__(self, _: steppyble.Number, /) -> steppyble.Number:
- ...
-
- def __eq__(self, _: object, /) -> bool:
- ...
-
- def __ne__(self, _: object, /) -> bool:
- ...
-
- def __ge__(self, _: steppyble.Number, /) -> bool:
- ...
-
- def __gt__(self, _: steppyble.Number, /) -> bool:
- ...
-
- def __le__(self, _: steppyble.Number, /) -> bool:
- ...
-
- def __lt__(self, _: steppyble.Number, /) -> bool:
- ...
diff --git a/tests/testFraction.cpp b/tests/testFraction.cpp
index 7ebbffa7e..d1799e275 100644
--- a/tests/testFraction.cpp
+++ b/tests/testFraction.cpp
@@ -21,8 +21,8 @@
**************************************************************************************************/
#include "colors.hpp"
-#include "fraction.hpp"
#include "output.hpp"
+#include "steppable/fraction.hpp"
#include "testing.hpp"
#include "util.hpp"
@@ -65,4 +65,14 @@ fraction.reciprocal();
_.assertIsEqual(fraction.present(), "4/1");
SECTION_END()
+SECTION(Unary)
+auto fraction = Fraction("1", "4");
+fraction = -fraction;
+_.assertIsEqual(fraction.present(), "-1/4");
+
+fraction = Fraction("-1", "4");
+fraction = -fraction;
+_.assertIsEqual(fraction.present(), "1/4");
+SECTION_END()
+
TEST_END()
diff --git a/tests/testHyp.cpp b/tests/testHyp.cpp
index f57efded2..be0ff75e9 100644
--- a/tests/testHyp.cpp
+++ b/tests/testHyp.cpp
@@ -34,7 +34,7 @@ TEST_START()
using namespace steppable::__internals::calc;
SECTION(Test hyperbolic sine)
-_.assertIsEqual(sinh("10", 3), "11013.233");
+_.assertIsEqual(sinh("10", 4), "11013.2329");
SECTION_END()
SECTION(Test hyperbolic cosine)
diff --git a/tests/testMat2d.cpp b/tests/testMat2d.cpp
new file mode 100644
index 000000000..044834333
--- /dev/null
+++ b/tests/testMat2d.cpp
@@ -0,0 +1,153 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+#include "steppable/mat2d.hpp"
+#include "steppable/number.hpp"
+#include "testing.hpp"
+#include "util.hpp"
+
+#include
+#include
+
+TEST_START()
+SECTION(Matrix multiplication)
+steppable::Matrix mat1({
+ { 1, 0, 1 },
+ { 2, 1, 1 },
+ { 0, 1, 1 },
+ { 1, 1, 2 },
+});
+steppable::Matrix mat2({
+ { 1, 2, 1 },
+ { 2, 3, 1 },
+ { 4, 2, 2 },
+});
+auto mat3 = mat1 * mat2;
+
+_.assertIsEqual(mat3,
+ steppable::Matrix({
+ { 5, 4, 3 },
+ { 8, 9, 5 },
+ { 6, 5, 3 },
+ { 11, 9, 6 },
+ }));
+SECTION_END()
+
+SECTION(Transpose)
+steppable::Matrix mat2({
+ { 1, 2, 1 },
+ { 2, 3, 1 },
+ { 4, 2, 2 },
+});
+// Two transposes results in the same matrix
+// T
+// ( T )
+// ( A ) = A
+_.assertIsEqual(mat2.transpose().transpose(), mat2);
+_.assertIsNotEqual(mat2.transpose(), mat2);
+SECTION_END()
+
+SECTION(Determinant)
+steppable::Matrix mat2({
+ { 6, 90 },
+ { 4892, 892 },
+});
+_.assertIsEqual(mat2.det(), steppable::Number(-434928));
+SECTION_END()
+
+SECTION(Matrix slicing)
+steppable::Matrix matrix({
+ { 5, 4, 3 },
+ { 8, 9, 5 },
+ { 6, 5, 3 },
+ { 11, 9, 6 },
+});
+matrix = matrix[{ .y1 = 1, .x1 = 0, .y2 = 1, .x2 = 2 }];
+_.assertIsEqual(matrix, steppable::Matrix({ { 8, 9, 5 } }));
+SECTION_END()
+
+SECTION(Matrix joining)
+steppable::Matrix matrix1({
+ { 5, 4, 3 },
+ { 8, 9, 5 },
+ { 6, 5, 3 },
+});
+steppable::Matrix matrix2({
+ { 11, 9, 6 },
+ { 8, 9, 5 },
+ { 6, 5, 3 },
+});
+steppable::Matrix matrix3 = matrix1 << matrix2;
+steppable::Matrix matrix4 = matrix1 >> matrix2;
+
+_.assertIsEqual(matrix3,
+ steppable::Matrix({
+ { 5, 4, 3, 11, 9, 6 },
+ { 8, 9, 5, 8, 9, 5 },
+ { 6, 5, 3, 6, 5, 3 },
+ }));
+
+_.assertIsEqual(matrix4,
+ steppable::Matrix({
+ { 11, 9, 6, 5, 4, 3 },
+ { 8, 9, 5, 8, 9, 5 },
+ { 6, 5, 3, 6, 5, 3 },
+ }));
+SECTION_END()
+
+SECTION(Matrix Rank)
+using namespace steppable::literals;
+
+steppable::Matrix matrix1({
+ { 1, 2, 1 },
+ { -2, -3, 1 },
+ { 3, 5, 0 },
+});
+_.assertIsEqual(matrix1.rank(), 2_n);
+SECTION_END()
+
+SECTION(Matrix inverse)
+steppable::Matrix matrix1({
+ { -1, 1.5 },
+ { 1, -1 },
+});
+_.assertIsEqual((matrix1 ^ -1),
+ steppable::Matrix({ {
+ 2.0000000000,
+ 3.0000000000,
+ },
+ {
+ 2.0000000000,
+ 2.0000000000,
+ } }));
+
+steppable::Matrix matrix2(
+ {
+ { 69, 420, 475 },
+ { 589, 4795, 33 },
+ { 52, 47.5, 20.2 },
+ },
+ 6);
+auto test = (matrix2 ^ -1 ^ -1).roundOffValues(1);
+_.assertIsEqual(test, matrix2);
+SECTION_END()
+TEST_END()
diff --git a/tests/testNumber.cpp b/tests/testNumber.cpp
index 301f2b697..0fa46e07a 100644
--- a/tests/testNumber.cpp
+++ b/tests/testNumber.cpp
@@ -21,8 +21,8 @@
**************************************************************************************************/
#include "colors.hpp"
-#include "number.hpp"
#include "output.hpp"
+#include "steppable/number.hpp"
#include "testing.hpp"
#include "util.hpp"
@@ -49,7 +49,7 @@ _.assertIsEqual((Number("456") * Number("123")).present(), "56088");
SECTION_END()
SECTION(Test Division)
-_.assertIsEqual((Number("123", 4, USE_CURRENT_PREC) / Number("456")).present(), "0.2697");
+_.assertIsEqual((Number("123", 4, RoundingMode::USE_CURRENT_PREC) / Number("456")).present(), "0.2697");
SECTION_END()
SECTION(Test Remainder)
@@ -69,4 +69,14 @@ _.assertTrue(Number("123") <= Number("456"));
_.assertTrue(Number("456") >= Number("123"));
SECTION_END()
+SECTION(Test Increment and Decrement)
+_.assertTrue(++Number("123") == Number("124"));
+_.assertTrue(--Number("124") == Number("123"));
+SECTION_END()
+
+SECTION(Test unary operators)
+_.assertTrue(-Number("123") == Number("-123"));
+_.assertTrue(+Number("123") == Number("123"));
+SECTION_END()
+
TEST_END()
diff --git a/tests/testRef.cpp b/tests/testRef.cpp
new file mode 100644
index 000000000..e8cbaa081
--- /dev/null
+++ b/tests/testRef.cpp
@@ -0,0 +1,32 @@
+/**************************************************************************************************
+ * Copyright (c) 2023-2025 NWSOFT *
+ * *
+ * 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. *
+ **************************************************************************************************/
+
+#include "colors.hpp"
+#include "output.hpp"
+#include "testing.hpp"
+#include "util.hpp"
+
+#include
+#include
+
+TEST_START()
+TEST_END()
diff --git a/tools/install.py b/tools/install.py
index 2c01e217a..0a52de907 100644
--- a/tools/install.py
+++ b/tools/install.py
@@ -24,9 +24,10 @@
Installs the Steppable settings and resources to the user home.
"""
-from lib.paths import DEFAULT_CONFIG_DIR, CONFIG_DIR
import shutil
+from lib.paths import CONFIG_DIR, DEFAULT_CONFIG_DIR
+
def copy_resources() -> None:
"""
diff --git a/tools/new_component.py b/tools/new_component.py
index acb34e3fe..fdbe297a5 100644
--- a/tools/new_component.py
+++ b/tools/new_component.py
@@ -23,8 +23,8 @@
import datetime
from pathlib import Path
-from lib.paths import PROJECT_PATH, SRC_DIR, TESTS_DIR
from lib.getch import getch
+from lib.paths import PROJECT_PATH, SRC_DIR, TESTS_DIR
WELCOME_STRING = """\
WELCOME TO STEPPABLE!
@@ -148,7 +148,7 @@ def make_dir(name: str, date: str, author: str) -> None:
path: Path = SRC_DIR / origin / name
if not path.is_dir():
- path.mkdir(exist_ok=False)
+ path.mkdir(exist_ok=True, parents=True)
else:
print(
"COMPONENT EXISTS. Maybe you want to add your code to that component, or choose another name."
@@ -191,13 +191,13 @@ def make_dir(name: str, date: str, author: str) -> None:
#include "{name}Report.hpp"
#include
-namespace steppable::__internals::arithmetic
-{BRACE}
+namespace steppable::__internals::{origin}
+{{
std::string {name}(/* Arguments... */)
- {BRACE}
+ {{
// Your code here...
- {END_BRACE}
-{END_BRACE} // namespace steppable::__internals::arithmetic
+ }}
+}} // namespace steppable::__internals::{origin}
"""
)
print(f"- Added {name}.cpp")
@@ -239,9 +239,9 @@ def make_dir(name: str, date: str, author: str) -> None:
#include
std::string report{name.capitalize()}()
-{BRACE}
+{{
// Your code here...
-{END_BRACE}
+}}
"""
)
print(f"- Added {name}Report.cpp")