A checkout can receive the text "19.95", calculate a decimal total, and eventually write a formatted amount to a file. Those are three different operations. Parsing interprets characters. Arithmetic calculates a value. Formatting produces characters again. A cast changes a numeric representation without interpreting text. Mixing these responsibilities can create errors that depend on the user's language or appear only when a large value arrives.
This guide uses a .NET 8 console project and C# 12. The numeric rules shown also apply to later stable versions, but the examples deliberately avoid preview features. You will build a small import boundary that accepts a known number format, rounds under a named rule, checks a narrowing conversion, and writes predictable output.
Range and precision answer different questions
A type's range describes the smallest and largest values it can represent. Precision describes how closely it can represent a particular value. Converting an int to a long preserves every integer because the destination has enough range and precision. An implicit int to float conversion is permitted, but sufficiently large integers lose precision. For example, single-precision floating point cannot distinguish every neighboring integer above sixteen million.
The compiler's permission to convert implicitly is therefore not a promise that every digit survives. Binary floating-point types are useful for measurements and many calculations, while decimal is often a better match for amounts expressed in decimal fractions. Decimal still has a finite range and precision. Choose the representation from the business requirement before choosing a cast. A database identifier that must remain exact should not travel through a floating-point value merely because that value supports large magnitudes.
Narrowing conversions require an explicit policy
An explicit conversion to a smaller integer type can fail or lose information. In a checked context, an out-of-range integral conversion throws OverflowException. In an unchecked integral conversion, the result follows the destination bit representation, which can produce a surprising negative value. Neither behavior is a sensible substitute for validating an imported quantity.
Converting a finite floating-point value to an integer discards the fractional part toward zero. That differs from rounding to the nearest integer. First decide whether fractional quantities are allowed. Reject a fractional value, truncate it intentionally, or round with an explicit midpoint policy. Then check the destination range. Checked arithmetic also matters before the cast: multiplying two large integers can overflow even when their final result is assigned to a long. Promote an operand before performing the operation, or use checked arithmetic to detect the error where it occurs.
Parsing text needs a format contract
TryParse reports whether text is acceptable without using an exception as ordinary input control flow. Its overloads can accept NumberStyles and an IFormatProvider. These parameters describe which characters are allowed and what decimal or group separators mean. The text "1,25" can describe a decimal amount in one culture and a different format in another. Silently using the machine's current culture makes a file format vary between computers.
A machine-to-machine format should name its convention, often invariant culture with a period decimal separator. A user interface can accept the user's selected culture. Do not guess by replacing every comma with a period: a comma might be a thousands separator. Reject formats outside the contract and show a useful validation message. Parsing success also does not prove a value is allowed. A negative decimal can parse perfectly while violating a quantity rule.
Formatting and rounding are separate decisions
A format such as F2 produces text with two fractional digits. It does not change the stored variable. If later arithmetic must use a rounded amount, store the result of decimal.Round first. Otherwise the program can display one amount while continuing to calculate with another. That distinction is particularly important when several line items are rounded separately from an invoice total.
Choose a midpoint rule with the people who define the calculation. ToEven and AwayFromZero produce different results for certain exact midpoint values. Neither is universally correct for every domain. Keep intermediate calculations precise enough for the required rule, then round at a documented boundary. For storage and interchange, prefer an explicit invariant representation. For a screen, use the intended display culture and appropriate currency metadata; formatting a number with a currency symbol does not convert between currencies.
Mistakes to avoid at numeric boundaries
Do not convert a malformed string to zero and continue: zero may be a valid amount, so that fallback hides the error. Preserve a separate success flag. Do not use a cast to validate a fractional input, because truncation can turn an invalid value into a plausible integer. Avoid a catch block around the entire workflow that reports every failure as an invalid number; a missing file or programming defect deserves a different diagnosis.
For a reusable parser, test the smallest and largest accepted values, one value outside each boundary, whitespace, a decimal separator, and an overflowing string. Include a culture different from the development computer's culture. These cases target the actual contract and catch assumptions that ordinary examples cannot reveal.
Official references
C# Numeric Conversions and Culture Examples
Each example shows the code, the output it produces and what each part does.
An explicit import boundary
Create a .NET 8 console application and replace Program.cs with this complete example. The accepted input uses an invariant decimal point and permits a leading sign, but does not permit a thousands separator.
The rounding rule is visible at the calculation boundary, and the output format independently fixes two decimal places. The second operation demonstrates detection, rather than a recovery strategy for a real order. A real importer should normally report the offending field and continue or reject the record according to its contract.
using System;
using System.Globalization;
const string raw = "19.955";
var styles = NumberStyles.AllowLeadingSign |
NumberStyles.AllowDecimalPoint;
if (!decimal.TryParse(raw, styles,
CultureInfo.InvariantCulture, out decimal amount) ||
amount < 0m || amount > 1000m)
{
Console.WriteLine("Invalid amount");
return;
}
decimal rounded = decimal.Round(amount, 2,
MidpointRounding.AwayFromZero);
Console.WriteLine(rounded.ToString("F2",
CultureInfo.InvariantCulture));
try
{
int tooLarge = 300;
byte narrowed = checked((byte)tooLarge);
Console.WriteLine(narrowed);
}
catch (OverflowException)
{
Console.WriteLine("Quantity exceeds byte range");
}19.96
Quantity exceeds byte rangeC# Numeric Conversions and Culture in Real Projects
Worked exercise: import a bounded quantity
The task is to accept a whole-number quantity between zero and two hundred and print its checked package total. Leading and trailing whitespace are allowed. A fractional number, an overflowing integer, or a negative value must produce a validation message. Keep the parser independent from the arithmetic so a successful parse cannot bypass the business rule.
using System;
using System.Globalization;
static bool TryQuantity(string text, out int quantity)
{
return int.TryParse(text, NumberStyles.Integer,
CultureInfo.InvariantCulture, out quantity) &&
quantity >= 0 && quantity <= 200;
}
foreach (string raw in new[] { " 12 ", "2.5", "-1" })
{
if (!TryQuantity(raw, out int quantity))
{
Console.WriteLine("Invalid quantity");
continue;
}
int units = checked(quantity * 24);
Console.WriteLine(units.ToString(
CultureInfo.InvariantCulture));
}288
Invalid quantity
Invalid quantityThe style allows surrounding whitespace and a leading sign, but the range rule rejects a negative result. The selected integer parser rejects the decimal point. Even though the current upper bound makes multiplication safe, checked communicates the expectation that an overflow must be detected if the business limit changes.
C# Numeric Conversions and Culture FAQ
Does an implicit conversion always preserve the value?
No. Some permitted implicit conversions preserve the range while losing precision. A large integer converted to float can become a nearby representable value. Read both the source and destination representation requirements. When exact identifiers or counts matter, keep an integral type with sufficient range. When a conversion feeds a comparison, test values near precision boundaries as well as near the numeric type's maximum.
Should I use decimal for every number?
Choose decimal when decimal arithmetic matches the requirement, such as a currency amount stored in decimal units. It is not a universal replacement for floating point. Scientific calculations, graphics, and APIs may require double or float. Decimal arithmetic can still overflow and still requires a rounding policy. Keep units visible and avoid casually mixing monetary values with measurements represented under a different numerical model.
Why can the same text parse differently on two computers?
An overload that relies on the current culture inherits the environment's separators and other number-format conventions. That can change the meaning or validity of text. For an interchange format, pass CultureInfo.InvariantCulture and a deliberate style. For a form, pass the selected user culture. A format contract should also explain whether grouping, signs, exponent notation, and surrounding whitespace are accepted.
When should parsing throw an exception?
Use TryParse for expected invalid input, such as a user typing a letter into a numeric field. Use Parse when invalid text represents a broken internal contract and an exception is appropriate to the caller. Neither method replaces a domain range check. A successful parse should be followed by rules such as nonnegative quantities, maximum batch sizes, or permitted fractions before the value changes application state.
C# Numeric Conversions and Culture Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
An imported value of 2.9 must be rejected because quantities are whole numbers. Which approach satisfies the rule?
C# Numeric Conversions and Culture Quiz
2 questions to check what you learned.
Which statement about an implicit int-to-float conversion is correct?
- The conversion is allowed, but some large integers lose precision.
- Every integer digit is always preserved.
- It parses the integer using the current culture.
- It always throws when the integer has more than six digits.
A CSV contract requires a period decimal separator regardless of the machine. What should the parser use?
- CurrentCulture with no documented style.
- An explicit invariant culture and permitted number styles.
- A global comma-to-period replacement.
- A checked cast from string to decimal.