using System; namespace SabreTools.Core.Tools { public static class NumberHelper { #region Constants #region Byte (1000-based) size comparisons private const long KiloByte = 1000; private readonly static long MegaByte = (long)Math.Pow(KiloByte, 2); private readonly static long GigaByte = (long)Math.Pow(KiloByte, 3); private readonly static long TeraByte = (long)Math.Pow(KiloByte, 4); private readonly static long PetaByte = (long)Math.Pow(KiloByte, 5); // The following are too big to be represented in Int64 // private readonly static long ExaByte = (long)Math.Pow(KiloByte, 6); // private readonly static long ZettaByte = (long)Math.Pow(KiloByte, 7); // private readonly static long YottaByte = (long)Math.Pow(KiloByte, 8); #endregion #region Byte (1024-based) size comparisons private const long KibiByte = 1024; private readonly static long MibiByte = (long)Math.Pow(KibiByte, 2); private readonly static long GibiByte = (long)Math.Pow(KibiByte, 3); private readonly static long TibiByte = (long)Math.Pow(KibiByte, 4); private readonly static long PibiByte = (long)Math.Pow(KibiByte, 5); // The following are too big to be represented in Int64 // private readonly static long ExiByte = (long)Math.Pow(KibiByte, 6); // private readonly static long ZittiByte = (long)Math.Pow(KibiByte, 7); // private readonly static long YittiByte = (long)Math.Pow(KibiByte, 8); #endregion #endregion /// /// Convert a string to a Double /// public static double? ConvertToDouble(string? numeric) { // If we don't have a valid string, we can't do anything if (string.IsNullOrEmpty(numeric)) return null; if (!double.TryParse(numeric, out double doubleValue)) return null; return doubleValue; } /// /// Convert a string to an Int64 /// public static long? ConvertToInt64(string? numeric) { // If we don't have a valid string, we can't do anything if (string.IsNullOrEmpty(numeric)) return null; // Normalize the string for easier comparison numeric = numeric!.ToLowerInvariant(); // Parse the numeric string, if possible if (numeric.StartsWith("0x")) { return Convert.ToInt64(numeric.Substring(2), 16); } else { // Get the multiplication modifier and trim characters long multiplier = DetermineMultiplier(numeric); numeric = numeric.TrimEnd(['k', 'm', 'g', 't', 'p', 'e', 'z', 'y', 'i', 'b', ' ']); // Apply the multiplier and return if (!long.TryParse(numeric, out long longValue)) return null; return longValue * multiplier; } } /// /// Determine the multiplier from a numeric string /// public static long DetermineMultiplier(string? numeric) { if (string.IsNullOrEmpty(numeric)) return 0; long multiplier = 1; if (numeric!.EndsWith("k") || numeric.EndsWith("kb")) multiplier = KiloByte; else if (numeric.EndsWith("ki") || numeric.EndsWith("kib")) multiplier = KibiByte; else if (numeric.EndsWith("m") || numeric.EndsWith("mb")) multiplier = MegaByte; else if (numeric.EndsWith("mi") || numeric.EndsWith("mib")) multiplier = MibiByte; else if (numeric.EndsWith("g") || numeric.EndsWith("gb")) multiplier = GigaByte; else if (numeric.EndsWith("gi") || numeric.EndsWith("gib")) multiplier = GibiByte; else if (numeric.EndsWith("t") || numeric.EndsWith("tb")) multiplier = TeraByte; else if (numeric.EndsWith("ti") || numeric.EndsWith("tib")) multiplier = TibiByte; else if (numeric.EndsWith("p") || numeric.EndsWith("pb")) multiplier = PetaByte; else if (numeric.EndsWith("pi") || numeric.EndsWith("pib")) multiplier = PibiByte; // The following are too big to be represented in Int64 // else if (numeric.EndsWith("e") || numeric.EndsWith("eb")) // multiplier = ExaByte; // else if (numeric.EndsWith("ei") || numeric.EndsWith("eib")) // multiplier = ExiByte; // else if (numeric.EndsWith("z") || numeric.EndsWith("zb")) // multiplier = ZettaByte; // else if (numeric.EndsWith("zi") || numeric.EndsWith("zib")) // multiplier = ZittiByte; // else if (numeric.EndsWith("y") || numeric.EndsWith("yb")) // multiplier = YottaByte; // else if (numeric.EndsWith("yi") || numeric.EndsWith("yib")) // multiplier = YittiByte; return multiplier; } /// /// Determine if a string is fully numeric or not /// public static bool IsNumeric(string? value) { // If we have no value, it is not numeric if (string.IsNullOrEmpty(value)) return false; // If we have a hex value value = value!.ToLowerInvariant(); if (value.StartsWith("0x")) value = value.Substring(2); // If we have a negative value if (value.StartsWith("-")) value = value.Substring(1); // If the value has a multiplier if (DetermineMultiplier(value) > 1) value = value.TrimEnd(['k', 'm', 'g', 't', 'p', 'e', 'z', 'y', 'i', 'b', ' ']); // If the value is empty after trimming if (value.Length == 0) return false; // Otherwise, make sure that every character is a proper match for (int i = 0; i < value.Length; i++) { char c = value[i]; #if NET7_0_OR_GREATER if (!char.IsAsciiHexDigit(c) && c != '.' && c != ',') #else if (!c.IsAsciiHexDigit() && c != '.' && c != ',') #endif return false; } return true; } /// /// Returns the human-readable file size for an arbitrary, 64-bit file size /// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB". /// /// http://www.somacon.com/p576.php /// This uses 1024-byte partitions, not 1000-byte public static string GetBytesReadable(long input) { // Get absolute value long absolute_i = (input < 0 ? -input : input); // Determine the suffix and readable value string suffix; double readable; if (absolute_i >= 0x1000_0000_0000_0000) // Exabyte { suffix = "EB"; readable = (input >> 50); } else if (absolute_i >= 0x4_0000_0000_0000) // Petabyte { suffix = "PB"; readable = (input >> 40); } else if (absolute_i >= 0x100_0000_0000) // Terabyte { suffix = "TB"; readable = (input >> 30); } else if (absolute_i >= 0x4000_0000) // Gigabyte { suffix = "GB"; readable = (input >> 20); } else if (absolute_i >= 0x10_0000) // Megabyte { suffix = "MB"; readable = (input >> 10); } else if (absolute_i >= 0x400) // Kilobyte { suffix = "KB"; readable = input; } else { return input.ToString("0 B"); // Byte } // Divide by 1024 to get fractional value readable /= 1024; // Return formatted number with suffix return readable.ToString("0.### ") + suffix; } #if NETFRAMEWORK || NETCOREAPP3_1 || NET5_0 || NET6_0 /// /// Indicates whether a character is categorized as an ASCII hexademical digit. /// /// The character to evaluate. /// true if c is a hexademical digit; otherwise, false. /// This method determines whether the character is in the range '0' through '9', inclusive, 'A' through 'F', inclusive, or 'a' through 'f', inclusive. internal static bool IsAsciiHexDigit(this char c) { return char.ToLowerInvariant(c) switch { '0' => true, '1' => true, '2' => true, '3' => true, '4' => true, '5' => true, '6' => true, '7' => true, '8' => true, '9' => true, 'a' => true, 'b' => true, 'c' => true, 'd' => true, 'e' => true, 'f' => true, _ => false, }; } #endif } }