HumanNumbers.AspNetCore 2.0.2

dotnet add package HumanNumbers.AspNetCore --version 2.0.2
                    
NuGet\Install-Package HumanNumbers.AspNetCore -Version 2.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="HumanNumbers.AspNetCore" Version="2.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HumanNumbers.AspNetCore" Version="2.0.2" />
                    
Directory.Packages.props
<PackageReference Include="HumanNumbers.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add HumanNumbers.AspNetCore --version 2.0.2
                    
#r "nuget: HumanNumbers.AspNetCore, 2.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package HumanNumbers.AspNetCore@2.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=HumanNumbers.AspNetCore&version=2.0.2
                    
Install as a Cake Addin
#tool nuget:?package=HumanNumbers.AspNetCore&version=2.0.2
                    
Install as a Cake Tool

HumanNumbers

Human-readable numbers for .NET — a governed, high-performance, API-safe formatting engine.

NuGet License: MIT


⚡ Quick Start

using HumanNumbers;

1500.ToHuman();        // "1.50K"
1500000.ToHuman();     // "1.50M"

🎯 Why HumanNumbers?

Formatting numbers correctly at scale is deceptively complex. Naive implementations often:

  • Break at rounding boundaries: (e.g., 999,499 rounding to 1,000K instead of 1.00M).
  • Allocate excessively: Creating intermediate strings in hot telemetry or logging paths.
  • Lack consistency: Different services formatting the same data in conflicting ways.

HumanNumbers provides a governed, high-performance platform for number presentation that prioritizes:

  • Correctness: Smart suffix promotion logic that handles rounding boundaries gracefully.
    999499m.ToHuman();                                      // "1.00M" (Readable)
    999499m.ToHuman(HumanNumberFormatOptions.StrictPreset); // "999.50K" (Accurate)
    
  • Performance: Optimized Span<char>-based paths and zero-allocation parsing.
  • Safety: Non-intrusive ASP.NET Core integration that preserves API contracts by default.
  • Governance: A central Policy system to ensure consistent formatting across distributed services.

🚀 Comparison: Three Ways to Format

Approach Code Trade-off
Naive .NET (val / 1000).ToString("F2") + "K" Error-prone at thresholds, high allocation, no culture support.
Fluent API val.ToHuman(2) Balanced. Minimal allocation with full formatting support.
Span API val.ToHuman(span, out _) High-Performance. Zero-allocation; ideal for telemetry and logging.

The "Naive" example reflects common real-world implementations, not optimized hand-written formatters. Both APIs use the same high-performance core engine; the Span overload simply bypasses string materialization for critical paths.


🔢 Core Formatting Engine

Readable vs. Strict Promotion

By default, we prioritize readability. If a number rounds up to the next threshold (e.g., 999,499 to 1.00M), we promote the suffix. For audit-heavy scenarios, use Strict Mode:

999499m.ToHuman();                                      // "1.00M" (Readable Default)
999499m.ToHuman(HumanNumberFormatOptions.StrictPreset); // "999.50K" (Strict Accuracy)

Specialized Units

  • Financial: 1234.56m.ToHumanWords(); // "One Thousand Two Hundred..."
  • Bytes: 1024L.ToHumanBytes(); // "1.00 KiB" (Supports Binary/Decimal)
  • Fractions: 1.5m.ToHumanFraction(32); // "1 16/32"
  • Roman: 2024.ToRoman(); // "MMXXIV"

🌍 Internationalization & Global Support

HumanNumbers is built on top of the native .NET CultureInfo system, meaning it automatically respects global numbering rules, separators, and currency symbols out of the box.

Verified Global Outputs (Examples)

Culture Scaled Human Currency Format Non-Scaled Grouping Notes
ar-SA (Arabic) 1٫25M ر.س.‏ 1٫25M 1٬000٬000 Uses unique Arabic separators.
hi-IN (Hindi) 1.25M ₹1.25M 10,00,000 Lakh/Crore grouping (10,00,000).
fr-FR (French) 1,25M 1,25M € 1 000 000 Space separator and trailing currency.
de-DE (German) 1,25M 1,25M € 1.000.000 Comma decimal and dot grouping.
am-ET (Amharic) 1.25M Br1.25M 1,000,000 Custom Ethiopian currency symbol.

Custom Words (Financial I18n)

For non-English support in financial word-formatting (Check Writing), implement the IWordsProvider interface:

public class SpanishWordsProvider : IWordsProvider
{
    public string NegativeWord => "Negativo";
    public string ConjunctionWord => "con";
    public string ToWords(decimal value) => "Mil Doscientos"; 
}

// Usage
1200m.ToHumanWords(provider: new SpanishWordsProvider()); // "Mil Doscientos"

🚀 Modernization & New Features (v2.0.2)

We have significantly hardened and optimized the codebase with robust enterprise-grade features:

  • Pluggable Currency Dependency Injection (ICurrencyMappingProvider): Map custom regional or business keys (like "EastAfrica", "AsiaPacific") to ISO currency codes by registering a custom provider to the DI container:
    builder.Services.AddSingleton<ICurrencyMappingProvider, MyCustomMappingProvider>();
    
  • O(1) Pre-Cached Currency Lookups: High-latency culture scanning is gone! Currency symbols are fully pre-cached, turning regional scans into O(1) dictionary lookups.
  • O(log N) Suffix Lookups: Support for large custom suffix arrays with O(log N) binary search threshold selection, retaining fast linear scanning for standard <= 8 suffix arrays.
  • Thread-Safe Option Mutations: Shared options registration utilizes record with-expressions, preventing thread cross-contamination during concurrent executions.
  • Circular Reference Protection: Dynamic serialization filter protects APIs from recursion crashes by tracing visited nodes via identity-based reference sets.

🧩 Policy-Driven Formatting (Enterprise Ready)

Define formatting rules once and enforce them across your entire infrastructure. Ensure that APIs, dashboards, and reports share the same magnitude logic and rounding rules.

// Setup central governance
builder.Services.AddHumanNumbersDefaults(options =>
{
    options.AddPolicy("Finance", new HumanNumberFormatOptions
    {
        DecimalPlaces = 2,
        PromotionThreshold = 1.0m // strict mode: 999,499 -> "999.50K"
    });
});

// Apply consistently across services
HumanNumber.Format(value).UsingPolicy("Finance").ToHuman();

🌍 Multi-Culture & Custom Magnitude Examples

The policy system handles unique numbering systems (like 10,000-based scaling) with ease:

builder.Services.AddHumanNumbersDefaults(options =>
{
    // Indian System: Lakhs (10^5) and Crores (10^7)
    options.AddPolicy("Indian", new HumanNumberFormatOptions
    {
        CachedCustomSuffixes = new[] {
            new MagnitudeSuffix(1000m, "K"),
            new MagnitudeSuffix(100_000m, "Lakh"),
            new MagnitudeSuffix(10_000_000m, "Crore")
        },
        Threshold = 1000m,
        DecimalPlaces = 2
    });

    // Chinese System: Wàn (10^4) and Yì (10^8)
    options.AddPolicy("Chinese", new HumanNumberFormatOptions
    {
        CachedCustomSuffixes = new[] {
            new MagnitudeSuffix(10_000m, "Wàn"),
            new MagnitudeSuffix(100_000_000m, "Yì")
        },
        Threshold = 10_000m
    });
});

// Usage
120000m.ToHuman(HumanNumbersConfig.Instance.GetPolicies()["Indian"]); // "1.20 Lakh"
120000m.ToHuman(HumanNumbersConfig.Instance.GetPolicies()["Chinese"]); // "12.00 Wàn"

Use CachedCustomSuffixes for non-standard scaling (like 10^4 or 10^5). For standard 10^3 scaling, simply use CustomSuffixes.


🚀 Real-World Scenario: Zero-Allocation Telemetry

In high-frequency logging or telemetry, even small string allocations can trigger GC pressure. HumanNumbers allows you to format directly into reusable buffers:

// Reusable buffer (stack or ArrayPool)
Span<char> buffer = stackalloc char[32];

if (largeValue.ToHuman(buffer, out var written))
{
    // Log directly from the span without creating a string
    logger.LogInformation("Metric: {Value}", buffer[..written]);
}

📊 Performance & Engineering Nuance

We benchmark against "Naive" implementations to provide an honest look at the costs of convenience.

Operation Latency Allocated Memory Engineering Note
Manual Concatenation ~60-80 ns 64-80 B Standard ToString() + "K" approach.
ToHuman() ~160-180 ns 56 B Balanced. Uses less memory than naive concatenation.
ToHuman (Span) ~150-170 ns 24 B Optimized. Significant memory reduction via Span paths.
TryParse ~40-50 ns 0 B Zero-Alloc. Fully allocation-free parsing.

Latency figures are environment-dependent. The "Naive" example reflects common real-world implementations, not optimized hand-written formatters.


🌐 ASP.NET Core: Safe by Default

Many libraries implicitly change JSON output globally, breaking API contracts. HumanNumbers is designed to be non-intrusive.

By default, HumanNumbers does not modify JSON output unless explicitly enabled via attributes or result extensions.

1. Register Policies

builder.Services.AddHumanNumbersDefaults(options => {
    options.AddPolicy("Compact", new HumanNumberFormatOptions { DecimalPlaces = 1 });
});

2. Selective Serialization

Control exactly what the client sees without breaking your DTOs.

public class AnalyticsDto {
    public decimal RawValue { get; set; } // JSON: 1500000

    [HumanNumber(OutputMode = HumanNumberOutputMode.SerializeAsHuman)]
    public decimal DisplayValue { get; set; } // JSON: "1.50M"
}

3. Explicit Transformations

In Minimal APIs, use HumanOk to trigger the transformation only when intended:

app.MapGet("/stats", () => Results.Extensions.HumanOk(new { Revenue = 1500000 }));

🧠 Design Philosophy

  • Predictable Performance: Every feature has a known and stable allocation profile.
  • Governance First: Use the Policy system to define "Brand Guidelines" for numbers once, then apply them everywhere.
  • Allocation Aware: We leverage Span<char> and ISpanFormattable to ensure that adding "humanity" to your data doesn't sink your GC performance.
  • Contract Safety: Your API types remain decimal. We only change the representation during the final serialization step.


🔄 Migration from NumberFormatter

HumanNumbers is the official successor to the legacy NumberFormatter package. It features a modernized API, significantly improved performance (via Span<char>), and a unified policy system.

Key Changes

  • Namespace: NumberFormatterHumanNumbers
  • Method Renaming:
    • ToShortString()ToHuman()
    • ToShortCurrencyString()ToHumanCurrency()
  • Binary Compatibility: A legacy shim is provided in the NumberFormatter namespace (marked as [Obsolete]) to help with a zero-friction transition.
// Legacy (NumberFormatter)
using NumberFormatter;
1500.ToShortString(); 

// Modern (HumanNumbers)
using HumanNumbers;
1500.ToHuman();

📦 Installation

dotnet add package HumanNumbers
dotnet add package HumanNumbers.AspNetCore

🙌 Contributing & License

Licensed under MIT. Contributions are welcome via Issues and Pull Requests.


📈 Appendix: Detailed Benchmarks

Detailed benchmarks below reflect full production runs and may differ slightly from summarized figures above. All results were generated using BenchmarkDotNet v0.14.0 on .NET 10.0 (X64 RyuJIT).

Method Scenario / Input Mean Gen 0 Allocated Engineering Context
StandardScaled Naive (999,499) 78.33 ns 0.0026 80 B Naive ToString + Concat approach.
ToHuman Governed (999,499) 172.79 ns 0.0024 56 B 30% less memory than naive.
ToHuman (Span) Governed (999,499) 152.09 ns 0.0007 24 B Optimized Span path.
TryParse $1.50M 50.86 ns - 0 B Zero-Alloc parsing.
ToHumanBytes 1024 Bytes 56.62 ns 0.0013 40 B Single materialized string.
ToHumanWords 1234.56 322.15 ns 0.0186 560 B Non-recursive assembly.
ToRoman 2024 31.79 ns 0.0013 40 B Optimized buffer path.

Key Takeaway: While HumanNumbers handles complex thresholds and rounding logic that manual code often misses, it does so with a smaller memory footprint than naive string concatenation approaches.


🔎 Keywords

human-readable numbers, number formatting, suffix formatting, K/M/B formatting, span formatting, zero allocation, .NET performance, telemetry formatting, financial formatting, magnitude formatting.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.2 81 5/20/2026
2.0.1 104 5/11/2026
2.0.0 92 5/10/2026
2.0.0-preview.1 57 4/27/2026