Basic Concepts of C# Numeric Types — From Integers to SIMD, for LLM-Ready Thinking

Published: (December 7, 2025 at 08:34 PM EST)
7 min read
Source: Dev.to

Source: Dev.to

Basic Concepts of C# Numeric Types — From Integers to SIMD, for LLM-Ready Thinking

Most C# developers use numeric types every day:

int, long, float, double, decimal

When you start asking deeper, precision‑ and performance‑focused questions, things get interesting:

  • What actually happens when an int overflows?
  • Why is 0.1 + 0.2 not exactly 0.3 with double?
  • When should I use decimal instead of double in real systems?
  • How do literal suffixes (f, m, L) change IL and JIT behavior?
  • How can I push numeric operations into SIMD using Vector?
  • How do I talk about numeric types with LLMs so they reason like performance engineers?

In this article we’ll walk through a ShowNumericTypes() module that treats numeric types as a full‑stack concept: from Roslyn and IL to JIT, CPU, and SIMD. Then we’ll connect that mental model to better prompts for LLMs so you can get production‑grade answers instead of shallow ones.

If you can run a console app, you can follow this.


1. Mental Model: How Numeric Types Flow Through the .NET Stack

C# sourceRoslynILJITCPU

int x = 42;
double y = 3.1416;
  • Roslyn compiles the source into IL with concrete stack types (int32, float64, valuetype System.Decimal, …).
  • The JIT turns IL into machine code, deciding which registers to use (general‑purpose vs floating‑point/SIMD) and whether to insert overflow checks (add.ovf).
  • The CPU executes the resulting instructions:
    • Integer ALU (add, imul, …)
    • Floating‑point/SIMD (addss, addps, vmulps, …)
    • Multi‑word integer sequences for decimal.

Key ideaint, double, decimal are contracts between your code, the CLR, the JIT, and the CPU, not just “types in C#”.

When you ask LLMs for help, frame questions in terms of this pipeline: Roslyn → IL → JIT → CPU.


2. The Demo File: ShowNumericTypes() Overview

partial class Program
{
    // Call ShowNumericTypes() from your own Main() in another partial Program.
    static void ShowNumericTypes()
    {
        var integerNumber   = 42m;
        double doubleNumber = 3.1416d;
        float floatingNumber = 274f;
        long longNumber     = 300_200_100L;
        decimal monetaryNumber = 99.99m;

        Console.WriteLine($"Entero: {integerNumber}");
        Console.WriteLine($"Double: {doubleNumber}");
        Console.WriteLine($"Float: {floatingNumber}");
        Console.WriteLine($"Long: {longNumber}");
        Console.WriteLine($"Decimal: {monetaryNumber}");

        BasicNumericTypesIntro();
        IntegerRangeAndOverflow();
        FloatingPointPrecision();
        DecimalForMoney();
        NumericLiteralsAndTypeInference();
        VectorizationAndSIMD();
    }
}

Each helper method is a “lab” focused on one part of the numeric story. Together they form a teaching file you can put into a public GitHub repo and reuse when talking with LLMs.


3. Basic Numeric Types: int, long, float, double, decimal

static void BasicNumericTypesIntro()
{
    int    integerNumber  = 42;              // System.Int32
    double doubleNumber   = 3.1416d;         // System.Double
    float  floatingNumber = 274f;            // System.Single
    long   longNumber     = 300_200_100L;    // System.Int64
    decimal monetaryNumber = 99.99m;         // System.Decimal

    Console.WriteLine($"[Basic]   Int:     {integerNumber}");
    Console.WriteLine($"[Basic]   Double:  {doubleNumber}");
    Console.WriteLine($"[Basic]   Float:   {floatingNumber}");
    Console.WriteLine($"[Basic]   Long:    {longNumber}");
    Console.WriteLine($"[Basic]   Decimal: {monetaryNumber}");
}

Conceptual IL

.locals init (
    [0] int32   integerNumber,
    [1] float64 doubleNumber,
    [2] float32 floatingNumber,
    [3] int64   longNumber,
    [4] valuetype [System.Runtime]System.Decimal monetaryNumber
)

CPU Mapping

C# typeTypical register
int / longGeneral‑purpose integer registers (EAX/RAX/RCX/…)
float / doubleFloating‑point / SIMD registers (XMM/YMM)
decimalHandled via multiple 32‑bit integer operations in software

decimal is more expensive than double because the CPU has native hardware for binary floating point but not for base‑10 decimal arithmetic.


4. Integer Range & Overflow: checked vs unchecked

static void IntegerRangeAndOverflow()
{
    int max = int.MaxValue;
    int min = int.MinValue;

    Console.WriteLine($"[IntRange] int.MinValue = {min}, int.MaxValue = {max}");

    int overflowUnchecked = unchecked(max + 1);
    Console.WriteLine($"[Overflow] unchecked(max + 1) = {overflowUnchecked}");

    try
    {
        int overflowChecked = checked(max + 1);
        Console.WriteLine($"[Overflow] checked(max + 1) = {overflowChecked}");
    }
    catch (OverflowException ex)
    {
        Console.WriteLine($"[Overflow] checked(max + 1) threw: {ex.GetType().Name}");
    }
}
  • int is a 32‑bit two’s‑complement value, range [-2^31, 2^31‑1].
  • int.MaxValue = 0x7FFFFFFF (2,147,483,647). Adding 1 wraps to 0x80000000 (‑2,147,483,648).

IL opcodes

OpcodeBehaviour
addUnchecked – wraps on overflow
add.ovfChecked – throws OverflowException on overflow

Design rule

  • Use checked around security‑critical or financial arithmetic to catch bugs early.
  • Use unchecked in hot paths where overflow is impossible or deliberately desired (e.g., hash functions).

LLM Prompt Idea

“Given my IntegerRangeAndOverflow() method, explain the IL (add vs add.ovf) and show the equivalent x86‑64 assembly, including how the CPU flags are used to detect overflow.”


5. Floating‑Point Precision: IEEE‑754 and Bit Patterns

static void FloatingPointPrecision()
{
    double a = 0.1;
    double b = 0.2;
    double c = a + b;

    Console.WriteLine($"[FP] 0.1 + 0.2 = {c:R}  (R = round‑trip format)");

    long rawBits = BitConverter.DoubleToInt64Bits(c);
    Console.WriteLine($"[FP] Bits of (0.1+0.2): 0x{rawBits:X16}");

    float  fx = 1f / 10f;
    double dx = 1d / 10d;
    Console.WriteLine($"[FP] 1/10 as float = {fx:R}");
    Console.WriteLine($"[FP] 1/10 as double = {dx:R}");
}
  • double follows IEEE‑754 binary64. The decimal numbers 0.1 and 0.2 cannot be represented exactly, leading to a tiny rounding error that shows up in the sum.
  • The R format specifier prints the round‑trip representation, guaranteeing that parsing the output yields the original binary value.
  • BitConverter.DoubleToInt64Bits reveals the underlying 64‑bit pattern (0x3FD999999999999A for 0.1 + 0.2).

6. Decimal: Base‑10 Arithmetic for Money

static void DecimalForMoney()
{
    decimal price = 19.95m;
    decimal taxRate = 0.07m;      // 7 %
    decimal total = price + price * taxRate;

    Console.WriteLine($"[Decimal] Price: {price:C}");
    Console.WriteLine($"[Decimal] Tax (7 %): {(price * taxRate):C}");
    Console.WriteLine($"[Decimal] Total: {total:C}");
}
  • decimal stores 96 bits of integer data plus a scaling factor (0‑28), giving up to 28‑digit precision with exact base‑10 representation.
  • Ideal for financial calculations where rounding errors are unacceptable.
  • The trade‑off is slower arithmetic because the CLR implements decimal operations in software using multiple 32‑bit integer operations.

7. Numeric Literals & Type Inference: How Suffixes Change IL

static void NumericLiteralsAndTypeInference()
{
    var i = 42;      // int (int32)
    var l = 42L;     // long (int64)
    var f = 42f;     // float (float32)
    var d = 42d;     // double (float64)
    var m = 42m;     // decimal (valuetype System.Decimal)

    Console.WriteLine($"[Literals] int: {i.GetType()}, long: {l.GetType()}, float: {f.GetType()}, double: {d.GetType()}, decimal: {m.GetType()}");
}
  • Suffixes (L, f, d, m) force the compiler to emit the corresponding IL type.
  • Without a suffix, integer literals default to int (or long if the value exceeds int.MaxValue).
  • Floating‑point literals default to double.

IL example (for var f = 42f;)

ldc.r4 42.0
stloc.0

Whereas 42 would compile to ldc.i4.s 42.


8. Vectorization & SIMD: Vector for Real Performance

using System.Numerics;

static void VectorizationAndSIMD()
{
    // Simple element‑wise addition of two float arrays using SIMD
    float[] a = { 1, 2, 3, 4, 5, 6, 7, 8 };
    float[] b = { 8, 7, 6, 5, 4, 3, 2, 1 };
    float[] result = new float[a.Length];

    int vectorSize = Vector<float>.Count; // typically 4 or 8 depending on hardware
    int i = 0;

    for (; i (a, i);
        var vb = new Vector<float>(b, i);
        (va + vb).CopyTo(result, i);
    }

    // Handle remaining elements
    for (; i < a.Length; i++)
    {
        result[i] = a[i] + b[i];
    }
}
  • Vector<T> abstracts hardware SIMD registers (e.g., SSE, AVX).
  • The JIT emits vector instructions (addps, addpd, …) when the target CPU supports them.
  • Use this pattern for hot numeric loops where the data size is large enough to amortize the overhead of loading/storing vectors.

9. Using This Mental Model to Get More from LLMs

When you need the LLM to reason about performance or correctness:

  1. Specify the layer you care about (source → IL → JIT → CPU).
  2. Provide the relevant code snippet and ask for the IL or assembly.
  3. Ask for trade‑off analysis (e.g., “compare float vs decimal for a financial aggregation of 10 M items”).
  4. Request micro‑benchmark ideas that isolate the JIT‑generated code.

Example prompt

“Given the VectorizationAndSIMD() method, show the JIT‑generated assembly for the loop on an x86‑64 machine with AVX2, and explain how the CPU’s vector registers are used.”


10. Numeric‑Type Mastery Checklist (Top‑1 % Developer Mindset)

  • Understand the full stack: source → Roslyn → IL → JIT → CPU.
  • Know when to use checked vs unchecked and the IL opcodes they generate.
  • Be able to read and interpret IEEE‑754 bit patterns for float/double.
  • Choose decimal for exact base‑10 arithmetic; recognize its performance cost.
  • Use literal suffixes to control the emitted IL type.
  • Apply Vector or hardware intrinsics for data‑parallel workloads.
  • Craft LLM prompts that reference the specific pipeline stage you’re interested in.

Armed with this knowledge, you can write numerically robust C# code and get precise, performance‑aware answers from LLMs.

Back to Blog

Related posts

Read more »