Skip to content

Ch4: Operations on Data

TL;DR: Arithmetic and Shift Operators: C++ provides standard arithmetic, compound, increment, decrement, and bitwise operators that manipulate memory states directly. | Every fundamental numeric type has hard boundaries defined by the standard library (std::numeric_limits) and by how many bits it uses to represent values in binary — signed types use two's complement, unsigned types wrap around.

⚡ Quick Reference

#include <iostream>

int main() {
    int a = 10;
    int b = 3;

    // a + b, a - b, a * b, a / b, a % b - Standard arithmetic operations.
    int sum = a + b;
    int diff = a - b;
    int prod = a * b;
    int quot = a / b;
    int rem = a % b;

    // ++a, a++, --a, a-- - Increment and decrement operators.
    int x1 = ++a; // Prefix: increment first, then assign.
    int x2 = a++; // Postfix: assign first, then increment.

    // a += b, a -= b - Compound assignment operators.
    a += b;

    // a < b, a > b, a == b - Comparison operators.
    bool eq = (a == b);

    // a && b, a || b, !a - Logical operations.
    bool res = (a > 5 && b < 10);

    // std::ios::boolalpha, std::noboolalpha - Outputs booleans as text (true/false) or numbers (1/0).
    std::cout << std::boolalpha << res << std::noboolalpha << std::endl;

    return 0;
}

Numeric Limits & Binary Representation

#include <iostream>
#include <limits>
#include <bitset>
#include <cstdint>

int main() {
    // std::numeric_limits<T> - query min/max/precision for any numeric type at compile time
    std::cout << "int min: "   << std::numeric_limits<int>::min() << '\n';
    std::cout << "int max: "   << std::numeric_limits<int>::max() << '\n';
    std::cout << "size_t max: " << std::numeric_limits<size_t>::max() << '\n';

    // std::numeric_limits<T>::digits - number of bits used for the value (excludes sign bit)
    std::cout << "int digits: " << std::numeric_limits<int>::digits << '\n';

    // Fixed-width integer types (<cstdint>) - guarantee exact bit width across platforms
    int32_t a{100};
    uint8_t b{255};

    // std::bitset<N> - view the raw binary representation of a value
    std::bitset<8> bits(b);
    std::cout << "255 as bits: " << bits << '\n';

    // Two's complement: negative signed ints are represented by inverting bits and adding 1
    int8_t neg{-1};
    std::bitset<8> negBits(static_cast<uint8_t>(neg));
    std::cout << "-1 as bits: " << negBits << '\n'; // 11111111

    return 0;
}

🧠 Core Concepts

  • Arithmetic and Shift Operators: C++ provides standard arithmetic, compound, increment, decrement, and bitwise operators that manipulate memory states directly.
  • Constant Correctness (const, constexpr, constinit): C++ offers compile-time constraints like const (runtime read-only), constexpr (compile-time evaluate), and constinit (guaranteed compile-time initialization of mutable variables).
  • Integral Promotion & Type Conversion: In arithmetic, smaller types like short and char are promoted to int, and explicit casting via static_cast avoids compiler warnings during narrowing.
  • std::numeric_limits<T>: A compile-time queryable trait class that exposes the min, max, precision, and other properties of any arithmetic type — safer than hardcoding magic numbers like 2147483647.
  • Two's Complement: The standard binary representation for signed integers. The most significant bit acts as the sign bit; negative values are stored by inverting all bits of the positive value and adding 1.
  • Fixed-Width Types: <cstdint> provides types like int32_t, uint8_t, int64_t that guarantee an exact bit width regardless of platform, unlike int or long whose size can vary by compiler/architecture.

⚠️ Pitfalls (Quick Scan)

Mistake Fix
Missing semicolon on statement Always end statements with ; (Ch 4.3, 5.2)
Nesting block comments / / / / Never nest block comments; use single-line comments (//) instead (Ch 4.2)
Division by zero Verify that the divisor is not zero before dividing (Ch 4.3)
Forgetting library #include directive Include the required header file at the top of the file (Ch 4.1)
Using functional initialization with narrowing values Use braced initialization {} to catch narrowing conversions at compile time (6.2, 7.2)
Using uninitialized variables Always initialize variables, using {} for default zero-init (5.2, 9.2)
Using std::cin >> for space-separated input Use std::getline(cin, var) for strings containing space characters (7.2)
Preceding integer literals with a leading zero (0) Never prefix decimal integer literals with a leading zero (6.2)
Assuming std::setw persists across multiple outputs Reapply std::setw before printing every aligned value in a table (5.8)
Relying on implicit narrowing in assignments Use static_cast<int>() explicitly or braced initialization to flag errors (7.2)
Overflowing unsigned integer limits Check ranges using std::numeric_limits and ensure types are large enough (7.4)
Underflowing unsigned variables Ensure values are strictly positive before decrementing unsigned types (7.4)
Mixing small integral types (short/char) in arithmetic Be aware of integral promotion and cast the result back if necessary (5.11)
Initializing constinit from non-constexpr variables Ensure the initializing expression is constexpr or a compile-time constant (6.5)
Combining constexpr and constinit on the same declaration Use constexpr for immutable constants, and constinit for mutable compile-time initialized globals (6.5)
Using non-constexpr values in static_assert Ensure the asserted condition only involves compile-time constants (6.4)
Assuming relational expressions print as true/false by default Configure the stream using std::cout << std::boolalpha (5.6)
Assuming hex values print in uppercase by default Use std::uppercase to output uppercase hexadecimal digits (5.8)
Setting precision beyond the limit of the floating-point type Do not exceed 7 digits for float or 15 digits for double in setprecision (5.8)
Hardcoding min/max values (e.g. 2147483647) Use std::numeric_limits<T>::max() instead
Assuming int is always 4 bytes on every platform Use fixed-width types (int32_t) when exact size matters
Comparing signed and unsigned values directly Cast explicitly or use same-signedness types to avoid silent conversion
Assuming negative numbers just "flip the sign bit" Understand two's complement inverts all bits and adds 1
Using int8_t/uint8_t in std::cout expecting a number These are typically char aliases — cast to int first or output prints a character

📖 Full Details

Cause → Effect → Fix with timestamp (click to expand) * **Missing semicolon on statement** -> **Compile-time error ("expected semicolon before return")** -> **Always end statements with `;` (Ch 4.3, 5.2)** * **Nesting block comments `/* /* */ */`** -> **Confusing compiler error about unterminated comment** -> **Never nest block comments; use single-line comments (`//`) instead (Ch 4.2)** * **Division by zero** -> **Compiler warning followed by runtime crash when result is captured** -> **Verify that the divisor is not zero before dividing (Ch 4.3)** * **Forgetting library `#include` directive** -> **Compile-time error stating namespace std has no member** -> **Include the required header file at the top of the file (Ch 4.1)** * **Using functional initialization with narrowing values** -> **Floating-point value is silently truncated without compiler warnings** -> **Use braced initialization `{}` to catch narrowing conversions at compile time (6.2, 7.2)** * **Using uninitialized variables** -> **Retrieving indeterminate garbage values from memory causes undefined behavior** -> **Always initialize variables, using `{}` for default zero-init (5.2, 9.2)** * **Using `std::cin >>` for space-separated input** -> **Input string is truncated at the first space, leaving remaining input in buffer** -> **Use `std::getline(cin, var)` for strings containing space characters (7.2)** * **Preceding integer literals with a leading zero (`0`)** -> **Literal is interpreted as octal, producing unexpected values** -> **Never prefix decimal integer literals with a leading zero (6.2)** * **Assuming `std::setw` persists across multiple outputs** -> **Padding only affects the immediately following output field** -> **Reapply `std::setw` before printing every aligned value in a table (5.8)** * **Relying on implicit narrowing in assignments** -> **Floating-point values are silently truncated, resulting in data loss** -> **Use `static_cast()` explicitly or braced initialization to flag errors (7.2)** * **Overflowing unsigned integer limits** -> **Value wraps around silently to zero, causing logic errors** -> **Check ranges using `std::numeric_limits` and ensure types are large enough (7.4)** * **Underflowing unsigned variables** -> **Value wraps around silently to maximum possible value of the type** -> **Ensure values are strictly positive before decrementing unsigned types (7.4)** * **Mixing small integral types (`short`/`char`) in arithmetic** -> **Small types are promoted to `int` in expressions, yielding an `int` result** -> **Be aware of integral promotion and cast the result back if necessary (5.11)** * **Initializing `constinit` from non-constexpr variables** -> **Compile-time error since initializer must be known during compilation** -> **Ensure the initializing expression is `constexpr` or a compile-time constant (6.5)** * **Combining `constexpr` and `constinit` on the same declaration** -> **Compile-time error because these modifiers are mutually exclusive** -> **Use `constexpr` for immutable constants, and `constinit` for mutable compile-time initialized globals (6.5)** * **Using non-constexpr values in `static_assert`** -> **Compile-time error because condition must be evaluatable at compile time** -> **Ensure the asserted condition only involves compile-time constants (6.4)** * **Assuming relational expressions print as true/false by default** -> **Prints numeric `1` or `0`, making debugging less readable** -> **Configure the stream using `std::cout << std::boolalpha` (5.6)** * **Assuming hex values print in uppercase by default** -> **Digits default to lowercase characters (`abcdef`)** -> **Use `std::uppercase` to output uppercase hexadecimal digits (5.8)** * **Setting precision beyond the limit of the floating-point type** -> **Displays meaningless garbage digits beyond valid precision** -> **Do not exceed `7` digits for `float` or `15` digits for `double` in `setprecision` (5.8)** * **Hardcoding min/max values** -> **Code breaks silently if compiled on a platform with different type sizes** -> **Use `std::numeric_limits::max()`/`::min()` so bounds adapt to the actual type** * **Assuming `int` is always 4 bytes** -> **Portability bugs on embedded/older platforms where `int` may be 2 bytes** -> **Use `` fixed-width types (`int32_t`, `uint16_t`, etc.) when exact width is required** * **Comparing signed and unsigned values directly (e.g. `int` vs `size_t`)** -> **Compiler implicitly converts the signed value to unsigned, causing unexpected results for negative numbers** -> **Cast explicitly or keep both operands the same signedness** * **Misunderstanding two's complement** -> **Confusion when debugging bitwise operations or overflow behavior** -> **Remember: negative = invert all bits of the positive magnitude, then add 1** * **Printing `int8_t`/`uint8_t` via `std::cout`** -> **Output shows a character glyph instead of a number, since these are `char`-family aliases** -> **Cast to `int` before printing: `std::cout << static_cast(b)`**

📎 Repo Files

  • 05.OperationsOnData/5.2.BasicOperations/main.cpp
  • 05.OperationsOnData/5.3.PrecedenceAndAssociativity/main.cpp
  • 05.OperationsOnData/5.4.PrefixPostfixIncrementDecrement/main.cpp
  • 05.OperationsOnData/5.5.CompoundAssignmentOperators/main.cpp
  • 05.OperationsOnData/5.6.RelationalOperators/main.cpp
  • 05.OperationsOnData/5.7.LogicalOperators/main.cpp
  • 05.OperationsOnData/5.8.OutputFormatting/main.cpp
  • 05.OperationsOnData/5.9.NumericLimits/main.cpp
  • 05.OperationsOnData/5.10.MathFunctions/main.cpp
  • 05.OperationsOnData/5.11.WeirdIntegralTypes/main.cpp
  • 06.LiteralsAndConstants/6.2Literals/main.cpp
  • 06.LiteralsAndConstants/6.3Constants/main.cpp
  • 06.LiteralsAndConstants/6.4ConstantExpressions/main.cpp
  • 06.LiteralsAndConstants/6.5constinit/main.cpp
  • 07.ConversionsOverflowAndUnderflow/7.2ImplicitDataConversions/main.cpp
  • 07.ConversionsOverflowAndUnderflow/7.3ExplicitDataConversions/main.cpp
  • 07.ConversionsOverflowAndUnderflow/7.4OverflowAndUnderflow/main.cpp
  • 08.BitwiseOperators/8.2PrintingIntegersInBinary/main.cpp
  • 08.BitwiseOperators/8.3ShiftOperators/main.cpp
  • 08.BitwiseOperators/8.4LogicalBitwiseOperators/main.cpp
  • 08.BitwiseOperators/8.5CompoundBitwiseOperators/main.cpp
  • 08.BitwiseOperators/8.6Masks/main.cpp
  • 08.BitwiseOperators/8.7MasksExample/main.cpp
  • 08.BitwiseOperators/8.8PackingColorInformation/main.cpp
  • 09.VariableLifetimeAndScope/9.2VariableScope/main.cpp
  • 07.ConversionsOverflowAndUnderflow/main.cpp
  • 08.BitwiseOperators/main.cpp