BrandGhost
Arithmetic Operators in C#: Numeric Types, Division, and Overflow
All articles

Arithmetic Operators in C#: Numeric Types, Division, and Overflow

Learn arithmetic operators in C# through numeric promotion, integer division, remainder, zero division, and checked overflow with clear practical examples.

Arithmetic operators in C# depend on the numeric types at the operation, not just the number a reader imagines or the type receiving the final result. Integer division, numeric promotion, remainder, and overflow all follow those typed rules.

This matters in ordinary code. A count divided by another count might deliberately produce a whole number. A ratio might need a fractional result. A calculation that fits for common inputs might exceed an integral range at its boundary. The same symbols can serve these jobs under different rules.

This article explains arithmetic operators for built-in integral, binary floating-point, and decimal operands. It excludes financial modeling, generic math, custom operator implementation, and another implicit-conversion tutorial. The baseline is .NET 10 with C# 14, as documented in Microsoft's C# 14 release overview. The examples retain older-compatible syntax rather than introducing unnecessary new declarations. Their results are documentation-derived and have not been compiled, executed, or benchmarked for this article.

Arithmetic Operators Start with Operand Types

Addition, subtraction, multiplication, division, and remainder act on values under the applicable numeric rules. Before calculating an expected result, identify the compile-time types of both operands.

Microsoft's arithmetic operator documentation describes numeric promotion. Smaller integral operands such as byte and short generally become int for binary arithmetic. The type storing a value is not necessarily the type of the arithmetic result.

using System;

byte completed = 120;
byte pending = 20;
var total = completed + pending;

Console.WriteLine(total);
Console.WriteLine(total.GetType().Name);

long largeTotal = (long)int.MaxValue + 1;
Console.WriteLine(largeTotal);

The byte addition produces an int result with value one hundred forty. The long calculation converts an operand before addition, allowing the operation itself to use long arithmetic. It does not first overflow an int and then widen the already-damaged result.

These are two related lessons. Promotion can widen an operation implicitly according to the language rules. An explicit conversion can also establish a desired operand type. In both cases, inspect the expression at the operator rather than inferring its type from nearby declarations.

The automatic casting guide explains conversions more broadly. Here arithmetic operators own the narrower question: which numeric operation happens after the applicable conversions have been established?

Integral Promotion Is Not Unlimited Widening

It is tempting to describe mixed arithmetic as "C# chooses a type large enough." That is too broad. Numeric promotion follows specified conversion rules and does not promise to find a representation for every combination or every possible result.

Adding two int variables uses int arithmetic even when their mathematical sum could require a long. The compiler does not examine the runtime sum and dynamically choose a wider result type.

Some mixed signed and unsigned combinations require particular promotion rules or explicit conversions. Likewise, decimal does not mix implicitly with float or double in ordinary arithmetic. The language rejects combinations that lack the required implicit conversions rather than silently choosing a universal numeric type.

Arithmetic operators therefore need both a type-level explanation and a range-level explanation. A legal operation can still produce an out-of-range result for its selected type. A mathematical result being representable in some other type does not automatically make that other type the operation's result.

This distinction keeps the model grounded. Promotion determines the operation's type; overflow rules describe what happens when an operation exceeds that type's range. Neither should be substituted for the other.

Integer Division Rounds Toward Zero

When division uses integral operands under an integral division operator, the result is an integer quotient. A fractional mathematical quotient is rounded toward zero, not toward negative infinity.

using System;

int numerator = 7;
int denominator = 2;

Console.WriteLine(numerator / denominator);
Console.WriteLine(-numerator / denominator);

double convertedAfter = numerator / denominator;
double convertedBefore = (double)numerator / denominator;
decimal decimalQuotient = (decimal)numerator / denominator;

Console.WriteLine(convertedAfter);
Console.WriteLine(convertedBefore);
Console.WriteLine(decimalQuotient);

The integral quotients are three and negative three. The value convertedAfter is numerically three, because conversion occurs after integer division. The other two quotients are numerically three and a half because a non-integral operand type is established before division.

Output punctuation and decimal separators can depend on the current culture. The example describes numeric values rather than claiming a particular formatted output in every environment.

Arithmetic operators do not use the destination type as a request to restore a discarded fraction. If the fraction matters, select the operation's operand types before the division. Converting the result afterward can widen its representation but cannot reconstruct information the division omitted.

Integer division is not inherently a bug. Counts of full batches, complete pages, or whole units can require a whole-number result. The important question is whether the chosen numeric semantics match the requirement.

Remainder Is Not Always Mathematical Modulo

The remainder operator returns the remainder associated with division. For nonzero integral results, the sign follows the left operand, which means a negative dividend can produce a negative remainder.

For example, negative seven divided by two has quotient negative three and remainder negative one. A common mathematical modulo convention instead requires a nonnegative result for a positive modulus. Those are different contracts.

Do not describe percent as a universal "wrap into range" operation without considering negative inputs. A calculation that works for a positive index may produce a negative result when the source value is negative.

If an application needs a normalized nonnegative position, define that requirement explicitly and reason about the normalization expression's own overflow and zero-divisor boundaries. The remainder symbol alone does not establish that policy.

Floating-point remainder has its own documented numeric behavior. It is not an invitation to ignore representation errors or assume all mathematical identities remain exact in finite precision.

For arithmetic operators, the practical takeaway is to say "remainder" unless the application has deliberately established a modulo convention. Accurate terminology avoids a hidden policy change at the negative boundary.

Division by Zero Depends on the Numeric Family

Integral division or remainder by a zero divisor throws DivideByZeroException. Decimal division by zero also throws. Binary floating-point division follows different rules and can produce infinity or NaN rather than that exception.

The difference matters because a generic instruction such as "division by zero always throws in C#" is false. It also means catching one exception does not establish a complete policy for every numeric family.

With double, a finite nonzero numerator divided by zero can produce an infinity whose sign follows the operands. Zero divided by zero produces NaN. Floating-point values can also become non-finite through other operations, so a zero check is not a complete numerical validity policy.

Decimal arithmetic does not have the same infinity and NaN values. Its finite range and operation rules can result in exceptions where binary floating-point arithmetic produces a special value.

Arithmetic operators should be explained with the operand types visible. If an input might be zero, the application's response should follow the numeric family and domain requirement rather than a universal catch-all explanation.

This article does not prescribe a fallback number when division is invalid. Returning zero can silently misrepresent a failed calculation. The error or missing-result policy is separate from understanding the operator.

Checked Overflow Is Textually Scoped

For applicable built-in integral arithmetic, a checked context reports overflow with OverflowException at runtime. An unchecked context retains the truncated representable result. Constants have additional compile-time rules.

using System;

int value = int.MaxValue;
Console.WriteLine(unchecked(value + 1));

try
{
    Console.WriteLine(checked(value + 1));
}
catch (OverflowException)
{
    Console.WriteLine("The checked addition exceeded Int32.");
}

Console.WriteLine(checked(AddUnchecked(value)));

int AddUnchecked(int input)
{
    return unchecked(input + 1);
}

The first addition wraps to the minimum int value. The second addition throws and the example explicitly reports that expected exception. The third call returns the same wrapped value as the first because the called method's arithmetic is explicitly unchecked.

Microsoft documents checked and unchecked as affecting operations textually inside their scope. A caller's checked expression does not reach into an arbitrary called body and rewrite that body's context.

The explicit unchecked inside AddUnchecked removes dependence on the project's default overflow setting for that addition. This makes the example's boundary inspectable. Without explicit context, relevant compiler settings would need to be considered.

Arithmetic operators inside a helper deserve inspection when overflow policy matters. Wrapping a call in checked is not a whole-call-tree safety guarantee, and a user-defined checked operator is not a guarantee that its author throws on every overflow.

C# 11 introduced checked user-defined numeric and explicit conversion operators, not C# 14. The checked modifier on an operator declaration does not change the overflow context inside its body. This released capability is relevant when reading custom operands, but the examples here keep built-in arithmetic rather than implementing a numeric wrapper.

Overflow Has Important Qualifications

Checked affects applicable integral arithmetic and conversions, not every numeric operation. Binary floating-point calculations can produce infinity or NaN without an integral overflow exception. Decimal arithmetic can throw on overflow regardless of an unchecked context.

Division also has a special signed boundary for int and long: dividing int.MinValue or long.MinValue by negative one asks for a positive value outside the operation's result type. On .NET, this throws an ArithmeticException even in an unchecked context. Smaller signed operands are promoted before division; short.MinValue divided by negative one, for example, fits in the int result type. Do not infer a portable wraparound promise from unchecked.

Constant expressions are another distinction. Integral constant calculations are checked by default unless explicitly placed in unchecked context. A compile-time diagnostic for an overflowing constant and a runtime exception for variable arithmetic are different observations.

Arithmetic operators therefore need an appropriately narrow explanation. "Checked prevents overflow" suggests too much. It detects out-of-range behavior for applicable operations in its textual scope; it does not change the mathematical range of int or establish every numeric policy.

Likewise, unchecked is not a generic "ignore errors" option. Division by zero, decimal behavior, and method exceptions remain governed by their own rules. Choosing unchecked should be an intentional numeric policy, not a way to suppress arbitrary failures.

Floating-Point and Decimal Represent Different Tradeoffs

Float and double use binary floating-point representation. Many decimal fractions are not exactly representable, so calculations can show round-off differences from an ideal real-number result.

Decimal represents decimal-scaled values with its own finite precision and range. It can represent many common decimal fractions exactly, but it is not unlimited-precision arithmetic and does not make every calculation exact.

The tradeoff is not "one numeric family is always correct." The appropriate representation depends on the quantities, accepted error, range, and required operations. Arithmetic operators follow the chosen representation rather than selecting a domain policy automatically.

This article makes no throughput, allocation, or elapsed-time ranking between decimal and double. Such a claim would require a specified workload and measurements. Documentation about representation is sufficient to explain semantics, not a benchmark.

The existing implicit conversion guide is useful when a domain wrapper participates in calculations. A conversion to a built-in numeric type establishes that type's arithmetic, but the wrapper's own operators may have separate contracts.

The conversion design tradeoffs remain relevant to hidden work. Convenient arithmetic-looking syntax should not be treated as proof that a custom type behaves like a built-in number.

Compound Assignment Adds Another Conversion Surface

Compound assignment can behave differently from spelling an ordinary binary operation and assignment separately. For predefined operators, its rules can include a conversion back to the left operand's type, and the left operand is evaluated only once.

A byte increment through compound assignment is therefore not evidence that all byte addition produces byte results. Ordinary binary addition still follows numeric promotion. The surrounding assignment construct supplies additional rules.

This is another reason to distinguish an operator result from the final stored value. Arithmetic operators produce a result under selected numeric rules; assignment can then impose conversion and storage behavior.

The built-in explanation is not a universal rewrite rule for custom types: C# 14 introduced instance compound-assignment operators. When the left operand is a variable and an applicable instance operator exists, that operator takes precedence over the corresponding static binary operator. Otherwise, the static-operator path applies. An in-place update can therefore differ from producing a new value and assigning it back.

The same C# 14 reference permits instance increment and decrement operators. An applicable instance operator is preferred for a variable in a prefix expression or a discarded postfix expression such as x++; it is ignored when the postfix value is used, as in y = x++. These dispatch distinctions concern custom operands, not a change to built-in integer increment.

Arithmetic Is Not Bitwise Manipulation

Bitwise and shift operations can appear in code that also uses numbers, but their contracts are not ordinary arithmetic overflow rules. A shift constructs a bit pattern under its own count and operand rules.

The existing flags and bitwise operations article owns that topic. An enum mask's sign bit should not be explained as an addition overflow merely because its numeric display is negative.

Keeping arithmetic operators separate from bitwise manipulation also makes examples easier to explain. A remainder is not a mask, and shifting is not universally a substitute for multiplication with identical behavior.

Review Numeric Boundaries Deliberately

Start with operand types, then identify promotion, result type, and overflow context. After that, ask which inputs distinguish plausible interpretations: a non-integral quotient, a negative dividend, a zero divisor, and a value near the selected type's boundary.

A calculation that works only for small positive values has not explained its full numeric contract. Conversely, understanding boundary behavior does not require turning every example into a financial or scientific modeling guide.

Arithmetic operators become easier to review when the domain policy and language rule are stated separately. Whole-number division may be the intended rule. Fractional division may be the intended rule. The same expression cannot be assumed to satisfy both simply because its result is assigned to a broad type.

Frequently Asked Questions

The numeric family changes the relevant boundary behavior.

Numeric family Division by zero
Integral Throws DivideByZeroException
Decimal Throws DivideByZeroException
Binary floating-point Can produce infinity or NaN

These categories describe numeric behavior, not a universal preference for one representation.

Why does byte addition produce int?

Binary arithmetic on smaller integral operands generally applies numeric promotion to int. The storage type of each operand does not necessarily remain the result type. Inspect the operation before considering any conversion used to store its result.

Does assigning integer division to double preserve the fraction?

No. Integral division happens first and rounds toward zero. Converting the quotient to double afterward does not recover the fraction. Convert an operand before division when a floating-point quotient is intended.

Is remainder always nonnegative?

No. A nonzero integral remainder follows the dividend's sign. A negative input can therefore produce a negative remainder even with a positive divisor. A nonnegative modulo policy requires additional deliberate reasoning.

Does dividing by zero always throw?

Not across all numeric types. Integral and decimal division by zero throw. Binary floating-point division can produce infinity or NaN. The application's invalid-result policy must account for the numeric family in use.

Does checked change arithmetic inside another method?

No, not merely because that method is called from checked syntax. The checking context is textual. Operations in the called body follow the context and settings applicable there, as the explicitly unchecked helper demonstrates.

Is decimal exact for every calculation?

No. Decimal has finite precision and range. It represents many decimal fractions exactly but can still round results or overflow. Choosing it is a representation decision, not a guarantee of unlimited mathematical precision.

Arithmetic Operators Follow a Typed Contract

Arithmetic operators are predictable when the operand types, promotion rules, division semantics, and overflow context are explicit. The surprising cases usually arise from answering one of those questions with evidence about another.

Read the operation where it happens. A wider destination is not a wider calculation, a remainder is not automatically normalized modulo, and checked is not a caller-controlled policy for every nested method. Those distinctions provide a dependable foundation for everyday numeric code.

Get the Dev Leader Newsletter

C#, .NET, and software engineering content every Saturday.

Subscribe to the newsletter
An error has occurred. This application may no longer respond until reloaded. Reload