Skip to main content

4.3 - Working with Numerals

C# provides robust support for working with numeric values, including formatting numbers for display, parsing numeric strings, and handling specialized numeric types like BigInteger and Complex. This chapter explores these capabilities in detail.

4.3.1 - Numeric Formatting

C# offers various ways to format numeric values for display, making it easier to present numbers in a human-readable format.

4.3.1.1 - Standard Numeric Format Strings

C# provides standard format specifiers that define a common formatting for numeric types. These format specifiers are single characters that represent a predefined format pattern.

Format SpecifierNameDescriptionExample
C or cCurrencyFormats the number as a currency value1234.56 → $1,234.56
D or dDecimalFormats an integer as a decimal number1234 → 1234
E or eExponentialFormats the number in scientific notation1234.56 → 1.234560E+003
F or fFixed-pointFormats the number with a fixed number of decimal places1234.56 → 1234.56
G or gGeneralFormats the number in the most compact form1234.56 → 1234.56
N or nNumberFormats the number with thousand separators1234.56 → 1,234.56
P or pPercentFormats the number as a percentage0.1234 → 12.34%
X or xHexadecimalFormats an integer as a hexadecimal value255 → FF or ff

Example:

using System;

class Program
{
static void Main()
{
double number = 1234.56789;
int intNumber = 1234;

// Currency format
Console.WriteLine($"Currency (C): {number:C}");
Console.WriteLine($"Currency with 1 decimal place (C1): {number:C1}");

// Decimal format (for integers)
Console.WriteLine($"Decimal (D): {intNumber:D}");
Console.WriteLine($"Decimal with 6 digits (D6): {intNumber:D6}");

// Exponential (scientific) format
Console.WriteLine($"Exponential (E): {number:E}");
Console.WriteLine($"Exponential with 2 decimal places (E2): {number:E2}");

// Fixed-point format
Console.WriteLine($"Fixed-point (F): {number:F}");
Console.WriteLine($"Fixed-point with 2 decimal places (F2): {number:F2}");

// General format
Console.WriteLine($"General (G): {number:G}");
Console.WriteLine($"General with 4 significant digits (G4): {number:G4}");

// Number format
Console.WriteLine($"Number (N): {number:N}");
Console.WriteLine($"Number with 1 decimal place (N1): {number:N1}");

// Percent format
double percentage = 0.1234;
Console.WriteLine($"Percent (P): {percentage:P}");
Console.WriteLine($"Percent with 1 decimal place (P1): {percentage:P1}");

// Hexadecimal format (for integers)
Console.WriteLine($"Hexadecimal (X): {intNumber:X}");
Console.WriteLine($"Hexadecimal with 8 digits (X8): {intNumber:X8}");
}
}

Output:

Currency (C): $1,234.57
Currency with 1 decimal place (C1): $1,234.6
Decimal (D): 1234
Decimal with 6 digits (D6): 001234
Exponential (E): 1.234568E+003
Exponential with 2 decimal places (E2): 1.23E+003
Fixed-point (F): 1234.57
Fixed-point with 2 decimal places (F2): 1234.57
General (G): 1234.56789
General with 4 significant digits (G4): 1235
Number (N): 1,234.57
Number with 1 decimal place (N1): 1,234.6
Percent (P): 12.34%
Percent with 1 decimal place (P1): 12.3%
Hexadecimal (X): 4D2
Hexadecimal with 8 digits (X8): 000004D2

4.3.1.2 - Custom Numeric Format Strings

Custom format strings provide more control over how numbers are formatted. They consist of one or more custom format specifiers that define the format of the output.

Format SpecifierDescription
0Zero placeholder (displays a digit or 0)
#Digit placeholder (displays a digit or nothing)
.Decimal point
,Thousand separator and number scaling
%Percentage placeholder
E or eExponential notation
;Section separator
\Escape character
'string' or "string"Literal string
OtherAll other characters are copied to the result string

Example:

using System;

class Program
{
static void Main()
{
double number = 1234.56789;

// Custom format with zero placeholders
Console.WriteLine($"Format with zeros: {number:00000.00}");

// Custom format with digit placeholders
Console.WriteLine($"Format with digits: {number:###,###.##}");

// Custom format with mixed placeholders
Console.WriteLine($"Mixed format: {number:#,##0.00}");

// Custom format with percentage
double percentage = 0.1234;
Console.WriteLine($"Custom percentage: {percentage:#0.00%}");

// Custom format with exponential notation
Console.WriteLine($"Custom exponential: {number:0.00E+00}");

// Custom format with sections (positive;negative;zero)
double positiveNumber = 1234.56;
double negativeNumber = -1234.56;
double zeroNumber = 0;
string customFormat = "#,##0.00;(#,##0.00);Zero";

Console.WriteLine($"Positive: {positiveNumber.ToString(customFormat)}");
Console.WriteLine($"Negative: {negativeNumber.ToString(customFormat)}");
Console.WriteLine($"Zero: {zeroNumber.ToString(customFormat)}");

// Custom format with literal strings
Console.WriteLine($"With literals: {number:'$'#,##0.00' USD'}");
}
}

Output:

Format with zeros: 01234.57
Format with digits: 1,234.57
Mixed format: 1,234.57
Custom percentage: 12.34%
Custom exponential: 1.23E+03
Positive: 1,234.56
Negative: (1,234.56)
Zero: Zero
With literals: $1,234.57 USD

4.3.1.3 - Formatting with IFormatProvider

The IFormatProvider interface allows you to control the formatting based on culture-specific information. The most common implementation is CultureInfo.

Example:

using System;
using System.Globalization;

class Program
{
static void Main()
{
double number = 1234.56;

// Format using different cultures
CultureInfo usCulture = new CultureInfo("en-US");
CultureInfo frCulture = new CultureInfo("fr-FR");
CultureInfo deCulture = new CultureInfo("de-DE");

Console.WriteLine("Currency formatting in different cultures:");
Console.WriteLine($"US: {number.ToString("C", usCulture)}");
Console.WriteLine($"France: {number.ToString("C", frCulture)}");
Console.WriteLine($"Germany: {number.ToString("C", deCulture)}");

Console.WriteLine("\nNumber formatting in different cultures:");
Console.WriteLine($"US: {number.ToString("N2", usCulture)}");
Console.WriteLine($"France: {number.ToString("N2", frCulture)}");
Console.WriteLine($"Germany: {number.ToString("N2", deCulture)}");

// Format using the current culture
Console.WriteLine("\nUsing current culture:");
Console.WriteLine($"Current: {number.ToString("C")}");

// Temporarily change the current culture
CultureInfo originalCulture = CultureInfo.CurrentCulture;
CultureInfo.CurrentCulture = new CultureInfo("ja-JP");

Console.WriteLine($"Japanese: {number.ToString("C")}");

// Restore the original culture
CultureInfo.CurrentCulture = originalCulture;
}
}

4.3.2 - Currency Formatting

Currency formatting is a specialized form of numeric formatting that displays monetary values with the appropriate currency symbol and formatting conventions.

4.3.2.1 - Basic Currency Formatting

The "C" format specifier is used for currency formatting. It formats a number as a currency value according to the current culture settings.

Example:

using System;
using System.Globalization;

class Program
{
static void Main()
{
decimal amount = 1234.56m;

// Basic currency formatting
Console.WriteLine($"Default currency format: {amount:C}");

// Currency with specific number of decimal places
Console.WriteLine($"Currency with 0 decimal places: {amount:C0}");
Console.WriteLine($"Currency with 3 decimal places: {amount:C3}");

// Currency formatting with different cultures
Console.WriteLine("\nCurrency in different cultures:");
Console.WriteLine($"US Dollar: {amount.ToString("C", new CultureInfo("en-US"))}");
Console.WriteLine($"Euro (France): {amount.ToString("C", new CultureInfo("fr-FR"))}");
Console.WriteLine($"British Pound: {amount.ToString("C", new CultureInfo("en-GB"))}");
Console.WriteLine($"Japanese Yen: {amount.ToString("C", new CultureInfo("ja-JP"))}");
Console.WriteLine($"Chinese Yuan: {amount.ToString("C", new CultureInfo("zh-CN"))}");
Console.WriteLine($"Indian Rupee: {amount.ToString("C", new CultureInfo("hi-IN"))}");
Console.WriteLine($"Brazilian Real: {amount.ToString("C", new CultureInfo("pt-BR"))}");
}
}

4.3.2.2 - Custom Currency Formatting

You can create custom currency formats to meet specific requirements.

Example:

using System;
using System.Globalization;

class Program
{
static void Main()
{
decimal amount = 1234567.89m;

// Custom currency format with thousand separators and fixed decimal places
string customFormat = "#,##0.00 'USD'";
Console.WriteLine($"Custom format: {amount.ToString(customFormat)}");

// Format large amounts in millions
decimal millions = amount / 1000000m;
Console.WriteLine($"In millions: {millions:0.##} million USD");

// Format with accounting style (negative values in parentheses)
decimal negativeAmount = -1234.56m;
string accountingFormat = "#,##0.00 USD;(#,##0.00 USD);Zero USD";
Console.WriteLine($"Accounting format (positive): {amount.ToString(accountingFormat)}");
Console.WriteLine($"Accounting format (negative): {negativeAmount.ToString(accountingFormat)}");
Console.WriteLine($"Accounting format (zero): {0m.ToString(accountingFormat)}");

// Format with different currency symbols
Console.WriteLine("\nCustom currency symbols:");
Console.WriteLine($"Euro: {amount:#,##0.00 '€'}");
Console.WriteLine($"Pound: {amount:#,##0.00 '£'}");
Console.WriteLine($"Yen: {amount:#,##0 '¥'}"); // No decimal places for Yen
Console.WriteLine($"Bitcoin: {amount:#,##0.00000000 '₿'}"); // 8 decimal places for Bitcoin
}
}

4.3.2.3 - Currency Conversion and Formatting

When working with multiple currencies, you might need to convert between them and format the results.

Example:

using System;
using System.Globalization;

class Program
{
static void Main()
{
// Define exchange rates (as of a specific date)
decimal usdToEurRate = 0.85m;
decimal usdToGbpRate = 0.75m;
decimal usdToJpyRate = 110.0m;

// Original amount in USD
decimal amountUsd = 1000.0m;

// Convert to other currencies
decimal amountEur = amountUsd * usdToEurRate;
decimal amountGbp = amountUsd * usdToGbpRate;
decimal amountJpy = amountUsd * usdToJpyRate;

// Format each currency appropriately
Console.WriteLine("Currency conversion example:");
Console.WriteLine($"USD: {amountUsd.ToString("C", new CultureInfo("en-US"))}");
Console.WriteLine($"EUR: {amountEur.ToString("C", new CultureInfo("fr-FR"))}");
Console.WriteLine($"GBP: {amountGbp.ToString("C", new CultureInfo("en-GB"))}");
Console.WriteLine($"JPY: {amountJpy.ToString("C0", new CultureInfo("ja-JP"))}"); // No decimal places for Yen

// Create a currency conversion table
Console.WriteLine("\nCurrency Conversion Table:");
Console.WriteLine("--------------------------------------------------");
Console.WriteLine("| Amount (USD) | EUR | GBP | JPY |");
Console.WriteLine("--------------------------------------------------");

for (int i = 1; i <= 5; i++)
{
decimal usd = i * 100;
decimal eur = usd * usdToEurRate;
decimal gbp = usd * usdToGbpRate;
decimal jpy = usd * usdToJpyRate;

Console.WriteLine($"| {usd,12:C} | {eur,12:C} | {gbp,12:C} | {jpy,7:N0} ¥ |",
new CultureInfo("en-US"),
new CultureInfo("fr-FR"),
new CultureInfo("en-GB"));
}

Console.WriteLine("--------------------------------------------------");
}
}

4.3.3 - Parsing Numeric Strings

Parsing is the process of converting string representations of numbers into their corresponding numeric types. C# provides several methods for parsing numeric strings.

4.3.3.1 - Basic Parsing Methods

Each numeric type in C# has a Parse method that converts a string to that type.

Example:

using System;

class Program
{
static void Main()
{
// Parsing integers
string intString = "123";
int parsedInt = int.Parse(intString);
Console.WriteLine($"Parsed int: {parsedInt}");

// Parsing doubles
string doubleString = "123.45";
double parsedDouble = double.Parse(doubleString);
Console.WriteLine($"Parsed double: {parsedDouble}");

// Parsing decimals
string decimalString = "123.45";
decimal parsedDecimal = decimal.Parse(decimalString);
Console.WriteLine($"Parsed decimal: {parsedDecimal}");

// Parsing other numeric types
Console.WriteLine($"Parsed byte: {byte.Parse("255")}");
Console.WriteLine($"Parsed short: {short.Parse("-32768")}");
Console.WriteLine($"Parsed long: {long.Parse("9223372036854775807")}");
Console.WriteLine($"Parsed float: {float.Parse("123.45")}");
}
}

4.3.3.2 - TryParse Methods

The TryParse methods are safer alternatives to Parse because they don't throw exceptions when parsing fails. Instead, they return a boolean indicating success or failure.

Example:

using System;

class Program
{
static void Main()
{
// TryParse for integers
string validInt = "123";
string invalidInt = "abc";

if (int.TryParse(validInt, out int result1))
{
Console.WriteLine($"Successfully parsed: {result1}");
}
else
{
Console.WriteLine("Failed to parse");
}

if (int.TryParse(invalidInt, out int result2))
{
Console.WriteLine($"Successfully parsed: {result2}");
}
else
{
Console.WriteLine("Failed to parse");
}

// TryParse for doubles
string validDouble = "123.45";
string invalidDouble = "123.45.67";

if (double.TryParse(validDouble, out double result3))
{
Console.WriteLine($"Successfully parsed: {result3}");
}
else
{
Console.WriteLine("Failed to parse");
}

if (double.TryParse(invalidDouble, out double result4))
{
Console.WriteLine($"Successfully parsed: {result4}");
}
else
{
Console.WriteLine("Failed to parse");
}
}
}

4.3.3.3 - Parsing with Culture Information

When parsing numeric strings that use culture-specific formats (like different decimal separators), you can specify the culture to use.

Example:

using System;
using System.Globalization;

class Program
{
static void Main()
{
// Different cultures use different decimal separators
string usNumber = "1,234.56"; // US format (comma as thousand separator, period as decimal separator)
string frNumber = "1 234,56"; // French format (space as thousand separator, comma as decimal separator)

// Parse using specific cultures
double usValue = double.Parse(usNumber, CultureInfo.GetCultureInfo("en-US"));
double frValue = double.Parse(frNumber, CultureInfo.GetCultureInfo("fr-FR"));

Console.WriteLine($"US number parsed: {usValue}");
Console.WriteLine($"French number parsed: {frValue}");

// TryParse with culture information
if (double.TryParse(frNumber, NumberStyles.Any, CultureInfo.GetCultureInfo("fr-FR"), out double result))
{
Console.WriteLine($"Successfully parsed French number: {result}");
}

// Parsing currency strings
string usCurrency = "$1,234.56";
string frCurrency = "1 234,56 €";

decimal usAmount = decimal.Parse(usCurrency, NumberStyles.Currency, CultureInfo.GetCultureInfo("en-US"));
decimal frAmount = decimal.Parse(frCurrency, NumberStyles.Currency, CultureInfo.GetCultureInfo("fr-FR"));

Console.WriteLine($"US currency parsed: {usAmount}");
Console.WriteLine($"French currency parsed: {frAmount}");
}
}

4.3.3.4 - Parsing with NumberStyles

The NumberStyles enumeration allows you to specify which elements can be present in the string being parsed.

Example:

using System;
using System.Globalization;

class Program
{
static void Main()
{
// Parse a hexadecimal string
string hexString = "1A3";
int hexValue = int.Parse(hexString, NumberStyles.HexNumber);
Console.WriteLine($"Hex string '{hexString}' parsed as: {hexValue}");

// Parse a number with currency symbol
string currencyString = "$123.45";
decimal currencyValue = decimal.Parse(currencyString, NumberStyles.Currency);
Console.WriteLine($"Currency string '{currencyString}' parsed as: {currencyValue}");

// Parse a number with leading and trailing whitespace
string paddedString = " 123.45 ";
double paddedValue = double.Parse(paddedString, NumberStyles.Float | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite);
Console.WriteLine($"Padded string '{paddedString}' parsed as: {paddedValue}");

// Parse a number with parentheses (negative value in accounting notation)
string accountingString = "(123.45)";
decimal accountingValue = decimal.Parse(accountingString, NumberStyles.Number | NumberStyles.AllowParentheses);
Console.WriteLine($"Accounting string '{accountingString}' parsed as: {accountingValue}");

// Parse a number with percentage symbol
string percentString = "12.34%";
double percentValue = double.Parse(percentString, NumberStyles.Float | NumberStyles.AllowPercent);
Console.WriteLine($"Percent string '{percentString}' parsed as: {percentValue}");
}
}

4.3.4 - BigInteger and Complex Numbers

C# provides specialized numeric types for working with very large integers and complex numbers.

4.3.4.1 - Working with BigInteger

The BigInteger structure represents an arbitrarily large integer. It's useful when you need to work with integers that exceed the range of the built-in numeric types.

Example:

using System;
using System.Numerics;

class Program
{
static void Main()
{
// Create BigInteger values
BigInteger a = new BigInteger(long.MaxValue);
BigInteger b = BigInteger.Parse("9223372036854775808"); // One more than long.MaxValue

Console.WriteLine($"a = {a}");
Console.WriteLine($"b = {b}");

// Arithmetic operations
BigInteger sum = a + b;
BigInteger difference = b - a;
BigInteger product = a * b;
BigInteger quotient = b / a;
BigInteger remainder = b % a;

Console.WriteLine($"a + b = {sum}");
Console.WriteLine($"b - a = {difference}");
Console.WriteLine($"a * b = {product}");
Console.WriteLine($"b / a = {quotient}");
Console.WriteLine($"b % a = {remainder}");

// Power operation
BigInteger squared = BigInteger.Pow(a, 2);
Console.WriteLine($"a^2 = {squared}");

// Factorial example
Console.WriteLine("\nFactorials:");
for (int i = 1; i <= 30; i += 5)
{
Console.WriteLine($"{i}! = {Factorial(i)}");
}

// Fibonacci example
Console.WriteLine("\nFibonacci numbers:");
for (int i = 0; i <= 100; i += 10)
{
Console.WriteLine($"Fibonacci({i}) = {Fibonacci(i)}");
}
}

// Calculate factorial using BigInteger
static BigInteger Factorial(int n)
{
BigInteger result = 1;

for (int i = 2; i <= n; i++)
{
result *= i;
}

return result;
}

// Calculate Fibonacci number using BigInteger
static BigInteger Fibonacci(int n)
{
if (n <= 1)
return n;

BigInteger a = 0;
BigInteger b = 1;
BigInteger c;

for (int i = 2; i <= n; i++)
{
c = a + b;
a = b;
b = c;
}

return b;
}
}

4.3.4.2 - Working with Complex Numbers

The Complex structure represents a complex number with real and imaginary parts. It's useful for mathematical and scientific calculations that involve complex numbers.

Example:

using System;
using System.Numerics;

class Program
{
static void Main()
{
// Create Complex numbers
Complex a = new Complex(3, 4); // 3 + 4i
Complex b = new Complex(1, 2); // 1 + 2i

Console.WriteLine($"a = {a}");
Console.WriteLine($"b = {b}");

// Properties
Console.WriteLine($"Real part of a: {a.Real}");
Console.WriteLine($"Imaginary part of a: {a.Imaginary}");
Console.WriteLine($"Magnitude (absolute value) of a: {Complex.Abs(a)}");
Console.WriteLine($"Phase (argument) of a: {a.Phase} radians");

// Arithmetic operations
Complex sum = a + b;
Complex difference = a - b;
Complex product = a * b;
Complex quotient = a / b;

Console.WriteLine($"a + b = {sum}");
Console.WriteLine($"a - b = {difference}");
Console.WriteLine($"a * b = {product}");
Console.WriteLine($"a / b = {quotient}");

// Other operations
Complex conjugate = Complex.Conjugate(a); // 3 - 4i
Complex reciprocal = Complex.Reciprocal(a); // 1/a

Console.WriteLine($"Conjugate of a = {conjugate}");
Console.WriteLine($"Reciprocal of a = {reciprocal}");

// Exponential and logarithmic functions
Complex exp = Complex.Exp(a);
Complex log = Complex.Log(a);

Console.WriteLine($"e^a = {exp}");
Console.WriteLine($"ln(a) = {log}");

// Trigonometric functions
Complex sin = Complex.Sin(a);
Complex cos = Complex.Cos(a);
Complex tan = Complex.Tan(a);

Console.WriteLine($"sin(a) = {sin}");
Console.WriteLine($"cos(a) = {cos}");
Console.WriteLine($"tan(a) = {tan}");

// Powers and roots
Complex squared = Complex.Pow(a, 2);
Complex squareRoot = Complex.Sqrt(a);

Console.WriteLine($"a^2 = {squared}");
Console.WriteLine($"√a = {squareRoot}");

// Euler's identity: e^(iπ) + 1 = 0
Complex eulerIdentity = Complex.Exp(new Complex(0, Math.PI)) + 1;
Console.WriteLine($"e^(iπ) + 1 = {eulerIdentity}");
}
}

4.3.4.3 - Practical Applications

Here are some practical applications of BigInteger and Complex numbers:

Example:

using System;
using System.Numerics;

class Program
{
static void Main()
{
// BigInteger application: Calculate large combinations
// nCr = n! / (r! * (n-r)!)
int n = 100;
int r = 50;
BigInteger combinations = Combinations(n, r);
Console.WriteLine($"C({n},{r}) = {combinations}");

// Complex application: Mandelbrot set iteration
Console.WriteLine("\nMandelbrot Set Iterations:");
Complex c1 = new Complex(0.25, 0.0);
Complex c2 = new Complex(-0.75, 0.1);
Complex c3 = new Complex(-0.75, 0.2);

Console.WriteLine($"Iterations for c = {c1}: {MandelbrotIterations(c1, 100)}");
Console.WriteLine($"Iterations for c = {c2}: {MandelbrotIterations(c2, 100)}");
Console.WriteLine($"Iterations for c = {c3}: {MandelbrotIterations(c3, 100)}");

// Complex application: AC circuit analysis
double resistance = 100.0; // 100 ohms
double inductance = 0.1; // 0.1 henry
double capacitance = 1e-6; // 1 microfarad
double frequency = 1000.0; // 1000 Hz

Complex impedance = CalculateImpedance(resistance, inductance, capacitance, frequency);
Console.WriteLine($"\nCircuit impedance at {frequency} Hz: {impedance} ohms");
Console.WriteLine($"Impedance magnitude: {Complex.Abs(impedance):F2} ohms");
Console.WriteLine($"Phase angle: {impedance.Phase * 180 / Math.PI:F2} degrees");
}

// Calculate combinations using BigInteger
static BigInteger Combinations(int n, int r)
{
return Factorial(n) / (Factorial(r) * Factorial(n - r));
}

static BigInteger Factorial(int n)
{
BigInteger result = 1;
for (int i = 2; i <= n; i++)
{
result *= i;
}
return result;
}

// Calculate Mandelbrot set iterations
static int MandelbrotIterations(Complex c, int maxIterations)
{
Complex z = Complex.Zero;
int iterations = 0;

while (Complex.Abs(z) <= 2 && iterations < maxIterations)
{
z = z * z + c;
iterations++;
}

return iterations;
}

// Calculate impedance in an AC circuit
static Complex CalculateImpedance(double resistance, double inductance, double capacitance, double frequency)
{
double omega = 2 * Math.PI * frequency;
double inductiveReactance = omega * inductance;
double capacitiveReactance = 1 / (omega * capacitance);

// Impedance = R + j(XL - XC)
return new Complex(resistance, inductiveReactance - capacitiveReactance);
}
}

Working with numerals in C# involves a rich set of formatting options, parsing capabilities, and specialized numeric types. By mastering these features, you can effectively handle a wide range of numeric data in your applications, from simple integer calculations to complex scientific computations.