< Summary

Information
Class: AmbientServices.Utilities.SI
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/SI.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 39
Uncovered lines: 0
Coverable lines: 39
Total lines: 209
Line coverage: 100%
Branch coverage
100%
Covered branches: 84
Total branches: 84
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
ToSi(...)100%8484100%
ToSi(...)100%11100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/SI.cs

#LineLine coverage
 1using System;
 2
 3namespace AmbientServices.Utilities;
 4
 5/// <summary>
 6/// A static class to hold units and unit conversions for the International System of Units (SI).
 7/// See https://en.wikipedia.org/wiki/Metric_prefix for definitions.
 8/// </summary>
 9/// <remarks>
 10/// <pitch>Named constants for every SI magnitude prefix (quecto through quetta) plus <see cref="ToSi(double, int, strin
 11/// <pledge>
 12/// Formatting targets a fixed character budget for the numeric part (default 4, i.e. three significant digits), choosin
 13/// Non-finite and extreme values render as sentinels (<c>NaN</c>, <c>INF</c>/<c>-INF</c>, <c>EPS</c>/<c>-EPS</c>, <c>MA
 14/// </pledge>
 15/// <plan>Prefix tables indexed by how many divide-or-multiply-by-1000 steps normalize the value into [1, 999.5), with e
 16/// <priority>
 17/// 1. A predictable column width over exact digits: output is fitted to a fixed character budget for the numeric part, 
 18/// 2. Always rendering something over refusing a value it cannot represent: non-finite and extreme inputs come back as 
 19/// </priority>
 20/// </remarks>
 21public static class SI
 22{
 23    /// <summary>
 24    /// A multiplier equivalent to the SI prefix of the corresponding name.
 25    /// </summary>
 26    public const double Quecto = .001 * .001 * .001 * .001 * .001 * .001 * .001 * .001 * .001 * .001;
 27    /// <summary>
 28    /// A multiplier equivalent to the SI prefix of the corresponding name.
 29    /// </summary>
 30    public const double Ronto = .001 * .001 * .001 * .001 * .001 * .001 * .001 * .001 * .001;
 31    /// <summary>
 32    /// A multiplier equivalent to the SI prefix of the corresponding name.
 33    /// </summary>
 34    public const double Yocto = .001 * .001 * .001 * .001 * .001 * .001 * .001 * .001;
 35    /// <summary>
 36    /// A multiplier equivalent to the SI prefix of the corresponding name.
 37    /// </summary>
 38    public const double Zepto = .001 * .001 * .001 * .001 * .001 * .001 * .001;
 39    /// <summary>
 40    /// A multiplier equivalent to the SI prefix of the corresponding name.
 41    /// </summary>
 42    public const double Atto = .001 * .001 * .001 * .001 * .001 * .001;
 43    /// <summary>
 44    /// A multiplier equivalent to the SI prefix of the corresponding name.
 45    /// </summary>
 46    public const double Femto = .001 * .001 * .001 * .001 * .001;
 47    /// <summary>
 48    /// A multiplier equivalent to the SI prefix of the corresponding name.
 49    /// </summary>
 50    public const double Pico = .001 * .001 * .001 * .001;
 51    /// <summary>
 52    /// A multiplier equivalent to the SI prefix of the corresponding name.
 53    /// </summary>
 54    public const double Nano = .001 * .001 * .001;
 55    /// <summary>
 56    /// A multiplier equivalent to the SI prefix of the corresponding name.
 57    /// </summary>
 58    public const double Micro = .001 * .001;
 59    /// <summary>
 60    /// A multiplier equivalent to the SI prefix of the corresponding name.
 61    /// </summary>
 62    public const double Milli = .001;
 63    /// <summary>
 64    /// A multiplier equivalent to the SI prefix of the corresponding name.
 65    /// </summary>
 66    public const double Centi = .01;
 67    /// <summary>
 68    /// A multiplier equivalent to the SI prefix of the corresponding name.
 69    /// </summary>
 70    public const double Deci = .1;
 71
 72    /// <summary>
 73    /// A multiplier equivalent to the SI prefix of the corresponding name.
 74    /// </summary>
 75    public const int Kilo = 1000;
 76    /// <summary>
 77    /// A multiplier equivalent to the SI prefix of the corresponding name.
 78    /// </summary>
 79    public const int Mega = 1000 * 1000;
 80    /// <summary>
 81    /// A multiplier equivalent to the SI prefix of the corresponding name.
 82    /// </summary>
 83    public const int Giga = 1000 * 1000 * 1000;
 84    /// <summary>
 85    /// A multiplier equivalent to the SI prefix of the corresponding name.
 86    /// </summary>
 87    public const long Tera = 1000L * 1000L * 1000L * 1000L;
 88    /// <summary>
 89    /// A multiplier equivalent to the SI prefix of the corresponding name.
 90    /// </summary>
 91    public const long Peta = 1000L * 1000L * 1000L * 1000L * 1000L;
 92    /// <summary>
 93    /// A multiplier equivalent to the SI prefix of the corresponding name.
 94    /// </summary>
 95    public const long Exa = 1000L * 1000L * 1000L * 1000L * 1000L * 1000L;
 96    /// <summary>
 97    /// A multiplier equivalent to the SI prefix of the corresponding name.
 98    /// </summary>
 99    public const double Zetta = 1000.0 * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L;
 100    /// <summary>
 101    /// A multiplier equivalent to the SI prefix of the corresponding name.
 102    /// </summary>
 103    public const double Yotta = 1000.0 * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L;
 104    /// <summary>
 105    /// A multiplier equivalent to the SI prefix of the corresponding name.
 106    /// </summary>
 107    public const double Ronna = 1000.0 * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L;
 108    /// <summary>
 109    /// A multiplier equivalent to the SI prefix of the corresponding name.
 110    /// </summary>
 111    public const double Quetta = 1000.0 * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L * 1000L;
 112
 2113    private static readonly string[] sSmallSiPrefixes = { "milli", "micro", "nano", "pico", "femto", "atto", "zepto", "y
 2114    private static readonly string[] sShortSmallSiPrefixes = { "m", "μ", "n", "p", "f", "a", "z", "y", "r", "q" };
 2115    private static readonly string[] sLargeSiPrefixes = { "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta
 2116    private static readonly string[] sShortLargeSiPrefixes = { "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q" };     /
 117    private const int QuectoPrefixIndex = 9;
 118    private const int QuettaPrefixIndex = 9;
 119    /// <summary>
 120    /// Gets a string containing an abbreviated version of a number using the International System of Units (SI).
 121    /// SI Units are exact decimal units, mostly powers of 1000.
 122    /// </summary>
 123    /// <param name="number">The number to output.</param>
 124    /// <param name="maxCharacters">The maximum number of characters to use to represent the numeric part (if possible),
 125    /// <param name="postfix">A postfix string (for example, "B").</param>
 126    /// <param name="longName">Whether or not to use the long version of the Si prefix (kilo, mega, etc.)</param>
 127    /// <param name="positiveSign">Whether or not to include a positive sign on positive numbers.</param>
 128    /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> to use to format the number, defaults t
 129    /// <returns>The SI representation of the number, for example for 5342432, the return value might be "5.34MB".</retu
 130    public static string ToSi(this double number, int maxCharacters = 4, string? postfix = null, bool longName = false, 
 131    {
 2132        if (culture == null) culture = System.Threading.Thread.CurrentThread.CurrentCulture;
 2133        if (postfix == null) postfix = string.Empty;
 2134        if (double.IsNaN(number)) return "NaN";
 2135        if (double.IsNegativeInfinity(number)) return "-INF";
 2136        if (double.IsPositiveInfinity(number)) return positiveSign ? "+INF" : "INF";
 137        // small non-zero number?
 2138        if (number != 0.0 && number > -0.9995 && number < 0.9995)
 139        {
 2140            if (number == -double.Epsilon) return "-EPS";
 2141            if (number == double.Epsilon) return positiveSign ? "+EPS" : "EPS";
 2142            string[] prefixes = longName ? sSmallSiPrefixes : sShortSmallSiPrefixes;
 2143            string extraQuectos = "";
 2144            while (number < Quecto && number > -1.0 * Quecto)
 145            {
 2146                extraQuectos += prefixes[QuectoPrefixIndex];
 2147                number /= Quecto;
 148            }
 149            int magnitude;
 2150            for (magnitude = 0; magnitude < sSmallSiPrefixes.Length && number > -0.9995 && number < 0.9995; ++magnitude)
 151            {
 2152                number *= 1000.0;
 153            }
 154            // handle a weird floating point rounding situation where we end up with 999.5 here
 2155            if (number <= -999.5 || number >= 999.5)
 156            {
 157                // undo the last multiplication and round (this should correct for the problem)
 2158                number = Math.Round(number / 1000.0, 15);
 2159                --magnitude;
 160            }
 161            System.Diagnostics.Debug.Assert(number > -999.5 && number < 999.5);
 2162            int digitsAfterDecimal = Math.Max(0, maxCharacters - (int)Math.Log10(Math.Abs(number)) - 2);     // Log10 gi
 2163            return ((positiveSign && number > 0.0) ? "+" : "") + number.ToString("N0" + digitsAfterDecimal.ToString(Syst
 164        }
 165        // large number?
 2166        else if (number <= -999.5 || number >= 999.5)
 167        {
 2168            if (number == double.MinValue) return "-MAX";
 2169            if (number == double.MaxValue) return positiveSign ? "+MAX" : "MAX";
 2170            string[] prefixes = longName ? sLargeSiPrefixes : sShortLargeSiPrefixes;
 2171            string extraQuettas = "";
 2172            while (number >= 1000.0 * Quetta|| number <= -1000.0 * Quetta)
 173            {
 2174                extraQuettas += prefixes[QuettaPrefixIndex];
 2175                number /= Quetta;
 176            }
 177            int magnitude;
 2178            for (magnitude = 0; magnitude < sLargeSiPrefixes.Length && (number <= -999.5 || number >= 999.5); ++magnitud
 179            {
 2180                number /= 1000.0;
 181            }
 182            // note that the rounding issue in the corresponding algorithm above doesn't happen when we're going this di
 183            System.Diagnostics.Debug.Assert(number > -999.5 && number < 999.5);
 2184            int digitsAfterDecimal = Math.Max(0, maxCharacters - (int)Math.Log10(Math.Abs(number)) - 2);     // Log10 gi
 2185            return ((positiveSign && number > 0.0) ? "+" : "") + number.ToString("N0" + digitsAfterDecimal.ToString(Syst
 186        }
 187        else // just a normal number!
 188        {
 189            System.Diagnostics.Debug.Assert(number > -999.5 && number < 999.5);
 2190            int digitsAfterDecimal =  Math.Max(0, maxCharacters - ((number == 0) ? 0 : (int)Math.Log10(Math.Abs(number))
 2191            return ((positiveSign && number > 0.0) ? "+" : "") + number.ToString("N0" + digitsAfterDecimal.ToString(Syst
 192        }
 193    }
 194    /// <summary>
 195    /// Gets a string containing an abbreviated version of a number using the International System of Units (SI).
 196    /// SI Units are exact decimal units, mostly powers of 1000.
 197    /// </summary>
 198    /// <param name="number">The number to output.</param>
 199    /// <param name="maxCharacters">The maximum number of characters to use to represent the numeric part (if possible),
 200    /// <param name="postfix">A postfix string (for example, "B").</param>
 201    /// <param name="longName">Whether or not to use the long version of the Si prefix (kilo, mega, etc.)</param>
 202    /// <param name="positiveSign">Whether or not to include a positive sign on positive numbers.</param>
 203    /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> to use to format the number, defaults t
 204    /// <returns>The SI representation of the number, for example for 5342432, the return value might be "5.34MB".</retu
 205    public static string ToSi(this float number, int maxCharacters = 4, string? postfix = null, bool longName = false, b
 206    {
 2207        return ToSi((double)number, maxCharacters, postfix, longName, positiveSign, culture);
 208    }
 209}