Back to Blog
C#

C# Arithmetic Operators: Syntax, Behavior, and Pitfalls

c# arithmetic operators: Learn how C# arithmetic operators behave in practice, including integer division, overflow, floating-point precision, and operator precedence.

C#arithmetic operatorsinteger overflowoperator precedencefloating-point arithmetic
Illustration of C# arithmetic operators with mathematical symbols and code context.

C# arithmetic operators are the foundation of numeric computation in the language, but their behavior is not always intuitive. Integer division truncates, overflow can silently wrap, and floating-point arithmetic introduces precision limits. This article examines the syntax and runtime behavior of these operators so you can write calculations that behave predictably.

The Core Arithmetic Operators

C# provides five binary arithmetic operators: addition (+), subtraction (-), multiplication (*), division (/), and remainder (%). It also includes unary plus (+), unary minus (-), increment (++), and decrement (--). These operators work on numeric types including int, long, float, double, decimal, and their nullable counterparts.

int a = 10; int b = 3; Console.WriteLine(a + b); // 13 Console.WriteLine(a - b); // 7 Console.WriteLine(a * b); // 30 Console.WriteLine(a / b); // 3 Console.WriteLine(a % b); // 1

The division and remainder operators deserve special attention because their behavior depends on the operand types and the sign of the values.

Integer Division and Remainder Behavior

When both operands are integers, the division operator performs integer division, which truncates the result toward zero. This means that 10 / 3 yields 3, not 3.333. The remainder operator % returns the remainder of the division, which satisfies the relationship a = (a / b) * b + a % b for integer operands.

int positive = 10 / 3; // 3 int negative = -10 / 3; // -3 (truncated toward zero) int remainder = -10 % 3; // -1 (sign follows the dividend)

The sign of the remainder follows the sign of the dividend in C#. This differs from some languages where the remainder follows the divisor. If you need a modulo operation that always returns a non-negative result, you must adjust the value manually.

int mod = (value % modulus + modulus) % modulus;

This pattern is common when working with array indices or cyclic behavior.

Overflow and the Checked Context

Arithmetic operations on integral types can overflow. By default, C# performs unchecked arithmetic, meaning that overflow silently wraps around using two's complement representation. For example, adding 1 to int.MaxValue produces int.MinValue without an exception.

int max = int.MaxValue; int wrapped = max + 1; // -2147483648

To detect overflow, you can use the checked keyword or the /checked compiler option. When an overflow occurs inside a checked block, a System.OverflowException is thrown.

try { checked { int result = int.MaxValue + 1; } } catch (OverflowException ex) { Console.WriteLine("Overflow detected"); }

The unchecked keyword explicitly disables overflow checking, which is useful when wrapping behavior is intended, such as in hash calculations or checksum algorithms. The decimal type, by contrast, always throws on overflow and is designed for financial calculations where exactness matters.

Floating-Point Arithmetic and Precision

Floating-point types (float and double) follow the IEEE 754 standard. Division by zero does not throw an exception; instead, it produces Infinity, -Infinity, or NaN depending on the operands. Operations that produce undefined results, such as 0.0 / 0.0, yield NaN.

double positiveInfinity = 1.0 / 0.0; // Infinity double negativeInfinity = -1.0 / 0.0; // -Infinity double notANumber = 0.0 / 0.0; // NaN

Floating-point arithmetic is also subject to rounding errors because not all decimal fractions can be represented exactly in binary. Comparing floating-point values for equality can lead to surprising results. Instead, compare against a tolerance or use decimal when exact decimal representation is required.

double x = 0.1 + 0.2; if (Math.Abs(x - 0.3) < 1e-10) { // Treat as equal }

The decimal type uses a base-10 representation and is suitable for monetary calculations, but it is slower than double and has a smaller range.

Operator Precedence and Associativity

C# defines a strict precedence order for arithmetic operators. Multiplication, division, and remainder have higher precedence than addition and subtraction. Unary operators have the highest precedence. When operators have the same precedence, associativity determines the evaluation order; arithmetic operators are left-associative, meaning they are evaluated from left to right.

Operator(s)Precedence (higher to lower)
++, --, unary +, unary -Highest
*, /, %Medium
+, -Lowest

For example, a + b * c is evaluated as a + (b * c). Using parentheses is recommended when the intended order is not obvious, as it improves readability and reduces the risk of errors.

Compound Assignment and Increment/Decrement

Compound assignment operators combine an arithmetic operation with assignment: +=, -=, *=, /=, and %=. These operators are equivalent to performing the operation and then assigning the result, but they evaluate the left-hand side only once.

int count = 10; count += 5; // count = 15 count *= 2; // count = 30

The increment (++) and decrement (--) operators have both prefix and postfix forms. The prefix form increments the variable and returns the new value; the postfix form returns the original value and then increments.

int i = 5; int a = i++; // a = 5, i = 6 int b = ++i; // i = 7, b = 7

These operators are commonly used in loops and indexers, but they can be a source of subtle bugs when used inside larger expressions. Prefer standalone statements for clarity.

Practical Considerations for Arithmetic in Production Code

When writing arithmetic code that will run in production, consider the data types and the range of values you expect. For integral types, decide whether overflow should be checked or allowed to wrap. Use checked in debug builds or when correctness is critical, but be aware that it adds a small runtime cost.

For financial calculations, use decimal to avoid floating-point rounding errors. For scientific or performance-sensitive code, double is usually appropriate, but avoid direct equality comparisons.

Performance-wise, integer arithmetic is generally faster than floating-point on modern CPUs, and decimal is the slowest of the three. However, the choice of type should be driven by correctness requirements first, and performance only when profiling indicates a bottleneck.

Also be mindful of the behavior of arithmetic operators with nullable numeric types. If either operand is null, the result is null for most operators, which can propagate null values unexpectedly.

int? a = 10; int? b = null; int? result = a + b; // null

This behavior is consistent with the lifted operator pattern in C# and can be useful, but it requires careful handling when nullability is not desired.

c# arithmetic operators: Practical Usage and Code Examples | RYUSLOG DEV