Arithmetic operations form the backbone of computational programming in C. From simple addition to complex bit manipulations, understanding arithmetic in C is essential for everything from embedded systems to high-performance computing. This comprehensive guide explores every aspect of arithmetic operations, including integer and floating-point arithmetic, operator precedence, overflow handling, and advanced optimization techniques.
Basic Arithmetic Operators
C provides five fundamental arithmetic operators:
#include <stdio.h>
int main() {
int a = 10, b = 3;
// Addition (+)
int sum = a + b; // 13
// Subtraction (-)
int diff = a - b; // 7
// Multiplication (*)
int product = a * b; // 30
// Division (/)
int quotient = a / b; // 3 (integer division truncates)
// Modulus (%) - remainder
int remainder = a % b; // 1
printf("a = %d, b = %d\n", a, b);
printf("a + b = %d\n", sum);
printf("a - b = %d\n", diff);
printf("a * b = %d\n", product);
printf("a / b = %d\n", quotient);
printf("a %% b = %d\n", remainder);
return 0;
}
Integer Arithmetic
1. Integer Types and Ranges
#include <stdio.h>
#include <limits.h>
void integer_arithmetic_demo() {
// Signed integers
int signed_int = -100;
long long large_num = 9223372036854775807LL;
// Unsigned integers
unsigned int unsigned_int = 4000000000U;
unsigned long long huge_num = 18446744073709551615ULL;
// Integer promotion
char c1 = 100, c2 = 100;
int result = c1 + c2; // char promoted to int (200)
printf("Size of char: %zu\n", sizeof(char));
printf("Size of short: %zu\n", sizeof(short));
printf("Size of int: %zu\n", sizeof(int));
printf("Size of long: %zu\n", sizeof(long));
printf("Size of long long: %zu\n", sizeof(long long));
// Integer limits
printf("\nInteger limits:\n");
printf("INT_MIN: %d\n", INT_MIN);
printf("INT_MAX: %d\n", INT_MAX);
printf("UINT_MAX: %u\n", UINT_MAX);
}
2. Integer Division and Modulus
#include <stdio.h>
void division_modulus_demo() {
// Integer division truncates toward zero
printf("10 / 3 = %d\n", 10 / 3); // 3
printf("-10 / 3 = %d\n", -10 / 3); // -3 (truncates toward zero)
printf("10 / -3 = %d\n", 10 / -3); // -3
// Modulus sign follows dividend
printf("10 %% 3 = %d\n", 10 % 3); // 1
printf("-10 %% 3 = %d\n", -10 % 3); // -1
printf("10 %% -3 = %d\n", 10 % -3); // 1
// Relationship: a = (a / b) * b + (a % b)
int a = 10, b = 3;
printf("\nVerification: %d = (%d) * %d + %d\n",
a, a / b, b, a % b);
// Avoid division by zero
int denominator = 0;
if (denominator != 0) {
// int result = a / denominator;
} else {
printf("Cannot divide by zero!\n");
}
}
3. Overflow and Underflow
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
// Check for overflow before addition
bool safe_add(int a, int b, int *result) {
if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) {
return false; // Overflow would occur
}
*result = a + b;
return true;
}
// Check for overflow before multiplication
bool safe_mul(int a, int b, int *result) {
if (a > 0 && b > 0 && a > INT_MAX / b) return false;
if (a > 0 && b < 0 && b < INT_MIN / a) return false;
if (a < 0 && b > 0 && a < INT_MIN / b) return false;
if (a < 0 && b < 0 && a < INT_MAX / b) return false;
*result = a * b;
return true;
}
void overflow_demo() {
int max = INT_MAX;
int min = INT_MIN;
printf("INT_MAX = %d\n", max);
printf("INT_MAX + 1 = %d (overflow)\n", max + 1);
printf("INT_MIN - 1 = %d (underflow)\n", min - 1);
// Safe addition
int result;
if (safe_add(max, 10, &result)) {
printf("Sum: %d\n", result);
} else {
printf("Addition would overflow!\n");
}
// Unsigned overflow is well-defined (wraps around)
unsigned int u_max = UINT_MAX;
printf("\nUINT_MAX = %u\n", u_max);
printf("UINT_MAX + 1 = %u (wraps to 0)\n", u_max + 1);
}
Floating-Point Arithmetic
1. Basic Floating-Point Operations
#include <stdio.h>
#include <float.h>
#include <math.h>
void floating_point_demo() {
float f1 = 3.14159f;
double d1 = 3.141592653589793;
long double ld1 = 3.14159265358979323846L;
// Floating-point arithmetic
double a = 10.5, b = 3.2;
double sum = a + b; // 13.7
double diff = a - b; // 7.3
double product = a * b; // 33.6
double quotient = a / b; // 3.28125
printf("Floating-point sizes:\n");
printf("float: %zu bytes\n", sizeof(float));
printf("double: %zu bytes\n", sizeof(double));
printf("long double: %zu bytes\n", sizeof(long double));
printf("\nPrecision:\n");
printf("FLT_DIG: %d decimal digits\n", FLT_DIG);
printf("DBL_DIG: %d decimal digits\n", DBL_DIG);
printf("LDBL_DIG: %d decimal digits\n", LDBL_DIG);
}
2. Floating-Point Precision Issues
#include <stdio.h>
#include <float.h>
void precision_demo() {
// Floating-point comparison - DON'T use ==
double a = 0.1 + 0.2;
double b = 0.3;
printf("0.1 + 0.2 = %.20f\n", a);
printf("0.3 = %.20f\n", b);
if (a == b) {
printf("Equal!\n");
} else {
printf("Not equal due to floating-point precision!\n");
}
// Safe comparison with epsilon
double epsilon = DBL_EPSILON;
if (fabs(a - b) < epsilon) {
printf("Effectively equal (within epsilon)\n");
}
// Accumulation of rounding errors
double sum = 0.0;
for (int i = 0; i < 10; i++) {
sum += 0.1;
}
printf("\nSum of 10 * 0.1 = %.20f\n", sum);
printf("Expected: 1.0\n");
}
3. Special Floating-Point Values
#include <stdio.h>
#include <math.h>
void special_float_values() {
double inf = INFINITY;
double neg_inf = -INFINITY;
double nan_val = NAN;
printf("Infinity: %f\n", inf);
printf("-Infinity: %f\n", neg_inf);
printf("NaN: %f\n", nan_val);
// Check for special values
printf("\nisinf(inf): %d\n", isinf(inf));
printf("isinf(-inf): %d\n", isinf(neg_inf));
printf("isnan(NAN): %d\n", isnan(nan_val));
printf("isfinite(1.0): %d\n", isfinite(1.0));
// Operations with infinity
printf("\ninf + 1 = %f\n", inf + 1);
printf("inf * 2 = %f\n", inf * 2);
printf("inf - inf = %f\n", inf - inf); // NaN
// Operations with NaN
printf("\nNAN + 1 = %f\n", nan_val + 1);
printf("NAN * 2 = %f\n", nan_val * 2);
}
Compound Assignment Operators
#include <stdio.h>
void compound_operators_demo() {
int x = 10;
x += 5; // x = x + 5 (x = 15)
x -= 3; // x = x - 3 (x = 12)
x *= 2; // x = x * 2 (x = 24)
x /= 4; // x = x / 4 (x = 6)
x %= 4; // x = x % 4 (x = 2)
printf("x = %d\n", x);
// Floating-point compound assignments
double y = 10.5;
y += 2.5; // y = 13.0
y /= 2.0; // y = 6.5
printf("y = %.1f\n", y);
}
Increment and Decrement Operators
#include <stdio.h>
void increment_decrement_demo() {
int a = 5;
int b, c;
// Prefix increment/decrement
b = ++a; // a becomes 6, then b = 6
printf("After ++a: a = %d, b = %d\n", a, b);
a = 5;
// Postfix increment/decrement
c = a++; // c = 5, then a becomes 6
printf("After a++: a = %d, c = %d\n", a, c);
// Practical usage in loops
printf("\nLoop with postfix increment:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", i);
}
printf("\n\nLoop with prefix increment:\n");
for (int i = 0; i < 5; ++i) {
printf("%d ", i);
}
printf("\n");
}
Operator Precedence and Associativity
#include <stdio.h>
void precedence_demo() {
int a = 10, b = 5, c = 2;
// Multiplication has higher precedence than addition
int result1 = a + b * c; // 10 + (5 * 2) = 20
int result2 = (a + b) * c; // (10 + 5) * 2 = 30
printf("a + b * c = %d\n", result1);
printf("(a + b) * c = %d\n", result2);
// Left associativity for same precedence
int result3 = a - b - c; // (10 - 5) - 2 = 3
int result4 = a / b / c; // (10 / 5) / 2 = 1
printf("a - b - c = %d\n", result3);
printf("a / b / c = %d\n", result4);
// Operator precedence table (highest to lowest)
printf("\nOperator precedence (top = highest):\n");
printf("() [] -> .\n");
printf("++ -- + - ! ~ * & (type) sizeof\n");
printf("* / %%\n");
printf("+ -\n");
printf("<< >>\n");
printf("< <= > >=\n");
printf("== !=\n");
printf("&\n");
printf("^\n");
printf("|\n");
printf("&&\n");
printf("||\n");
printf("?: (ternary)\n");
printf("= += -= *= /= %%= &= ^= |= <<= >>=\n");
}
Type Conversions
1. Implicit Type Conversion
#include <stdio.h>
void implicit_conversion_demo() {
// Integer promotion
char c = 'A';
int i = c; // char promoted to int (65)
// Conversion between integer types
long l = 100;
int i2 = l; // May lose data (warning)
// Mixed arithmetic - lower type promoted
int a = 10;
double b = 3.14;
double result = a + b; // a promoted to double
printf("char to int: %d\n", i);
printf("int + double = double: %f\n", result);
// Signed to unsigned conversion
int negative = -1;
unsigned int positive = negative; // Becomes UINT_MAX
printf("-1 as unsigned: %u\n", positive);
}
2. Explicit Type Casting
#include <stdio.h>
void explicit_cast_demo() {
int a = 10, b = 3;
// Integer division
int int_result = a / b; // 3
// Float division via casting
float float_result = (float)a / b; // 3.33333
double double_result = (double)a / b;
printf("Integer division: %d\n", int_result);
printf("Float division: %f\n", float_result);
// Casting between pointer types (dangerous)
int value = 0x12345678;
char *byte_ptr = (char*)&value;
printf("Bytes of integer: %02x %02x %02x %02x\n",
byte_ptr[0], byte_ptr[1], byte_ptr[2], byte_ptr[3]);
// Safe casting with integers
unsigned int u_val = 4000000000U;
int signed_val = (int)u_val; // Implementation-defined
printf("Unsigned to signed: %u -> %d\n", u_val, signed_val);
}
Bitwise Arithmetic Operations
1. Bitwise Operators
#include <stdio.h>
#include <stdint.h>
void bitwise_demo() {
unsigned int a = 0b1010; // 10
unsigned int b = 0b1100; // 12
// Bitwise AND (&)
unsigned int and = a & b; // 0b1000 (8)
// Bitwise OR (|)
unsigned int or = a | b; // 0b1110 (14)
// Bitwise XOR (^)
unsigned int xor = a ^ b; // 0b0110 (6)
// Bitwise NOT (~)
unsigned int not = ~a; // 0b...11110101 (all bits flipped)
printf("a = %04b\n", a);
printf("b = %04b\n", b);
printf("a & b = %04b (%u)\n", and, and);
printf("a | b = %04b (%u)\n", or, or);
printf("a ^ b = %04b (%u)\n", xor, xor);
printf("~a = %08b (%u)\n", not, not);
}
2. Shift Operations
#include <stdio.h>
void shift_demo() {
unsigned int x = 0b0001; // 1
// Left shift (multiply by power of 2)
unsigned int left1 = x << 1; // 0b0010 (2)
unsigned int left2 = x << 3; // 0b1000 (8)
unsigned int left3 = x << 31; // 0b1000...0000
// Right shift (divide by power of 2)
unsigned int y = 0b1000; // 8
unsigned int right1 = y >> 1; // 0b0100 (4)
unsigned int right2 = y >> 3; // 0b0001 (1)
printf("x = %04b (%u)\n", x, x);
printf("x << 1 = %04b (%u)\n", left1, left1);
printf("x << 3 = %04b (%u)\n", left2, left2);
printf("\ny = %04b (%u)\n", y, y);
printf("y >> 1 = %04b (%u)\n", right1, right1);
printf("y >> 3 = %04b (%u)\n", right2, right2);
// Signed right shift (implementation-defined)
int signed_val = -8;
int signed_shift = signed_val >> 2;
printf("\n-8 >> 2 = %d (implementation-defined)\n", signed_shift);
}
3. Practical Bit Manipulation
#include <stdio.h>
#include <stdint.h>
// Check if bit n is set
#define BIT_IS_SET(value, n) (((value) >> (n)) & 1)
// Set bit n
#define BIT_SET(value, n) ((value) | (1U << (n)))
// Clear bit n
#define BIT_CLEAR(value, n) ((value) & ~(1U << (n)))
// Toggle bit n
#define BIT_TOGGLE(value, n) ((value) ^ (1U << (n)))
void bit_manipulation_demo() {
uint32_t flags = 0b00000000;
// Set bits
flags = BIT_SET(flags, 0); // Set bit 0
flags = BIT_SET(flags, 3); // Set bit 3
flags = BIT_SET(flags, 5); // Set bit 5
printf("Flags: %08b\n", flags);
printf("Bit 0: %d\n", BIT_IS_SET(flags, 0));
printf("Bit 1: %d\n", BIT_IS_SET(flags, 1));
printf("Bit 3: %d\n", BIT_IS_SET(flags, 3));
// Clear bit
flags = BIT_CLEAR(flags, 3);
printf("\nAfter clearing bit 3: %08b\n", flags);
// Toggle bit
flags = BIT_TOGGLE(flags, 0);
printf("After toggling bit 0: %08b\n", flags);
// Extract a bitfield
uint32_t value = 0b10101010;
uint32_t lower_4 = value & 0x0F; // Extract lower 4 bits
uint32_t upper_4 = (value >> 4) & 0x0F;
printf("\nValue: %08b\n", value);
printf("Lower 4 bits: %04b\n", lower_4);
printf("Upper 4 bits: %04b\n", upper_4);
}
Mathematical Functions
#include <stdio.h>
#include <math.h>
void math_functions_demo() {
double x = 2.5;
// Basic math functions
printf("sqrt(%.1f) = %.6f\n", x, sqrt(x));
printf("pow(%.1f, 3) = %.1f\n", x, pow(x, 3));
printf("exp(%.1f) = %.6f\n", x, exp(x));
printf("log(%.1f) = %.6f\n", x, log(x));
printf("log10(%.1f) = %.6f\n", x, log10(x));
// Trigonometric functions
double angle = 30.0 * M_PI / 180.0; // Convert to radians
printf("\nsin(30°) = %.6f\n", sin(angle));
printf("cos(30°) = %.6f\n", cos(angle));
printf("tan(30°) = %.6f\n", tan(angle));
// Rounding functions
double val = 3.14159;
printf("\nfloor(%.6f) = %.0f\n", val, floor(val));
printf("ceil(%.6f) = %.0f\n", val, ceil(val));
printf("round(%.6f) = %.0f\n", val, round(val));
printf("trunc(%.6f) = %.0f\n", val, trunc(val));
// Absolute value
int negative = -42;
double neg_double = -3.14;
printf("\nabs(%d) = %d\n", negative, abs(negative));
printf("fabs(%.2f) = %.2f\n", neg_double, fabs(neg_double));
}
Advanced Arithmetic Techniques
1. Fixed-Point Arithmetic
#include <stdio.h>
#include <stdint.h>
// Fixed-point representation: 16.16 (16 integer bits, 16 fractional bits)
typedef int32_t fixed16_t;
#define FIXED_SHIFT 16
#define FIXED_SCALE (1 << FIXED_SHIFT)
// Convert integer to fixed-point
fixed16_t int_to_fixed(int x) {
return x << FIXED_SHIFT;
}
// Convert fixed-point to integer
int fixed_to_int(fixed16_t x) {
return x >> FIXED_SHIFT;
}
// Convert float to fixed-point
fixed16_t float_to_fixed(float x) {
return (fixed16_t)(x * FIXED_SCALE);
}
// Convert fixed-point to float
float fixed_to_float(fixed16_t x) {
return (float)x / FIXED_SCALE;
}
// Fixed-point multiplication
fixed16_t fixed_mul(fixed16_t a, fixed16_t b) {
return (int64_t)a * b >> FIXED_SHIFT;
}
// Fixed-point division
fixed16_t fixed_div(fixed16_t a, fixed16_t b) {
return ((int64_t)a << FIXED_SHIFT) / b;
}
void fixed_point_demo() {
fixed16_t a = float_to_fixed(3.5f);
fixed16_t b = float_to_fixed(2.0f);
fixed16_t sum = a + b;
fixed16_t product = fixed_mul(a, b);
fixed16_t quotient = fixed_div(a, b);
printf("Fixed-point arithmetic (16.16 format):\n");
printf("a = %.2f\n", fixed_to_float(a));
printf("b = %.2f\n", fixed_to_float(b));
printf("a + b = %.2f\n", fixed_to_float(sum));
printf("a * b = %.2f\n", fixed_to_float(product));
printf("a / b = %.2f\n", fixed_to_float(quotient));
}
2. Saturating Arithmetic
#include <stdio.h>
#include <limits.h>
int saturating_add(int a, int b) {
int result = a + b;
// Check for overflow
if ((a > 0 && b > 0 && result < 0) || (a < 0 && b < 0 && result > 0)) {
return (a > 0) ? INT_MAX : INT_MIN;
}
return result;
}
int saturating_sub(int a, int b) {
return saturating_add(a, -b);
}
unsigned int saturating_add_unsigned(unsigned int a, unsigned int b) {
unsigned int result = a + b;
if (result < a || result < b) {
return UINT_MAX; // Overflow
}
return result;
}
void saturating_demo() {
int max = INT_MAX;
int min = INT_MIN;
printf("Saturating addition:\n");
printf("INT_MAX + 1 = %d (saturates to %d)\n",
saturating_add(max, 1), max);
printf("INT_MIN - 1 = %d (saturates to %d)\n",
saturating_sub(min, 1), min);
unsigned int u_max = UINT_MAX;
printf("\nUnsigned saturating addition:\n");
printf("UINT_MAX + 10 = %u (saturates to %u)\n",
saturating_add_unsigned(u_max, 10), u_max);
}
3. Branchless Arithmetic
#include <stdio.h>
#include <stdint.h>
// Branchless absolute value
int branchless_abs(int x) {
int mask = x >> (sizeof(int) * 8 - 1);
return (x ^ mask) - mask;
}
// Branchless min/max
int branchless_min(int a, int b) {
return a ^ ((a ^ b) & -(a > b));
}
int branchless_max(int a, int b) {
return a ^ ((a ^ b) & -(a < b));
}
// Branchless clamp
int branchless_clamp(int x, int low, int high) {
return branchless_min(branchless_max(x, low), high);
}
// Branchless absolute difference
int branchless_abs_diff(int a, int b) {
int diff = a - b;
int mask = diff >> (sizeof(int) * 8 - 1);
return (diff ^ mask) - mask;
}
void branchless_demo() {
int x = -42;
int a = 10, b = 20;
printf("Branchless abs(%d) = %d\n", x, branchless_abs(x));
printf("Branchless min(%d, %d) = %d\n", a, b, branchless_min(a, b));
printf("Branchless max(%d, %d) = %d\n", a, b, branchless_max(a, b));
printf("Branchless clamp(15, 0, 10) = %d\n", branchless_clamp(15, 0, 10));
printf("Branchless abs diff(%d, %d) = %d\n", a, b, branchless_abs_diff(a, b));
}
4. SIMD-Like Arithmetic with Vectorization
#include <stdio.h>
#include <stdint.h>
// Manual vectorization of 4 operations at once
typedef struct {
int x, y, z, w;
} Vec4;
Vec4 vec4_add(Vec4 a, Vec4 b) {
Vec4 result;
result.x = a.x + b.x;
result.y = a.y + b.y;
result.z = a.z + b.z;
result.w = a.w + b.w;
return result;
}
Vec4 vec4_mul(Vec4 a, Vec4 b) {
Vec4 result;
result.x = a.x * b.x;
result.y = a.y * b.y;
result.z = a.z * b.z;
result.w = a.w * b.w;
return result;
}
void vec4_print(Vec4 v) {
printf("(%d, %d, %d, %d)\n", v.x, v.y, v.z, v.w);
}
void vectorization_demo() {
Vec4 a = {1, 2, 3, 4};
Vec4 b = {5, 6, 7, 8};
printf("Vector addition: ");
vec4_print(vec4_add(a, b));
printf("Vector multiplication: ");
vec4_print(vec4_mul(a, b));
}
Performance Considerations
#include <stdio.h>
#include <time.h>
// Measure operation performance
double measure_time(void (*func)(void)) {
clock_t start = clock();
func();
return (double)(clock() - start) / CLOCKS_PER_SEC;
}
void test_multiplication() {
volatile int result = 0;
for (int i = 0; i < 100000000; i++) {
result = i * 2;
}
}
void test_shift() {
volatile int result = 0;
for (int i = 0; i < 100000000; i++) {
result = i << 1; // Multiply by 2 using shift
}
}
void performance_comparison() {
double mul_time = measure_time(test_multiplication);
double shift_time = measure_time(test_shift);
printf("Multiplication time: %.3f seconds\n", mul_time);
printf("Shift time: %.3f seconds\n", shift_time);
printf("Shift is %.2fx faster\n", mul_time / shift_time);
}
// Compiler optimization hints
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
int optimized_division(int a, int b) {
if (unlikely(b == 0)) {
return 0; // Division by zero is rare
}
return a / b;
}
Common Pitfalls and Best Practices
#include <stdio.h>
void common_pitfalls() {
// PITFALL 1: Integer division truncation
double ratio = 5 / 2; // ratio = 2.0, not 2.5!
printf("5 / 2 = %.1f (should be 2.5)\n", ratio);
// FIX: Use floating-point
ratio = 5.0 / 2.0; // ratio = 2.5
printf("5.0 / 2.0 = %.1f\n", ratio);
// PITFALL 2: Modulus with negative numbers
int mod = -5 % 2; // -1 (implementation-defined in C89)
printf("-5 %% 2 = %d\n", mod);
// PITFALL 3: Overflow in intermediate calculations
int a = 1000000;
int b = 1000000;
long long product = (long long)a * b; // Cast before multiplication
printf("1,000,000 * 1,000,000 = %lld\n", product);
// PITFALL 4: Floating-point comparison
double x = 0.1;
double y = 0.2;
if (x + y == 0.3) { // Usually false
printf("Equal\n");
} else {
printf("Not equal\n");
}
// FIX: Use epsilon
double epsilon = 1e-9;
if (fabs((x + y) - 0.3) < epsilon) {
printf("Effectively equal\n");
}
}
Summary Table
| Operation | Integer | Floating-Point | Notes |
|---|---|---|---|
| Addition (+) | Fast, check overflow | Fast, watch precision | |
| Subtraction (-) | Fast, check underflow | Fast, watch precision | |
| Multiplication (*) | Moderate, watch overflow | Moderate, watch precision | |
| Division (/) | Slow, integer truncates | Slow, watch division by zero | |
| Modulus (%) | Slow, sign of dividend | Not available | Integer only |
| Bitwise (&, |, ^, ~) | Very fast | Not available | Integer only |
| Shifts (<<, >>) | Very fast | Not available | Multiply/divide by powers of 2 |
| Increment (++) | Very fast | Very fast | Use prefix for performance |
Conclusion
Arithmetic operations in C form the foundation of all computational work. Understanding the nuances of integer vs. floating-point arithmetic, operator precedence, overflow handling, and bitwise operations is essential for writing correct and efficient C code.
Key takeaways:
- Know your types: Understand the ranges and behaviors of different integer and floating-point types
- Watch for overflow: Always check for overflow in critical calculations
- Be precise: Understand floating-point limitations and use appropriate comparison methods
- Use bitwise operations: Leverage bitwise operations for flags, masks, and performance-critical code
- Consider performance: Use shifts for powers of two, avoid unnecessary conversions
- Test edge cases: Always test with boundary values (zero, negative, maximum, minimum)
Mastering arithmetic operations in C is not just about knowing the operators—it's about understanding how they interact with data types, memory, and the underlying hardware to create reliable and efficient programs.