An amount can look correct on a screen and still produce a different total when the same lines are added elsewhere. The problem may be the numeric type, the rounding rule, or the point at which rounding happens.
Those are separate decisions. A format string does not settle all three.
Start with decimal values
decimal is usually a better fit than double for amounts defined in decimal units. It can represent values such as 0.1 exactly. It still has finite precision; division can produce a result that needs rounding.
Use decimal literals from the start. Converting a previously calculated binary floating-point result into a decimal does not recover the intended calculation.
using System;
public static decimal RoundLine(decimal quantity, decimal unitPrice)
{
return Math.Round(
quantity * unitPrice,
2,
MidpointRounding.ToEven);
}
Here, two fractional digits and midpoint-to-even are explicit example rules. They are not universal rules for every currency, contract or application. The database column's precision and scale also need to match the values being stored.
Choose where rounding happens
Consider three lines with an unrounded value of 0.335m each, using midpoint-to-even:
| Calculation | Result |
|---|---|
| Round each line, then add | 0.34 + 0.34 + 0.34 = 1.02 |
| Add the unrounded values, then round | Round(1.005, 2) = 1.00 |
Both calculations follow their stated rule. Mixing them creates a reconciliation problem.
Decide whether displayed line amounts are the amounts used in the total. Put that decision in the calculation code and its checks. Preserve existing behavior deliberately when changing an older system.
Separate display from calculation
Displaying a value with two decimal places changes its presentation. It does not replace a rule for what should be persisted, compared or added later.
Keep amounts numeric through the calculation. Choose an explicit culture when parsing external text. For exports, document the decimal separator and format rather than relying on whichever locale happens to run the process.
Before changing this code, check midpoint values, negative values, repeated lines and the largest supported amount. Use the expected values from the application's actual rules, not values inferred from a formatted screenshot.
Microsoft documents the numeric differences in floating-point types and the rounding choices in MidpointRounding.