millify 1.1.0
dotnet add package millify --version 1.1.0
NuGet\Install-Package millify -Version 1.1.0
<PackageReference Include="millify" Version="1.1.0" />
<PackageVersion Include="millify" Version="1.1.0" />
<PackageReference Include="millify" />
paket add millify --version 1.1.0
#r "nuget: millify, 1.1.0"
#:package millify@1.1.0
#addin nuget:?package=millify&version=1.1.0
#tool nuget:?package=millify&version=1.1.0
Millify for .NET
Millify is a small .NET library that turns large counts and measurements into short, readable labels (2.5K, 1.44M, 1Mi, …). Use it anywhere you would otherwise hand-roll suffix logic: analytics tiles, tables, logs, charts, and mobile or web API payloads.
The NuGet package is named millify. This repository is millify-dotnet; the API lives in the MillifyDotnet namespace.
Conceptually similar to the millify package for JavaScript.
What you get
- One-call formatting via
Millify.Shortenforlong,double,decimal, andBigInteger. - Decimal (1000) or binary (1024) scaling with sensible default suffixes, including IEC-style units for byte-style values.
- Split “scale then format” via
Millify.DecomposeandMillify.FormatScaled(orMillifiedNumber.ToFormattedString) when you need the numeric magnitude for tooltips, charts, tests, or accessibility text. - Culture-aware decimal separators (separator only; digit grouping stays off so labels stay compact).
- Optional
Span<char>output viaMillify.TryFormatfor fixed buffers and reduced allocations. - Tunable presentation: precision, “smart” precision by magnitude, trimming of insignificant fractional zeros, custom unit lists, spacing before the suffix, and casing.
Install
Package ID: millify
dotnet add package millify
Package Manager Console:
Install-Package millify
Or add a PackageReference in your project file (pin a version in production apps):
<PackageReference Include="millify" Version="1.1.0" />
Quick start
using MillifyDotnet;
Millify.Shorten(2500); // 2.5K
Millify.Shorten(1_500_000); // 1.5M
Millify.Shorten(-5_300_000); // -5.3M
Millify.Shorten(1234.5m); // 1.2K (default precision is 1)
Default options trim trailing fractional zeros (for example 1.0M becomes 1M). Set TrimInsignificantZeros = false on MillifyOptions if you want a fixed-width fractional part.
Common uses
Custom suffixes and layout
var options = new MillifyOptions(
precision: 2,
lowercase: true,
spaceBeforeUnit: true,
units: new[] { "", "k", "m", "b", "t" });
Millify.Shorten(1_440_000, options); // "1.44 m"
Byte-style values (1024 steps, IEC-style defaults)
When ScaleBase is Binary and you do not pass units, suffixes default to IEC-style (Ki, Mi, Gi, …) with appropriate casing.
var bytes = new MillifyOptions(precision: 2, scaleBase: MillifyScaleBase.Binary);
Millify.Shorten(1_048_576, bytes); // 1Mi
Localized decimal separator
Culture affects the decimal separator only (for example 1,2K with fr-FR). Thousands separators are not inserted.
using System.Globalization;
using MillifyDotnet;
var fr = new MillifyOptions(precision: 1, culture: CultureInfo.GetCultureInfo("fr-FR"));
Millify.Shorten(1234.5m, fr); // "1,2K" — comma as decimal separator
Decompose for UI logic
using System.Globalization;
using MillifyDotnet;
var options = new MillifyOptions(precision: 2, culture: CultureInfo.GetCultureInfo("fr-FR"));
var parts = Millify.Decompose(9_990, options);
var label = parts.ToFormattedString(options); // same formatting rules as Shorten
var same = Millify.FormatScaled(parts, options);
// parts.ScaledValue — signed magnitude after scaling
// parts.UnitIndex — index into options.Units for the active suffix
TryFormat into a buffer
Span<char> buffer = stackalloc char[32];
if (Millify.TryFormat(1234.5m, buffer, out var written))
{
var text = buffer[..written].ToString();
}
Overloads exist for decimal, long, double, and BigInteger. On netstandard1.6 and netstandard2.0, the library references System.Memory so Span<char> is available.
Smarter fractions on large magnitudes
Enable SmartPrecision to reduce fractional digits when the scaled value is large (for denser dashboards), while keeping finer detail when the scaled magnitude is small. Behavior is still capped by Precision.
var dense = new MillifyOptions(precision: 2, smartPrecision: true);
Millify.Shorten(9_990, dense); // 9.99K — full precision while scaled value < 10
Millify.Shorten(125_000_000, dense); // 125M — no fractional digits once scaled value ≥ 100
API at a glance
| Member | Role |
|---|---|
Millify.Shorten(...) |
Format a number to a single string with suffix. |
Millify.Decompose(...) |
Return MillifiedNumber (scaled value + unit index) without final string formatting. |
Millify.FormatScaled(MillifiedNumber, ...) |
Format a decomposed value with the same rules as Shorten. |
MillifiedNumber.ToFormattedString(...) |
Instance helper equivalent to FormatScaled. |
Millify.TryFormat(..., Span<char>, ...) |
Write the same string as Shorten into a buffer; returns false if the buffer is too small. |
MillifyOptions |
Precision, casing, spacing, custom Units, ScaleBase, trimming, smart precision, Culture. |
MillifyScaleBase |
Decimal (1000) or Binary (1024) steps between tiers. |
MillifyOptions reference
| Option | Type | Default | Description |
|---|---|---|---|
Precision |
int |
1 |
Maximum fractional digits (F precision). Must be >= 1. |
Lowercase |
bool |
false |
When true, suffixes are lowercased. When false, decimal single-letter units are uppercased; binary ki-style units become Ki, Mi, etc. |
SpaceBeforeUnit |
bool |
false |
Inserts a space between the number and the suffix. |
Units |
string[] |
See below | Suffix per magnitude, index 0 for the unscaled tier. Must be non-empty and must not contain null entries. |
ScaleBase |
MillifyScaleBase |
Decimal (1000) |
Binary (1024) for IEC-style scaling. |
TrimInsignificantZeros |
bool |
true |
Removes trailing fractional zeros (for example 2.50K → 2.5K). |
SmartPrecision |
bool |
false |
When true, fractional digits are reduced for larger scaled magnitudes: scaled absolute value >= 100 → 0 decimals; [10, 100) → up to 1 (still capped by Precision); below 10 (including values < 1) → full Precision. |
Culture |
CultureInfo? |
null |
Supplies the decimal separator only (invariant when null). Digit grouping is disabled so only the separator changes (for example 1,2K in fr-FR). |
Constructor parameter order (all have defaults except where noted):
precision, lowercase, spaceBeforeUnit, units, scaleBase, trimInsignificantZeros, smartPrecision, culture.
Default Units
When you do not pass units:
ScaleBase.Decimal:{ "", "k", "m", "g", "t", "p", "e", "z", "y" }(shown asK,M,G, … whenLowercaseisfalse).ScaleBase.Binary:{ "", "ki", "mi", "gi", "ti", "pi", "ei", "zi", "yi" }(shown asKi,Mi,Gi, … whenLowercaseisfalse).
BigInteger behavior
Shorten and Decompose compute the scaled value as (decimal)abs(n) / (decimal)pow(scaleBase, unitIndex) when that fits in decimal. If the cast overflows, a double ratio is used; if the value is still not representable, an OverflowException is thrown with guidance to add more Units tiers so the value can be scaled further.
Validation
Units is validated when options are constructed and whenever the Units property is assigned: the array must contain at least one entry, and no element may be null.
Target frameworks
The library targets:
netstandard1.6netstandard2.0netstandard2.1
netstandard1.6 and netstandard2.0 builds reference System.Memory for Span<char> support in TryFormat.
Tests in this repository target a current .NET SDK (see CI); your app only needs a TFM compatible with the standards above.
Contributing
Issues and pull requests are welcome. The millify package ships this README from the repository root.
Build and test (from the repository root):
cd src
dotnet restore
dotnet build --configuration Release
dotnet test --configuration Release
CI runs the same steps under Ubuntu with the .NET 10 SDK. The solution file is src/millify.slnx.
Layout
src/millify— library source (MillifyDotnetnamespace)src/millify.Tests— xUnit tests
License
MIT. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
| .NET Core | netcoreapp1.0 was computed. netcoreapp1.1 was computed. netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard1.6 is compatible. netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen30 was computed. tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 1.6
- System.Memory (>= 4.5.5)
-
.NETStandard 2.0
- System.Memory (>= 4.5.5)
-
.NETStandard 2.1
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
- Add optional trimming of insignificant fractional zeros (default on; set TrimInsignificantZeros=false to preserve fixed-width fractions).
- Add SmartPrecision to reduce fractional digits for larger scaled magnitudes while keeping finer detail near 1–10.
- Add MillifyScaleBase (decimal 1000 vs binary 1024) with IEC-style default suffixes for binary mode.
- Add BigInteger Shorten/Decompose with exact decimal scaling when representable, plus a controlled overflow path.
- Add CultureInfo support for the decimal separator only (digit grouping stays disabled).
- Add MillifiedNumber plus Decompose/FormatScaled for split formatting workflows (tooltips, charts, a11y).
- Add TryFormat overloads writing into Span (System.Memory referenced on netstandard1.6 and netstandard2.0).
- Validate Units (non-empty, no null entries) on construction and when the Units property is set.
- Fix incorrect casing logic that previously called ToUpperInvariant on the numeric text when Lowercase was true.