C++ PROGRAMMING · 40 MIN STUDY

Project: build a calculator and unit converter

Create a small console calculator and unit converter with validated input, focused functions, error handling, and a repeatable build.

Prerequisites and project outcomes

Know functions, conditions, loops, strings, numeric types, and basic input validation. You will build a console program that adds, subtracts, multiplies, divides, and converts Celsius to Fahrenheit and kilometres to miles.

Requirements

  • Read a command and the required numeric operands.
  • Reject malformed numbers and trailing characters.
  • Reject division by zero.
  • Keep calculation logic separate from menu interaction.
  • Explain floating-point limitations and test boundary cases.

Prerequisites: functions, arithmetic, input validation, and basic exception handling. Review Functions, Explicit conversions, Input validation, and Exceptions first. Unit testing and CMake are optional extensions covered later in the project module.

Complete single-file implementation (C++17)

#include <iostream>
#include <string>
#include <stdexcept>
#include <cmath>

bool parse_number(const std::string& text, double& value) {
    try {
        std::size_t used = 0;
        value = std::stod(text, &used);
        return used == text.size() && std::isfinite(value);
    } catch (const std::exception&) { return false; }
}
double calculate(char op, double a, double b) {
    switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/':
            if (b == 0.0) throw std::domain_error("division by zero");
            return a / b;
        default: throw std::invalid_argument("unknown operator");
    }
}
double celsius_to_fahrenheit(double c) { return c * 9.0 / 5.0 + 32.0; }
double kilometres_to_miles(double km) { return km * 0.621371192237334; }
int main() {
    for (;;) {
        std::cout << "\n1 Calculate  2 C-to-F  3 km-to-mi  0 Exit\nChoice: ";
        std::string line;
        if (!std::getline(std::cin, line)) break;
        if (line == "0") break;
        if (line == "1") {
            std::string op_text, left, right;
            std::cout << "Operator (+ - * /): "; if (!std::getline(std::cin, op_text)) break;
            std::cout << "First number: "; if (!std::getline(std::cin, left)) break;
            std::cout << "Second number: "; if (!std::getline(std::cin, right)) break;
            double a{}, b{};
            if (op_text.size() != 1 || !parse_number(left, a) || !parse_number(right, b)) {
                std::cout << "Invalid input.\n"; continue;
            }
            try { std::cout << "Result: " << calculate(op_text[0], a, b) << '\n'; }
            catch (const std::exception& e) { std::cout << e.what() << '\n'; }
        } else if (line == "2" || line == "3") {
            std::string raw; std::cout << "Value: "; if (!std::getline(std::cin, raw)) break;
            double value{};
            if (!parse_number(raw, value)) { std::cout << "Invalid number.\n"; continue; }
            if (line == "2") std::cout << celsius_to_fahrenheit(value) << " F\n";
            else std::cout << kilometres_to_miles(value) << " miles\n";
        } else std::cout << "Unknown choice.\n";
    }
}

How the design works

  • parse_number checks that the entire string was consumed and rejects non-finite values.
  • calculate owns arithmetic rules and reports invalid operations with exceptions.
  • Conversion functions are small pure functions that can be tested independently.
  • The menu handles input/output only; it does not duplicate the calculation formulas.

Build and run

g++ -std=c++17 -Wall -Wextra -Wpedantic calculator.cpp -o calculator
./calculator

On Windows, run calculator.exe. A CMake build can be added using the same pattern shown in the library-catalogue capstone.

Test plan

  • Calculate 7 + 5, 7 / 2, and 7 / 0.
  • Try malformed values such as abc, 12cats, and an empty line.
  • Convert 0°C to 32°F and 100°C to 212°F.
  • Convert 1 km to approximately 0.621371 miles.
  • Try very large magnitudes and check whether the result remains finite.
  • Send EOF at each input prompt and confirm the program exits cleanly.

Limitations and safety

This is a learning calculator, not a financial or scientific calculator. Binary floating-point represents many decimal fractions approximately. Floating-point overflow can produce infinity during arithmetic even when inputs were finite, so a more robust version should check results with std::isfinite. Unit conversion constants are approximations; state the precision expected by the application.

Practice extensions

  1. Add pounds-to-kilograms and metres-to-feet conversions.
  2. Write tests for calculate without using terminal input.
  3. Reject values outside a domain-specific range where appropriate.
  4. Move declarations and implementations into separate files and add CTest.

Practice extension solutions

Show implementation guidance
  1. Add pure conversion functions: double pounds_to_kg(double pounds) { return pounds * 0.45359237; } and double metres_to_feet(double metres) { return metres * 3.280839895; }. Label the output units and document the precision of the constants.
  2. Test calculate directly: ordinary addition/subtraction/multiplication/division, negative operands, fractional results, division by zero (expect std::domain_error), and an unknown operator (expect std::invalid_argument). Use a tolerance for floating-point comparisons instead of exact equality for non-integer results.
  3. Validate the domain before calculation. For example, a conversion that accepts only non-negative physical quantities should reject negative input with a clear message; use std::isfinite to reject NaN and infinity. The valid range depends on the application and must be documented rather than guessed.
  4. Move declarations into a header, definitions into a source file, and keep main for menu interaction. Add the files to a CMake target and register a separate test executable with CTest, following the build and test lessons.

Summary

This project practices input parsing, separation of concerns, pure functions, exceptions, and boundary testing. The next step is to study modern C++23 library additions and their compiler-support requirements.

References