Clarify the observable contract
Compute a nonnegative quantity times a nonnegative price expressed in integer cents. The result is a checked long, zero quantity is valid and an unrepresentable total is rejected with OverflowException. The task avoids floating-point currency rounding by defining integer units, but taxes, fractional pricing and currency conversion remain separate business rules.
State whether invalid operands, unrepresentable totals and zero-value orders are different outcomes. Here negatives are argument errors, arithmetic overflow is exceptional and zero quantity returns zero. Integer cents make the unit precise, but they do not establish rules for currencies whose minor units or rounding requirements differ.
Walk through the verified program
Three units at one hundred twenty-five cents produce three hundred seventy-five. Two units at int.MaxValue cents produce 4294967294 because the long price operand makes multiplication occur in the wider type. Two units at long.MaxValue overflow that type, and the harness records explicit rejection rather than printing a wrapped negative amount.
The wide-product fixture is intentionally larger than int.MaxValue but still valid as long. It catches code that multiplies two ints first and casts afterward. The overflow fixture then exceeds long itself, demonstrating that widening and checked arithmetic solve different parts of the numeric contract.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
Console.WriteLine("total:"+Bill(3,125));
Console.WriteLine("large:"+Bill(2,int.MaxValue));
bool rejected=false; try { Bill(2,long.MaxValue); } catch(OverflowException) { rejected=true; }
Console.WriteLine("overflow rejected:"+rejected.ToString().ToLowerInvariant());
static long Bill(int quantity,long unitCents) { if(quantity<0 || unitCents<0) throw new ArgumentOutOfRangeException(); return checked(quantity*unitCents); }
Account for time, storage and allocations
The arithmetic and validation are O(1) time and auxiliary storage for fixed-width inputs. Widening only after an int multiplication would be too late to repair overflow. Checked syntax must cover the actual multiplication inside the method; a checked caller context does not retroactively change unchecked arithmetic in another method.
A fixed-width multiplication has constant algorithmic cost, while parsing arbitrary-length numeric input or using arbitrary-precision arithmetic would have another model. The sample neither parses money text nor formats a currency. Keeping calculation and representation separate makes both the overflow proof and deterministic output easier to inspect.
Review assumptions and likely defects
Using decimal does not remove the need for domain units, bounds or overflow handling. Swallowing overflow and returning zero can turn invalid input into an apparently free order. An exception also needs a consistent API boundary so callers can distinguish invalid quantity from an unrepresentable accepted calculation.
Checked behavior is lexical for the operation actually written in the method. A caller wrapping Bill in checked syntax cannot repair an unchecked implementation inside Bill. Likewise, catching OverflowException should produce a named failure at the API boundary, not an undocumented default that looks like a legitimate bill.
Practice one: discriminate edge cases
Test zero quantity, zero price, a negative operand and the largest representable successful product. Hint: derive the safe boundary by division before multiplication, while retaining checked arithmetic as the final guard.
Use quantity two and derive a largest safe unit price as long.MaxValue divided by two, then test the next price as failure. Keep validation tests separate from overflow tests because negative input should fail before multiplication. This gives a concrete boundary pair rather than relying only on small positive examples.
Practice two: defend an extension
Design a batch billing operation that must either publish all line totals or none. Specify rounding units, accumulation overflow and error reporting. Explain why local checked arithmetic alone does not provide transactional storage or external-payment idempotency.
A batch can calculate every proposed total and checked aggregate into a private result before publishing it. That gives an all-or-error in-memory calculation boundary, while a real persistent commit still needs its own transaction. Define rounding once at the chosen unit conversion rather than applying inconsistent rounding independently to partial totals.
Evaluate correctness and interview reasoning
A rigorous numeric answer identifies units, operand ranges, the type used during multiplication and the failure contract. It demonstrates both a valid widened product and rejection beyond the widened range. The associated checks should target zero, invalid signs and adjacent overflow boundaries. A senior review separates arithmetic safety from storage atomicity and payment replay behavior; neither follows from the checked keyword. This lesson is deliberately small enough that every printed amount can be derived by hand, making a wrapped value or a too-late cast easy to distinguish from a correct implementation. Review the exact evaluation types in the expression tree: an explicit cast applied after multiplication cannot restore bits already discarded by a narrower operation.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
Console.WriteLine("total:"+Bill(3,125));
Console.WriteLine("large:"+Bill(2,int.MaxValue));
bool rejected=false; try { Bill(2,long.MaxValue); } catch(OverflowException) { rejected=true; }
Console.WriteLine("overflow rejected:"+rejected.ToString().ToLowerInvariant());
static long Bill(int quantity,long unitCents) { if(quantity<0 || unitCents<0) throw new ArgumentOutOfRangeException(); return checked(quantity*unitCents); }
Expected output
total:375 large:4294967294 overflow rejected:true
Test zero quantity, zero price, a negative operand and the largest representable successful product. Hint: derive the safe boundary by division before multiplication, while retaining checked arithmetic as the final guard.
Further reading
Interview questions for this lesson
- Explain: Why must widening and checked arithmetic occur at the actual billing multiplication, and which failures remain distinct?
- Trace: Trace the complete program for reject overflow with a deliberate billing contract. State the exact stdout, then justify the state transitions and relevant edge conditions.
- Design: Design a batch billing operation that must either publish all line totals or none. Specify rounding units, accumulation overflow and error reporting. Explain why local checked arithmetic alone does not provide transactional storage or external-payment idempotency.