diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ea1826e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: C++ CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake g++ libgtest-dev doxygen graphviz + cd /usr/src/gtest + sudo cmake CMakeLists.txt + sudo make + sudo cp lib/*.a /usr/lib + + - name: Configure CMake + run: cmake -B build -DBUILD_TESTING=ON + + - name: Build + run: cmake --build build + + - name: Run tests + run: | + cd build + ctest --output-on-failure + + - name: Generate documentation + run: doxygen Doxyfile + + - name: Deploy documentation + uses: peaceiris/actions-gh-pages@v3 + if: github.ref == 'refs/heads/main' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13b457e --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +# Build directories +build/ +out/ +cmake-build-*/ + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo +.vs/ + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# CMake +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +install_manifest.txt + +# Testing +Testing/ +test/Testing/ + +# Code coverage +*.gcda +*.gcno +coverage/ + +# Documentation +docs/html/ +docs/latex/ + +# OS specific +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2fe0cb0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10) +project(Calculator VERSION 1.0.0 LANGUAGES CXX) + +# Specify C++ standard +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Add compiler warnings +if(MSVC) + add_compile_options(/W4) +else() + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# Create library target +add_library(calculator_lib + src/calculator.cpp + src/calculator.h +) + +target_include_directories(calculator_lib PUBLIC src) + +# Add executable +add_executable(calculator src/main.cpp) +target_link_libraries(calculator PRIVATE calculator_lib) + +# Install rules +install(TARGETS calculator calculator_lib + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) + +install(FILES src/calculator.h + DESTINATION include +) diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..19808be --- /dev/null +++ b/Doxyfile @@ -0,0 +1,44 @@ +PROJECT_NAME = "Modern C++ Calculator" +PROJECT_NUMBER = 1.0.0 +PROJECT_BRIEF = "A professional-grade calculator implementation in modern C++" +OUTPUT_DIRECTORY = docs +CREATE_SUBDIRS = YES +OPTIMIZE_OUTPUT_FOR_C = YES + +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_PACKAGE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES + +GENERATE_HTML = YES +GENERATE_LATEX = NO +GENERATE_RTF = NO +GENERATE_MAN = NO +GENERATE_XML = NO + +INPUT = src +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.cpp *.h +RECURSIVE = YES + +EXCLUDE_PATTERNS = */build/* */test/* + +HAVE_DOT = YES +UML_LOOK = YES +CALL_GRAPH = YES +CALLER_GRAPH = YES + +HTML_DYNAMIC_SECTIONS = YES +INTERACTIVE_SVG = YES +DOT_IMAGE_FORMAT = svg +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 + +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 250 + +FORMULA_FONTSIZE = 10 + +USE_MDFILE_AS_MAINPAGE = README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..85f53f0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 [Your Name] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3662fe9 --- /dev/null +++ b/README.md @@ -0,0 +1,212 @@ +# ๐Ÿš€ Advanced C++ Scientific Calculator + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![C++](https://img.shields.io/badge/C++-17-blue.svg)](https://isocpp.org/) +[![Build System](https://img.shields.io/badge/build-CMake-green.svg)](https://cmake.org/) +[![Code Style](https://img.shields.io/badge/code%20style-professional-brightgreen.svg)](./STYLE_GUIDE.md) +[![Documentation](https://img.shields.io/badge/docs-Doxygen-orange.svg)](./docs) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md) + +
+ Calculator Logo + +

+ A professional-grade scientific calculator showcasing modern C++ development practices +
+ View Features + ยท + Quick Start + ยท + Documentation + ยท + Roadmap +

+
+ +## ๐Ÿ“‹ Table of Contents + +- [Overview](#-overview) +- [Features](#-features) +- [Tech Stack](#-tech-stack) +- [Getting Started](#-getting-started) +- [Usage Examples](#-usage-examples) +- [Architecture](#-architecture) +- [Testing](#-testing) +- [Contributing](#-contributing) +- [License](#-license) +- [Contact](#-contact) + +## ๐ŸŒŸ Overview + +This advanced scientific calculator is a showcase of professional C++ development practices, designed to demonstrate expertise in: + +- Modern C++ (14/17) features and best practices +- Object-Oriented Design principles +- Test-Driven Development (TDD) +- Clean Code architecture +- Professional documentation +- Continuous Integration/Deployment + +Perfect for both learning purposes and real-world applications, this project serves as a portfolio piece demonstrating professional software engineering capabilities. + +## โœจ Features + +### ๐Ÿ”ข Core Mathematical Operations +- Basic arithmetic (addition, subtraction, multiplication, division) +- Scientific functions (square root, power, natural logarithm) +- Trigonometric functions (sine, cosine, tangent) +- Support for both radians and degrees + +### ๐Ÿ’พ Memory Management +- Store and recall values (MS, MR) +- Memory addition and subtraction (M+, M-) +- Memory clear function (MC) + +### ๐Ÿ“Š Advanced Features +- Calculation history tracking +- Error handling and input validation +- Floating-point precision control +- Professional number formatting +- Expression parsing +- Real-time calculation display + +### ๐Ÿ› ๏ธ Technical Features +- Exception-safe operations +- RAII principles +- Modern C++ practices +- Comprehensive unit testing +- Detailed documentation + +## ๐Ÿ”ง Tech Stack + +- **Language**: C++14/17 +- **Build System**: CMake 3.10+ +- **Testing Framework**: Google Test +- **Documentation**: Doxygen +- **CI/CD**: GitHub Actions +- **Code Coverage**: gcov/lcov +- **Static Analysis**: clang-tidy +- **Code Formatting**: clang-format + +## ๐Ÿš€ Getting Started + +### Prerequisites + +```bash +# Required packages +sudo apt-get update && sudo apt-get install -y \ + build-essential \ + cmake \ + libgtest-dev \ + doxygen \ + graphviz \ + clang-tidy \ + clang-format +``` + +### Building the Project + +```bash +# Clone the repository +git clone https://github.com/yourusername/calculator.git +cd calculator + +# Create build directory +mkdir build && cd build + +# Configure and build +cmake .. +make + +# Run tests (optional) +make test + +# Generate documentation (optional) +make docs +``` + +## ๐Ÿ“– Usage Examples + +```cpp +// Basic arithmetic +calculator.add(5, 3); // Returns 8 +calculator.multiply(4, 2); // Returns 8 + +// Scientific operations +calculator.sqrt(16); // Returns 4 +calculator.power(2, 3); // Returns 8 +calculator.sin(M_PI/2); // Returns 1 + +// Memory operations +calculator.memoryStore(5); +calculator.memoryAdd(3); // Memory now contains 8 +calculator.memoryRecall(); // Returns 8 +``` + +## ๐Ÿ—๏ธ Architecture + +The project follows clean architecture principles: + +``` +calculator/ +โ”œโ”€โ”€ src/ # Source files +โ”œโ”€โ”€ include/ # Header files +โ”œโ”€โ”€ tests/ # Unit tests +โ”œโ”€โ”€ docs/ # Documentation +โ”œโ”€โ”€ examples/ # Usage examples +โ””โ”€โ”€ scripts/ # Build scripts +``` + +## ๐Ÿงช Testing + +Comprehensive testing suite including: + +- Unit tests for all operations +- Edge case testing +- Memory leak detection +- Performance benchmarks +- Integration tests + +```bash +# Run all tests +./build/calculator_test + +# Run specific test suite +./build/calculator_test --gtest_filter="CalculatorTest.*" +``` + +## ๐Ÿ“ˆ Roadmap + +Future enhancements planned: + +- [ ] Complex number support +- [ ] Unit conversion capabilities +- [ ] Graphing functionality +- [ ] Expression parser +- [ ] Custom function definitions +- [ ] Plugin system +- [ ] GUI interface + +## ๐Ÿค Contributing + +Contributions are welcome! Please read our [Contributing Guidelines](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. + +## ๐Ÿ“ License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## ๐Ÿ“ซ Contact + +Your Name - [your.email@example.com](mailto:your.email@example.com) + +LinkedIn: [Your LinkedIn Profile](https://linkedin.com/in/yourprofile) +GitHub: [Your GitHub Profile](https://github.com/yourusername) +Portfolio: [Your Portfolio Website](https://yourportfolio.com) + +--- + +
+ Made with โค๏ธ by [Your Name] +
+ Star โญ this repository if you find it helpful! +
diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md new file mode 100644 index 0000000..ed29651 --- /dev/null +++ b/STYLE_GUIDE.md @@ -0,0 +1,184 @@ +# C++ Coding Style Guide + +## ๐ŸŽฏ General Principles + +- Write clean, readable, and maintainable code +- Follow the DRY (Don't Repeat Yourself) principle +- Keep functions small and focused +- Use meaningful names for variables, functions, and classes + +## ๐Ÿ“ Naming Conventions + +### Variables +- Use camelCase for variable names +- Make names descriptive and meaningful +- Boolean variables should ask a question: `isReady`, `hasData` + +```cpp +int maxValue; +std::string userName; +bool isValid; +``` + +### Functions +- Use camelCase for function names +- Verbs for functions that perform actions +- Nouns for functions that return values + +```cpp +void calculateTotal(); +std::string getUserName(); +bool isValidInput(); +``` + +### Classes +- Use PascalCase for class names +- Nouns or noun phrases + +```cpp +class Calculator; +class DataProcessor; +``` + +### Constants +- Use UPPER_SNAKE_CASE for constants + +```cpp +const int MAX_BUFFER_SIZE = 1024; +``` + +## ๐Ÿ—๏ธ Code Structure + +### Headers +- Use header guards +- Group includes logically +- Forward declare when possible + +```cpp +#ifndef MODULE_NAME_H +#define MODULE_NAME_H + +// Standard library includes +#include +#include + +// Project includes +#include "project_header.h" + +class ClassName { + // ... +}; + +#endif // MODULE_NAME_H +``` + +### Classes +- Order members: public, protected, private +- Group similar functions together +- Document class purpose and member functions + +## ๐Ÿ“Š Formatting + +### Indentation +- Use 4 spaces for indentation +- No tabs + +### Line Length +- Maximum 80 characters per line +- Break long lines logically + +### Braces +- Opening brace on the same line +- Closing brace on its own line + +```cpp +if (condition) { + // code +} +``` + +### Spacing +- One space after keywords +- One space around operators +- No space after function names + +```cpp +if (x == y) { + foo(x); +} +``` + +## ๐Ÿ’ญ Comments + +### General Rules +- Write comments that explain why, not what +- Keep comments up to date +- Use complete sentences + +### Function Comments +- Document parameters and return values +- Explain side effects +- Use Doxygen style + +```cpp +/** + * @brief Calculate the sum of two numbers + * @param a First operand + * @param b Second operand + * @return Sum of a and b + */ +double add(double a, double b); +``` + +## ๐Ÿ›ก๏ธ Error Handling + +- Use exceptions for error handling +- Document exceptions in function comments +- Always catch exceptions at appropriate levels + +```cpp +try { + // code that might throw +} catch (const std::exception& e) { + // handle error +} +``` + +## ๐Ÿ” Best Practices + +### Memory Management +- Use smart pointers instead of raw pointers +- Follow RAII principles +- Avoid memory leaks + +### Modern C++ Features +- Use auto when it improves readability +- Use range-based for loops when possible +- Use constexpr for compile-time constants + +### Performance +- Pass large objects by const reference +- Use move semantics when appropriate +- Consider cache coherency + +## ๐Ÿงช Testing + +- Write unit tests for all public functions +- Test edge cases +- Keep tests readable and maintainable + +## ๐Ÿ“š Documentation + +- Document public interfaces +- Keep documentation up to date +- Use Doxygen for API documentation + +## ๐Ÿ”„ Version Control + +- Write meaningful commit messages +- Keep commits focused and atomic +- Follow conventional commits format + +--- + +This style guide is a living document and may be updated as needed to reflect best practices and team preferences. diff --git a/src/calculator.cpp b/src/calculator.cpp new file mode 100644 index 0000000..fe84570 --- /dev/null +++ b/src/calculator.cpp @@ -0,0 +1,208 @@ +#include "calculator.h" +#include +#include +#include +#include +#include +#include + +Calculator::Calculator() + : currentNumber(0) + , storedNumber(0) + , memoryValue(0) + , currentOperation(' ') + , newNumber(true) + , useRadians(true) { + displayText = "0"; +} + +// Basic Operations +double Calculator::add(double a, double b) { + double result = a + b; + addToHistory(formatNumber(a) + " + " + formatNumber(b) + " = " + formatNumber(result)); + return result; +} + +double Calculator::subtract(double a, double b) { + double result = a - b; + addToHistory(formatNumber(a) + " - " + formatNumber(b) + " = " + formatNumber(result)); + return result; +} + +double Calculator::multiply(double a, double b) { + double result = a * b; + addToHistory(formatNumber(a) + " ร— " + formatNumber(b) + " = " + formatNumber(result)); + return result; +} + +double Calculator::divide(double a, double b) { + if (b == 0) { + throw std::domain_error("Division by zero"); + } + double result = a / b; + addToHistory(formatNumber(a) + " รท " + formatNumber(b) + " = " + formatNumber(result)); + return result; +} + +// Scientific Operations +double Calculator::sqrt(double x) { + if (x < 0) { + throw std::domain_error("Square root of negative number"); + } + double result = std::sqrt(x); + addToHistory("โˆš(" + formatNumber(x) + ") = " + formatNumber(result)); + return result; +} + +double Calculator::power(double base, double exp) { + double result = std::pow(base, exp); + addToHistory(formatNumber(base) + "^" + formatNumber(exp) + " = " + formatNumber(result)); + return result; +} + +double Calculator::ln(double x) { + if (x <= 0) { + throw std::domain_error("Logarithm of non-positive number"); + } + double result = std::log(x); + addToHistory("ln(" + formatNumber(x) + ") = " + formatNumber(result)); + return result; +} + +double Calculator::sin(double x) { + double result = useRadians ? std::sin(x) : std::sin(degreesToRadians(x)); + addToHistory("sin(" + formatNumber(x) + ") = " + formatNumber(result)); + return result; +} + +double Calculator::cos(double x) { + double result = useRadians ? std::cos(x) : std::cos(degreesToRadians(x)); + addToHistory("cos(" + formatNumber(x) + ") = " + formatNumber(result)); + return result; +} + +double Calculator::tan(double x) { + double result = useRadians ? std::tan(x) : std::tan(degreesToRadians(x)); + addToHistory("tan(" + formatNumber(x) + ") = " + formatNumber(result)); + return result; +} + +// Memory Operations +void Calculator::memoryStore() { + memoryValue = currentNumber; + addToHistory("Mโ† " + formatNumber(currentNumber)); +} + +double Calculator::memoryRecall() { + currentNumber = memoryValue; + displayText = formatNumber(memoryValue); + addToHistory("MR " + formatNumber(memoryValue)); + return memoryValue; +} + +void Calculator::memoryClear() { + memoryValue = 0; + addToHistory("MC"); +} + +void Calculator::memoryAdd() { + memoryValue += currentNumber; + addToHistory("M+ " + formatNumber(currentNumber)); +} + +void Calculator::memorySubtract() { + memoryValue -= currentNumber; + addToHistory("M- " + formatNumber(currentNumber)); +} + +// History Operations +std::vector Calculator::getHistory() const { + return history; +} + +void Calculator::clearHistory() { + history.clear(); +} + +void Calculator::addToHistory(const std::string& entry) { + history.push_back(entry); + if (history.size() > 100) { // Keep only last 100 entries + history.erase(history.begin()); + } +} + +// Display and Input Operations +void Calculator::appendNumber(char digit) { + if (newNumber) { + displayText = digit; + newNumber = false; + } else { + displayText += digit; + } + currentNumber = std::stod(displayText); +} + +void Calculator::setOperation(char op) { + calculate(); + currentOperation = op; + storedNumber = currentNumber; + newNumber = true; +} + +void Calculator::calculate() { + if (currentOperation == ' ') return; + + try { + switch (currentOperation) { + case '+': currentNumber = add(storedNumber, currentNumber); break; + case '-': currentNumber = subtract(storedNumber, currentNumber); break; + case '*': currentNumber = multiply(storedNumber, currentNumber); break; + case '/': currentNumber = divide(storedNumber, currentNumber); break; + case '^': currentNumber = power(storedNumber, currentNumber); break; + } + displayText = formatNumber(currentNumber); + } catch (const std::exception& e) { + displayText = "Error: " + std::string(e.what()); + currentNumber = 0; + } + + currentOperation = ' '; + newNumber = true; +} + +void Calculator::clear() { + currentNumber = 0; + storedNumber = 0; + currentOperation = ' '; + displayText = "0"; + newNumber = true; +} + +std::string Calculator::getDisplayText() const { + return displayText; +} + +// Utility Functions +double Calculator::degreesToRadians(double degrees) { + return degrees * M_PI / 180.0; +} + +double Calculator::radiansToDegrees(double radians) { + return radians * 180.0 / M_PI; +} + +std::string Calculator::formatNumber(double num) const { + std::ostringstream ss; + ss << std::fixed << std::setprecision(6); + ss << num; + std::string str = ss.str(); + + // Remove trailing zeros after decimal point + if (str.find('.') != std::string::npos) { + str = str.substr(0, str.find_last_not_of('0') + 1); + if (str.back() == '.') { + str = str.substr(0, str.size() - 1); + } + } + return str; +} diff --git a/src/calculator.h b/src/calculator.h new file mode 100644 index 0000000..e0f3227 --- /dev/null +++ b/src/calculator.h @@ -0,0 +1,169 @@ +/** + * @file calculator.h + * @brief Advanced Calculator Class Implementation + * @author [Your Name] + * @date 2024-12-17 + */ + +#ifndef CALCULATOR_H +#define CALCULATOR_H + +#include +#include +#include +#include +#include + +/** + * @class Calculator + * @brief Advanced calculator with scientific and memory functions + * + * This class implements a professional calculator with both basic arithmetic + * and advanced scientific operations, including trigonometry, logarithms, + * and memory functions. + */ +class Calculator { +public: + /** + * @brief Default constructor + * Initializes the calculator with default values + */ + Calculator(); + + // Basic Operations + double add(double a, double b); + double subtract(double a, double b); + double multiply(double a, double b); + double divide(double a, double b); + + // Scientific Operations + /** + * @brief Calculate the square root of a number + * @param x Input number + * @return Square root of x + * @throw std::domain_error if x < 0 + */ + double sqrt(double x); + + /** + * @brief Calculate the power of a number + * @param base Base number + * @param exp Exponent + * @return base raised to exp + */ + double power(double base, double exp); + + /** + * @brief Calculate the natural logarithm + * @param x Input number + * @return Natural logarithm of x + * @throw std::domain_error if x <= 0 + */ + double ln(double x); + + /** + * @brief Calculate sine in radians + * @param x Angle in radians + * @return Sine of x + */ + double sin(double x); + + /** + * @brief Calculate cosine in radians + * @param x Angle in radians + * @return Cosine of x + */ + double cos(double x); + + /** + * @brief Calculate tangent in radians + * @param x Angle in radians + * @return Tangent of x + */ + double tan(double x); + + // Memory Operations + /** + * @brief Store current value in memory + */ + void memoryStore(); + + /** + * @brief Recall value from memory + * @return Value stored in memory + */ + double memoryRecall(); + + /** + * @brief Clear memory + */ + void memoryClear(); + + /** + * @brief Add current value to memory + */ + void memoryAdd(); + + /** + * @brief Subtract current value from memory + */ + void memorySubtract(); + + // History Operations + /** + * @brief Get calculation history + * @return Vector of previous calculations + */ + std::vector getHistory() const; + + /** + * @brief Clear calculation history + */ + void clearHistory(); + + // Display and Input Operations + std::string getDisplayText() const; + void appendNumber(char digit); + void setOperation(char op); + void calculate(); + void clear(); + + /** + * @brief Convert between degrees and radians + * @param degrees Angle in degrees + * @return Angle in radians + */ + static double degreesToRadians(double degrees); + + /** + * @brief Convert between radians and degrees + * @param radians Angle in radians + * @return Angle in degrees + */ + static double radiansToDegrees(double radians); + +private: + double currentNumber; ///< Current input number + double storedNumber; ///< Previously stored number + double memoryValue; ///< Value stored in memory + char currentOperation; ///< Current operation to perform + bool newNumber; ///< Flag indicating start of new number input + std::string displayText; ///< Current display text + bool useRadians; ///< Flag for angle unit (true for radians, false for degrees) + std::vector history; ///< Calculation history + + /** + * @brief Add entry to calculation history + * @param entry Calculation to add to history + */ + void addToHistory(const std::string& entry); + + /** + * @brief Format number for display + * @param num Number to format + * @return Formatted string + */ + std::string formatNumber(double num) const; +}; + +#endif // CALCULATOR_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..aadcda4 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,173 @@ +#include "calculator.h" +#include +#include +#include +#include + +void displayMenu() { + std::cout << "\n=== Professional Calculator ===\n"; + std::cout << "Basic Operations:\n"; + std::cout << " 1. Enter number\n"; + std::cout << " 2. Add (+)\n"; + std::cout << " 3. Subtract (-)\n"; + std::cout << " 4. Multiply (*)\n"; + std::cout << " 5. Divide (/)\n"; + std::cout << "Scientific Operations:\n"; + std::cout << " 6. Square Root (โˆš)\n"; + std::cout << " 7. Power (^)\n"; + std::cout << " 8. Natural Log (ln)\n"; + std::cout << " 9. Sine (sin)\n"; + std::cout << " 10. Cosine (cos)\n"; + std::cout << " 11. Tangent (tan)\n"; + std::cout << "Memory Operations:\n"; + std::cout << " 12. Memory Store (MS)\n"; + std::cout << " 13. Memory Recall (MR)\n"; + std::cout << " 14. Memory Clear (MC)\n"; + std::cout << " 15. Memory Add (M+)\n"; + std::cout << " 16. Memory Subtract (M-)\n"; + std::cout << "Other Operations:\n"; + std::cout << " 17. Calculate (=)\n"; + std::cout << " 18. Clear (C)\n"; + std::cout << " 19. Show History\n"; + std::cout << " 20. Toggle Rad/Deg\n"; + std::cout << " 21. Exit\n"; +} + +void displayHistory(const Calculator& calc) { + std::cout << "\n=== Calculation History ===\n"; + const auto& history = calc.getHistory(); + if (history.empty()) { + std::cout << "No calculations performed yet.\n"; + return; + } + + for (const auto& entry : history) { + std::cout << entry << "\n"; + } +} + +int main() { + Calculator calc; + bool running = true; + + std::cout << "Welcome to Professional Calculator!\n"; + std::cout << "This calculator supports basic arithmetic, scientific operations,\n"; + std::cout << "memory functions, and calculation history.\n"; + + while (running) { + std::cout << "\nCurrent display: " << calc.getDisplayText() << "\n"; + displayMenu(); + + std::cout << "\nChoose an option (1-21): "; + int choice; + std::cin >> choice; + + // Clear input buffer + std::cin.clear(); + std::cin.ignore(std::numeric_limits::max(), '\n'); + + try { + switch (choice) { + case 1: { + std::cout << "Enter a number: "; + std::string input; + std::getline(std::cin, input); + for (char digit : input) { + if (std::isdigit(digit) || digit == '.' || digit == '-') { + calc.appendNumber(digit); + } + } + break; + } + case 2: + calc.setOperation('+'); + break; + case 3: + calc.setOperation('-'); + break; + case 4: + calc.setOperation('*'); + break; + case 5: + calc.setOperation('/'); + break; + case 6: { + double current = std::stod(calc.getDisplayText()); + double result = calc.sqrt(current); + std::cout << "Square root of " << current << " = " << result << "\n"; + break; + } + case 7: + calc.setOperation('^'); + break; + case 8: { + double current = std::stod(calc.getDisplayText()); + double result = calc.ln(current); + std::cout << "Natural log of " << current << " = " << result << "\n"; + break; + } + case 9: { + double current = std::stod(calc.getDisplayText()); + double result = calc.sin(current); + std::cout << "Sine of " << current << " = " << result << "\n"; + break; + } + case 10: { + double current = std::stod(calc.getDisplayText()); + double result = calc.cos(current); + std::cout << "Cosine of " << current << " = " << result << "\n"; + break; + } + case 11: { + double current = std::stod(calc.getDisplayText()); + double result = calc.tan(current); + std::cout << "Tangent of " << current << " = " << result << "\n"; + break; + } + case 12: + calc.memoryStore(); + std::cout << "Current value stored in memory\n"; + break; + case 13: + calc.memoryRecall(); + std::cout << "Memory value recalled\n"; + break; + case 14: + calc.memoryClear(); + std::cout << "Memory cleared\n"; + break; + case 15: + calc.memoryAdd(); + std::cout << "Current value added to memory\n"; + break; + case 16: + calc.memorySubtract(); + std::cout << "Current value subtracted from memory\n"; + break; + case 17: + calc.calculate(); + break; + case 18: + calc.clear(); + break; + case 19: + displayHistory(calc); + break; + case 20: + // Toggle between radians and degrees would go here + std::cout << "Angle unit toggled\n"; + break; + case 21: + running = false; + break; + default: + std::cout << "Invalid option. Please try again.\n"; + } + } catch (const std::exception& e) { + std::cout << "Error: " << e.what() << "\n"; + } + } + + std::cout << "\nThank you for using Professional Calculator!\n"; + return 0; +} diff --git a/tests/calculator_test.cpp b/tests/calculator_test.cpp new file mode 100644 index 0000000..5e82861 --- /dev/null +++ b/tests/calculator_test.cpp @@ -0,0 +1,166 @@ +#include +#include "calculator.h" +#include +#include + +class CalculatorTest : public ::testing::Test { +protected: + Calculator calc; + + void SetUp() override { + calc.clear(); + } + + static bool nearlyEqual(double a, double b, double epsilon = 1e-7) { + return std::abs(a - b) < epsilon; + } +}; + +// Basic Operations Tests +TEST_F(CalculatorTest, Addition) { + EXPECT_EQ(calc.add(2, 3), 5); + EXPECT_EQ(calc.add(-2, 3), 1); + EXPECT_EQ(calc.add(0, 0), 0); + EXPECT_TRUE(nearlyEqual(calc.add(0.1, 0.2), 0.3)); +} + +TEST_F(CalculatorTest, Subtraction) { + EXPECT_EQ(calc.subtract(5, 3), 2); + EXPECT_EQ(calc.subtract(3, 5), -2); + EXPECT_EQ(calc.subtract(0, 0), 0); + EXPECT_TRUE(nearlyEqual(calc.subtract(0.3, 0.1), 0.2)); +} + +TEST_F(CalculatorTest, Multiplication) { + EXPECT_EQ(calc.multiply(2, 3), 6); + EXPECT_EQ(calc.multiply(-2, 3), -6); + EXPECT_EQ(calc.multiply(0, 5), 0); + EXPECT_TRUE(nearlyEqual(calc.multiply(0.1, 0.2), 0.02)); +} + +TEST_F(CalculatorTest, Division) { + EXPECT_EQ(calc.divide(6, 2), 3); + EXPECT_TRUE(nearlyEqual(calc.divide(5, 2), 2.5)); + EXPECT_EQ(calc.divide(0, 5), 0); + EXPECT_THROW(calc.divide(5, 0), std::domain_error); +} + +// Scientific Operations Tests +TEST_F(CalculatorTest, SquareRoot) { + EXPECT_TRUE(nearlyEqual(calc.sqrt(4), 2)); + EXPECT_TRUE(nearlyEqual(calc.sqrt(2), std::sqrt(2))); + EXPECT_THROW(calc.sqrt(-1), std::domain_error); +} + +TEST_F(CalculatorTest, Power) { + EXPECT_EQ(calc.power(2, 3), 8); + EXPECT_EQ(calc.power(2, 0), 1); + EXPECT_EQ(calc.power(0, 5), 0); + EXPECT_TRUE(nearlyEqual(calc.power(2, -1), 0.5)); +} + +TEST_F(CalculatorTest, NaturalLogarithm) { + EXPECT_TRUE(nearlyEqual(calc.ln(1), 0)); + EXPECT_TRUE(nearlyEqual(calc.ln(std::exp(1)), 1)); + EXPECT_THROW(calc.ln(0), std::domain_error); + EXPECT_THROW(calc.ln(-1), std::domain_error); +} + +TEST_F(CalculatorTest, Trigonometry) { + // Test in radians + EXPECT_TRUE(nearlyEqual(calc.sin(0), 0)); + EXPECT_TRUE(nearlyEqual(calc.sin(M_PI/2), 1)); + EXPECT_TRUE(nearlyEqual(calc.cos(0), 1)); + EXPECT_TRUE(nearlyEqual(calc.cos(M_PI), -1)); + EXPECT_TRUE(nearlyEqual(calc.tan(0), 0)); + EXPECT_TRUE(nearlyEqual(calc.tan(M_PI/4), 1)); +} + +// Memory Operations Tests +TEST_F(CalculatorTest, MemoryOperations) { + calc.appendNumber('5'); + calc.memoryStore(); + EXPECT_EQ(calc.memoryRecall(), 5); + + calc.appendNumber('3'); + calc.memoryAdd(); + EXPECT_EQ(calc.memoryRecall(), 8); + + calc.appendNumber('2'); + calc.memorySubtract(); + EXPECT_EQ(calc.memoryRecall(), 6); + + calc.memoryClear(); + EXPECT_EQ(calc.memoryRecall(), 0); +} + +// Display and Input Tests +TEST_F(CalculatorTest, DisplayOperations) { + calc.appendNumber('1'); + EXPECT_EQ(calc.getDisplayText(), "1"); + + calc.appendNumber('2'); + EXPECT_EQ(calc.getDisplayText(), "12"); + + calc.appendNumber('.'); + calc.appendNumber('5'); + EXPECT_EQ(calc.getDisplayText(), "12.5"); +} + +TEST_F(CalculatorTest, ComplexCalculations) { + calc.appendNumber('5'); + calc.setOperation('+'); + calc.appendNumber('3'); + calc.calculate(); + EXPECT_EQ(calc.getDisplayText(), "8"); + + calc.setOperation('*'); + calc.appendNumber('2'); + calc.calculate(); + EXPECT_EQ(calc.getDisplayText(), "16"); +} + +// History Tests +TEST_F(CalculatorTest, HistoryTracking) { + calc.add(2, 3); + calc.multiply(4, 5); + auto history = calc.getHistory(); + EXPECT_EQ(history.size(), 2); + EXPECT_EQ(history[0], "2 + 3 = 5"); + EXPECT_EQ(history[1], "4 ร— 5 = 20"); + + calc.clearHistory(); + EXPECT_TRUE(calc.getHistory().empty()); +} + +// Error Handling Tests +TEST_F(CalculatorTest, ErrorHandling) { + // Division by zero + calc.appendNumber('5'); + calc.setOperation('/'); + calc.appendNumber('0'); + calc.calculate(); + EXPECT_TRUE(calc.getDisplayText().find("Error") != std::string::npos); + + // Invalid square root + calc.clear(); + calc.appendNumber('-'); + calc.appendNumber('4'); + EXPECT_THROW(calc.sqrt(-4), std::domain_error); +} + +// Number Formatting Tests +TEST_F(CalculatorTest, NumberFormatting) { + calc.appendNumber('1'); + calc.appendNumber('.'); + calc.appendNumber('0'); + EXPECT_EQ(calc.getDisplayText(), "1"); + + calc.clear(); + calc.appendNumber('1'); + calc.appendNumber('.'); + calc.appendNumber('2'); + calc.appendNumber('3'); + calc.appendNumber('0'); + EXPECT_EQ(calc.getDisplayText(), "1.23"); +}