diff --git a/src/Humanizer.Tests.Shared/MetricNumeralTests.cs b/src/Humanizer.Tests.Shared/MetricNumeralTests.cs
index 0f1892018..5ad53ed81 100644
--- a/src/Humanizer.Tests.Shared/MetricNumeralTests.cs
+++ b/src/Humanizer.Tests.Shared/MetricNumeralTests.cs
@@ -103,12 +103,100 @@ public void TestAllSymbolsAsInt(int exponent)
[InlineData("-3.9m", -3.91e-3, false, true, 1)]
[InlineData("10 ", 10, true, false, 0)]
[InlineData("1.2", 1.23, false, false, 1)]
- public void ToMetric(string expected, double input, bool hasSpace, bool useSymbol, int? decimals)
- {
+ public void ToMetricObsolete(string expected, double input, bool hasSpace, bool useSymbol, int? decimals)
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
Assert.Equal(expected, input.ToMetric(hasSpace, useSymbol, decimals));
+#pragma warning restore CS0618 // Type or member is obsolete
}
-
+ [Theory]
+ [InlineData("1.3M", 1300000, null, null)]
+ [InlineData("1.3million", 1300000, MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1.3 million", 1300000, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1.3 million", 1300000, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("0", 0d, null, null)]
+ [InlineData("123", 123d, null, null)]
+ [InlineData("-123", -123d, null, null)]
+ [InlineData("1.23k", 1230d, null, null)]
+ [InlineData("1 k", 1000d, MetricNumeralFormats.WithSpace, null)]
+ [InlineData("1milli", 1E-3, MetricNumeralFormats.UseName, null)]
+ [InlineData("1.23milli", 1.234E-3, MetricNumeralFormats.UseName, 2)]
+ [InlineData("12.34k", 12345, null, 2)]
+ [InlineData("12k", 12345, null, 0)]
+ [InlineData("-3.9m", -3.91e-3, null, 1)]
+ [InlineData("10 ", 10, MetricNumeralFormats.WithSpace, 0)]
+ [InlineData("1.2", 1.23, null, 1)]
+ [InlineData("1thousand", 1000d, MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1.23 thousand", 1230d, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1Y", 1E24, null, null)]
+ [InlineData("1 yotta", 1E24, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 septillion", 1E24, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 quadrillion", 1E24, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1Z", 1E21, null, null)]
+ [InlineData("1 zetta", 1E21, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 sextillion", 1E21, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 trilliard", 1E21, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1E", 1E18, null, null)]
+ [InlineData("1 exa", 1E18, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 quintillion", 1E18, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 trillion", 1E18, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1P", 1E15, null, null)]
+ [InlineData("1 peta", 1E15, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 quadrillion", 1E15, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 billiard", 1E15, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1T", 1E12, null, null)]
+ [InlineData("1 tera", 1E12, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 trillion", 1E12, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 billion", 1E12, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1G", 1E9, null, null)]
+ [InlineData("1 giga", 1E9, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 billion", 1E9, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 milliard", 1E9, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1M", 1E6, null, null)]
+ [InlineData("1 mega", 1E6, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 million", 1E6, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 million", 1E6, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1k", 1E3, null, null)]
+ [InlineData("1 kilo", 1E3, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 thousand", 1E3, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 thousand", 1E3, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1y", 1E-24, null, null)]
+ [InlineData("1 yocto", 1E-24, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 septillionth", 1E-24, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 quadrillionth", 1E-24, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1z", 1E-21, null, null)]
+ [InlineData("1 zepto", 1E-21, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 sextillionth", 1E-21, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 trilliardth", 1E-21, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1a", 1E-18, null, null)]
+ [InlineData("1 atto", 1E-18, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 quintillionth", 1E-18, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 trillionth", 1E-18, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1f", 1E-15, null, null)]
+ [InlineData("1 femto", 1E-15, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 quadrillionth", 1E-15, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 billiardth", 1E-15, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1p", 1E-12, null, null)]
+ [InlineData("1 pico", 1E-12, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 trillionth", 1E-12, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 billionth", 1E-12, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1n", 1E-9, null, null)]
+ [InlineData("1 nano", 1E-9, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 billionth", 1E-9, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 milliardth", 1E-9, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1μ", 1E-6, null, null)]
+ [InlineData("1 micro", 1E-6, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 millionth", 1E-6, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 millionth", 1E-6, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ [InlineData("1m", 1E-3, null, null)]
+ [InlineData("1 milli", 1E-3, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseName, null)]
+ [InlineData("1 thousandth", 1E-3, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseShortScaleWord, null)]
+ [InlineData("1 thousandth", 1E-3, MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseLongScaleWord, null)]
+ public void ToMetric(string expected, double input, MetricNumeralFormats? format, int? decimals)
+ {
+ Assert.Equal(expected, input.ToMetric(format, decimals));
+ }
[Theory]
[InlineData(1E+27)]
diff --git a/src/Humanizer/MetricNumeralExtensions.cs b/src/Humanizer/MetricNumeralExtensions.cs
index 271f6b45d..76d1a9f9a 100644
--- a/src/Humanizer/MetricNumeralExtensions.cs
+++ b/src/Humanizer/MetricNumeralExtensions.cs
@@ -53,7 +53,7 @@ static MetricNumeralExtensions()
};
///
- /// Names link a Metric symbol (as key) to its name (as value).
+ /// UnitPrefixes link a Metric symbol (as key) to its prefix (as value).
///
///
/// We dont support :
@@ -62,11 +62,26 @@ static MetricNumeralExtensions()
/// {'d', "deci" },
/// {'c', "centi"},
///
- private static readonly Dictionary Names = new Dictionary
- {
- {'Y', "yotta" }, {'Z', "zetta" }, {'E', "exa" }, {'P', "peta" }, {'T', "tera" }, {'G', "giga" }, {'M', "mega" }, {'k', "kilo" },
- {'m', "milli" }, {'μ', "micro" }, {'n', "nano" }, {'p', "pico" }, {'f', "femto" }, {'a', "atto" }, {'z', "zepto" }, {'y', "yocto" }
- };
+ private static readonly Dictionary UnitPrefixes = new Dictionary
+ {
+ {'Y', new UnitPrefix("yotta", "septillion", "quadrillion")},
+ {'Z', new UnitPrefix("zetta", "sextillion", "trilliard")},
+ {'E', new UnitPrefix("exa", "quintillion", "trillion")},
+ {'P', new UnitPrefix("peta", "quadrillion", "billiard")},
+ {'T', new UnitPrefix("tera", "trillion", "billion")},
+ {'G', new UnitPrefix("giga", "billion", "milliard")},
+ {'M', new UnitPrefix("mega", "million")},
+ {'k', new UnitPrefix("kilo", "thousand")},
+
+ {'m', new UnitPrefix("milli", "thousandth")},
+ {'μ', new UnitPrefix("micro", "millionth")},
+ {'n', new UnitPrefix("nano", "billionth", "milliardth")},
+ {'p', new UnitPrefix("pico", "trillionth", "billionth")},
+ {'f', new UnitPrefix("femto", "quadrillionth", "billiardth")},
+ {'a', new UnitPrefix("atto", "quintillionth", "trillionth")},
+ {'z', new UnitPrefix("zepto", "sextillionth", "trilliardth")},
+ {'y', new UnitPrefix("yocto", "septillionth", "quadrillionth")}
+ };
///
/// Converts a Metric representation into a number.
@@ -109,11 +124,36 @@ public static double FromMetric(this string input)
///
///
/// A valid Metric representation
- public static string ToMetric(this int input, bool hasSpace = false, bool useSymbol = true, int? decimals = null)
+ [Obsolete("Please use overload with MetricNumeralFormats")]
+ public static string ToMetric(this int input, bool hasSpace, bool useSymbol = true, int? decimals = null)
{
return ((double)input).ToMetric(hasSpace, useSymbol, decimals);
}
+
+ ///
+ /// Converts a number into a valid and Human-readable Metric representation.
+ ///
+ ///
+ /// Inspired by a snippet from Thom Smith.
+ /// See this link for more.
+ ///
+ /// Number to convert to a Metric representation.
+ /// A bitwise combination of enumeration values that format the metric representation.
+ /// If not null it is the numbers of decimals to round the number to
+ ///
+ ///
+ /// 1000.ToMetric() => "1k"
+ /// 123.ToMetric() => "123"
+ /// 1E-1.ToMetric() => "100m"
+ ///
+ ///
+ /// A valid Metric representation
+ public static string ToMetric(this int input, MetricNumeralFormats? formats = null, int? decimals = null)
+ {
+ return ((double)input).ToMetric(formats, decimals);
+ }
+
///
/// Converts a number into a valid and Human-readable Metric representation.
///
@@ -133,7 +173,41 @@ public static string ToMetric(this int input, bool hasSpace = false, bool useSym
///
///
/// A valid Metric representation
- public static string ToMetric(this double input, bool hasSpace = false, bool useSymbol = true, int? decimals = null)
+ [Obsolete("Please use overload with MetricNumeralFormats")]
+ public static string ToMetric(this double input, bool hasSpace, bool useSymbol = true, int? decimals = null)
+ {
+ var formats = (MetricNumeralFormats?)null;
+ if (hasSpace)
+ {
+ formats = MetricNumeralFormats.WithSpace;
+ }
+ if (!useSymbol)
+ {
+ formats = formats.HasValue ? formats | MetricNumeralFormats.UseName
+ : MetricNumeralFormats.UseName;
+ }
+ return ToMetric(input, formats, decimals);
+ }
+
+ ///
+ /// Converts a number into a valid and Human-readable Metric representation.
+ ///
+ ///
+ /// Inspired by a snippet from Thom Smith.
+ /// See this link for more.
+ ///
+ /// Number to convert to a Metric representation.
+ /// A bitwise combination of enumeration values that format the metric representation.
+ /// If not null it is the numbers of decimals to round the number to
+ ///
+ ///
+ /// 1000d.ToMetric() => "1k"
+ /// 123d.ToMetric() => "123"
+ /// 1E-1.ToMetric() => "100m"
+ ///
+ ///
+ /// A valid Metric representation
+ public static string ToMetric(this double input, MetricNumeralFormats? formats = null, int? decimals = null)
{
if (input.Equals(0))
{
@@ -145,7 +219,7 @@ public static string ToMetric(this double input, bool hasSpace = false, bool use
throw new ArgumentOutOfRangeException(nameof(input));
}
- return BuildRepresentation(input, hasSpace, useSymbol, decimals);
+ return BuildRepresentation(input, formats, decimals);
}
///
@@ -206,25 +280,24 @@ private static double BuildMetricNumber(string input, char last)
/// A metric representation with a symbol
private static string ReplaceNameBySymbol(string input)
{
- return Names.Aggregate(input, (current, name) =>
- current.Replace(name.Value, name.Key.ToString()));
+ return UnitPrefixes.Aggregate(input, (current, unitPrefix) =>
+ current.Replace(unitPrefix.Value.Name, unitPrefix.Key.ToString()));
}
///
/// Build a Metric representation of the number.
///
/// Number to convert to a Metric representation.
- /// True will split the number and the symbol with a whitespace.
- /// True will use symbol instead of name
+ /// A bitwise combination of enumeration values that format the metric representation.
/// If not null it is the numbers of decimals to round the number to
/// A number in a Metric representation
- private static string BuildRepresentation(double input, bool hasSpace, bool useSymbol, int? decimals)
+ private static string BuildRepresentation(double input, MetricNumeralFormats? formats, int? decimals)
{
var exponent = (int)Math.Floor(Math.Log10(Math.Abs(input)) / 3);
- if (!exponent.Equals(0)) return BuildMetricRepresentation(input, exponent, hasSpace, useSymbol, decimals);
+ if (!exponent.Equals(0)) return BuildMetricRepresentation(input, exponent, formats, decimals);
var representation = decimals.HasValue ? Math.Round(input, decimals.Value).ToString() : input.ToString();
- if (hasSpace)
+ if ((formats & MetricNumeralFormats.WithSpace) == MetricNumeralFormats.WithSpace)
{
representation += " ";
}
@@ -236,11 +309,10 @@ private static string BuildRepresentation(double input, bool hasSpace, bool useS
///
/// Number to convert to a Metric representation.
/// Exponent of the number in a scientific notation
- /// True will split the number and the symbol with a whitespace.
- /// True will use symbol instead of name
+ /// A bitwise combination of enumeration values that format the metric representation.
/// If not null it is the numbers of decimals to round the number to
/// A number in a Metric representation
- private static string BuildMetricRepresentation(double input, int exponent, bool hasSpace, bool useSymbol, int? decimals)
+ private static string BuildMetricRepresentation(double input, int exponent, MetricNumeralFormats? formats, int? decimals)
{
var number = input * Math.Pow(1000, -exponent);
if (decimals.HasValue)
@@ -252,19 +324,34 @@ private static string BuildMetricRepresentation(double input, int exponent, bool
? Symbols[0][exponent - 1]
: Symbols[1][-exponent - 1];
return number
- + (hasSpace ? " " : string.Empty)
- + GetUnit(symbol, useSymbol);
+ + (formats.HasValue && formats.Value.HasFlag(MetricNumeralFormats.WithSpace) ? " " : string.Empty)
+ + GetUnitText(symbol, formats);
}
///
/// Get the unit from a symbol of from the symbol's name.
///
/// The symbol linked to the unit
- /// True will use symbol instead of name
- /// A symbol or a symbol's name
- private static string GetUnit(char symbol, bool useSymbol)
+ /// A bitwise combination of enumeration values that format the metric representation.
+ /// A symbol, a symbol's name, a symbol's short scale word or a symbol's long scale word
+ private static string GetUnitText(char symbol, MetricNumeralFormats? formats)
{
- return useSymbol ? symbol.ToString() : Names[symbol];
+ if (formats.HasValue
+ && formats.Value.HasFlag(MetricNumeralFormats.UseName))
+ {
+ return UnitPrefixes[symbol].Name;
+ }
+ if (formats.HasValue
+ && formats.Value.HasFlag(MetricNumeralFormats.UseShortScaleWord))
+ {
+ return UnitPrefixes[symbol].ShortScaleWord;
+ }
+ if (formats.HasValue
+ && formats.Value.HasFlag(MetricNumeralFormats.UseLongScaleWord))
+ {
+ return UnitPrefixes[symbol].LongScaleWord;
+ }
+ return symbol.ToString();
}
///
@@ -297,5 +384,21 @@ private static bool IsInvalidMetricNumeral(this string input)
var isSymbol = Symbols[0].Contains(last) || Symbols[1].Contains(last);
return !double.TryParse(isSymbol ? input.Remove(index) : input, out var number);
}
+
+ private struct UnitPrefix
+ {
+ private readonly string _longScaleWord;
+
+ public string Name { get; }
+ public string ShortScaleWord { get; }
+ public string LongScaleWord => _longScaleWord ?? ShortScaleWord;
+
+ public UnitPrefix(string name, string shortScaleWord, string longScaleWord = null)
+ {
+ Name = name;
+ ShortScaleWord = shortScaleWord;
+ _longScaleWord = longScaleWord;
+ }
+ }
}
}
diff --git a/src/Humanizer/MetricNumeralFormats.cs b/src/Humanizer/MetricNumeralFormats.cs
new file mode 100644
index 000000000..a15dcba88
--- /dev/null
+++ b/src/Humanizer/MetricNumeralFormats.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Humanizer
+{
+ ///
+ /// Flags for formatting the metric representation of numerals.
+ ///
+ [Flags]
+ public enum MetricNumeralFormats
+ {
+ ///
+ /// Use the metric prefix long scale word.
+ ///
+ UseLongScaleWord = 1,
+
+ ///
+ /// Use the metric prefix name instead of the symbol.
+ ///
+ UseName = 2,
+
+ ///
+ /// Use the metric prefix short scale word.
+ ///
+ UseShortScaleWord = 4,
+
+ ///
+ /// Include a space after the numeral.
+ ///
+ WithSpace = 8
+ }
+}