using System.Runtime.CompilerServices; namespace Sandbox; public static partial class SandboxSystemExtensions { /// /// Given a number, will format as a memory value, ie 10gb, 4mb /// public static string FormatBytes( this T input, bool shortFormat = false ) where T : struct, IComparable, IComparable, IConvertible, IEquatable, IFormattable { ulong i = 0; if ( input is float f ) i = (ulong)Math.Max( 0, f ); else if ( input is int ii ) i = (ulong)Math.Max( 0, ii ); else if ( input is long l ) i = (ulong)Math.Max( 0, l ); else if ( input is double d ) i = (ulong)Math.Max( 0, d ); else i = (ulong)Convert.ChangeType( input, typeof( ulong ) ); double readable; string suffix; if ( i >= 0x1000000000000000 ) // Exabyte { suffix = "eb"; readable = (double)(i >> 50); } else if ( i >= 0x4000000000000 ) // Petabyte { suffix = "pb"; readable = (double)(i >> 40); } else if ( i >= 0x10000000000 ) // Terabyte { suffix = "tb"; readable = (double)(i >> 30); } else if ( i >= 0x40000000 ) // Gigabyte { suffix = "gb"; readable = (double)(i >> 20); } else if ( i >= 0x100000 ) // Megabyte { suffix = "mb"; readable = (double)(i >> 10); } else if ( i >= 0x400 ) // Kilobyte { suffix = "kb"; readable = (double)i; } else { return i.ToString( "0b" ); // Byte } readable /= 1024; return readable.ToString( shortFormat ? "0" : "0.00" ) + suffix; } /// /// Clamp a number between two values. /// public static T Clamp( this T input, T min, T max ) where T : struct, IComparable, IComparable, IConvertible, IEquatable, IFormattable { if ( input.CompareTo( min ) < 0 ) return min; if ( input.CompareTo( max ) > 0 ) return max; return input; } /// /// Formats the given value in format "1w2d3h4m5s". Will not display 0 values. /// /// Time to format, in seconds. public static string FormatSeconds( this long secs ) { var m = System.Math.Floor( (float)secs / 60.0f ); var h = System.Math.Floor( (float)m / 60.0f ); var d = System.Math.Floor( (float)h / 24.0f ); var w = System.Math.Floor( (float)d / 7.0f ); if ( secs < 60 ) return string.Format( "{0}s", secs ); // 1s if ( m < 60 ) return string.Format( "{1}m{0}s", secs % 60, m ); // 5m3s if ( h < 48 ) return string.Format( "{2}h{1}m{0}s", secs % 60, m % 60, h ); // 6h40m34h if ( d < 7 ) return string.Format( "{3}d{2}h{1}m{0}s", secs % 60, m % 60, h % 24, d ); // 5d15h15m10s return string.Format( "{4}w{3}d{2}h{1}m{0}s", secs % 60, m % 60, h % 24, d % 7, w ); } /// public static string FormatSeconds( this ulong secs ) { return FormatSeconds( (long)secs ); } /// /// Formats the given value in format "4 weeks, 3 days, 2 hours and 1 minutes". /// Will not display 0 values. Will not display seconds if value is more than 1 hour. /// /// Time to format, in seconds. public static string FormatSecondsLong( this long secs ) { var m = System.Math.Floor( (float)secs / 60.0f ); var h = System.Math.Floor( (float)m / 60.0f ); var d = System.Math.Floor( (float)h / 24.0f ); var w = System.Math.Floor( (float)d / 7.0f ); if ( secs < 60 ) return string.Format( "{0} seconds", secs ); if ( m < 60 ) return string.Format( "{1} minutes, {0} seconds", secs % 60, m ); if ( h < 48 ) return string.Format( "{2} hours and {1} minutes", secs % 60, m % 60, h ); if ( d < 7 ) return string.Format( "{3} days, {2} hours and {1} minutes", secs % 60, m % 60, h % 24, d ); return string.Format( "{4} weeks, {3} days, {2} hours and {1} minutes", secs % 60, m % 60, h % 24, d % 7, w ); } /// public static string FormatSecondsLong( this ulong secs ) { return FormatSecondsLong( (long)secs ); } /// /// "1500" becomes "1,500", "15 000" becomes "15K", "15 000 000" becomes "15KK", etc. /// public static string FormatNumberShort( this long num ) { if ( num >= 100000 ) { return FormatNumberShort( num / 1000 ) + "K"; } if ( num >= 10000 ) { return (num / 1000D).ToString( "0.#" ) + "K"; } return num.ToString( "#,0" ); } /// public static string FormatNumberShort( this ulong num ) { return FormatNumberShort( (long)num ); } /// /// Does what you expected to happen when you did "a % b", that is, handles negative values by returning a positive number from the end. /// public static int UnsignedMod( this int a, int b ) { // pasted from https://stackoverflow.com/questions/2691025/mathematical-modulus-in-c-sharp return (Math.Abs( a * b ) + a) % b; } /// /// Returns the number of bits set in an integer. This us usually used for flags to count /// the amount of flags set. /// public static int BitsSet( this int i ) { i = i - ((i >> 1) & 0x55555555); // add pairs of bits i = (i & 0x33333333) + ((i >> 2) & 0x33333333); // quads i = (i + (i >> 4)) & 0x0F0F0F0F; // groups of 8 return (i * 0x01010101) >> 24; // horizontal sum of bytes } /// /// Return single if 1 else plural /// public static string Plural( this int a, string single, string plural ) { if ( a == 1 ) return single; if ( a == -1 ) return single; return plural; } /// /// Change 1 to 1st, 2 to 2nd etc /// public static string FormatWithSuffix( this int num ) { string number = num.ToString(); if ( number.EndsWith( "11" ) ) return number + "th"; if ( number.EndsWith( "12" ) ) return number + "th"; if ( number.EndsWith( "13" ) ) return number + "th"; if ( number.EndsWith( "1" ) ) return number + "st"; if ( number.EndsWith( "2" ) ) return number + "nd"; if ( number.EndsWith( "3" ) ) return number + "rd"; return number + "th"; } private enum SizeUnit { B, KB, MB, GB, TB, PB, EB, ZB, YB } public static string SizeFormat( this long bytes ) { if ( bytes < 0 ) return "-" + SizeFormat( -bytes ); int unit = 0; double value = bytes; while ( unit < (int)SizeUnit.YB && value >= 1024 ) { value /= 1024; unit++; } return $"{value:F2} {(SizeUnit)unit}"; } public static string SizeFormat( this int bytes ) => SizeFormat( (long)bytes ); /// /// Format a large number into "1045M", "56K" /// public static string KiloFormat( this int num ) { if ( num >= 10000000 ) return (num / 1000000).ToString( "#,0M" ); if ( num >= 1000000 ) return (num / 1000000).ToString( "0.#" ) + "M"; if ( num >= 100000 ) return (num / 1000).ToString( "#,0K" ); if ( num >= 1000 ) return (num / 1000).ToString( "0.#" ) + "K"; return num.ToString( "#,0" ); } /// /// Format a large number into "1045M", "56K" /// public static string KiloFormat( this long num ) { if ( num >= 10000000 ) return (num / 1000000).ToString( "#,0M" ); if ( num >= 1000000 ) return (num / 1000000).ToString( "0.#" ) + "M"; if ( num >= 100000 ) return (num / 1000).ToString( "#,0K" ); if ( num >= 1000 ) return (num / 1000).ToString( "0.#" ) + "K"; return num.ToString( "#,0" ); } /// /// Humanize a timespan into "x hours", "x seconds" /// public static string Humanize( this TimeSpan timespan, bool shortVersion = false, bool minutes = true, bool hours = true, bool days = true ) { if ( shortVersion ) { if ( timespan.TotalSeconds < 1 ) { return "0s"; } else if ( timespan.TotalSeconds < 60 || !minutes ) { return $"{timespan.TotalSeconds:n0}s"; } else if ( timespan.TotalMinutes < 60 || !hours ) { return $"{timespan.TotalMinutes:n0}m"; } else if ( timespan.TotalHours < 24 || !days ) { return $"{timespan.TotalHours:n0}h"; } else { return $"{timespan.TotalDays:n0}d"; } } else { if ( timespan.TotalSeconds < 1 ) { return "0 seconds"; } else if ( timespan.TotalSeconds < 60 || !minutes ) { return $"{timespan.TotalSeconds:n0} seconds"; } else if ( timespan.TotalMinutes < 60 || !hours ) { return $"{timespan.TotalMinutes:n0} minutes"; } else if ( timespan.TotalHours < 24 || !days ) { return $"{timespan.TotalHours:n0} hours"; } else { return $"{timespan.TotalDays:n0} days"; } } } [MethodImpl( MethodImplOptions.AggressiveInlining )] public static unsafe bool Contains( this T value, T flag ) where T : unmanaged, Enum { if ( Unsafe.SizeOf() == sizeof( int ) ) { return (Unsafe.As( ref value ) & Unsafe.As( ref flag )) == Unsafe.As( ref flag ); } else if ( Unsafe.SizeOf() == sizeof( long ) ) { return (Unsafe.As( ref value ) & Unsafe.As( ref flag )) == Unsafe.As( ref flag ); } else { throw new ArgumentException( "Unsupported enum type" ); } } [MethodImpl( MethodImplOptions.AggressiveInlining )] public static unsafe T WithFlag( this T value, T flag, bool set ) where T : unmanaged, Enum { switch ( sizeof( T ) ) { case 1: { byte* valPtr = (byte*)&value; byte* flagPtr = (byte*)&flag; byte result = set ? (byte)(*valPtr | *flagPtr) : (byte)(*valPtr & ~(*flagPtr)); return *(T*)&result; } case 2: { ushort* valPtr = (ushort*)&value; ushort* flagPtr = (ushort*)&flag; ushort result = set ? (ushort)(*valPtr | *flagPtr) : (ushort)(*valPtr & ~(*flagPtr)); return *(T*)&result; } case 4: { uint* valPtr = (uint*)&value; uint* flagPtr = (uint*)&flag; uint result = set ? *valPtr | *flagPtr : *valPtr & ~(*flagPtr); return *(T*)&result; } case 8: { ulong* valPtr = (ulong*)&value; ulong* flagPtr = (ulong*)&flag; ulong result = set ? *valPtr | *flagPtr : *valPtr & ~(*flagPtr); return *(T*)&result; } default: throw new NotSupportedException( $"Unsupported enum underlying type size {sizeof( T )}" ); } } [MethodImpl( MethodImplOptions.AggressiveInlining )] public static int AsInt( this T value ) where T : unmanaged, Enum { if ( Unsafe.SizeOf() == sizeof( int ) ) return Unsafe.As( ref value ); if ( Unsafe.SizeOf() == sizeof( byte ) ) return Unsafe.As( ref value ); if ( Unsafe.SizeOf() == sizeof( short ) ) return Unsafe.As( ref value ); if ( Unsafe.SizeOf() == sizeof( long ) ) return (int)Unsafe.As( ref value ); return 0; } /// /// Convert 1100 to 1.1k /// public static string ToMetric( this int input, int decimals = 2 ) { return Humanizer.MetricNumeralExtensions.ToMetric( input, decimals: decimals ); } /// /// Return true if the number is a power of two (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, etc) /// public static bool IsPowerOfTwo( this int x ) { return (x & (x - 1)) == 0; } /// /// Convert 1100 to 1.1k /// public static string ToMetric( this long input, int decimals = 2 ) { return Humanizer.MetricNumeralExtensions.ToMetric( (int)input, decimals: decimals ); } /// /// Convert 1100 to 1.1k /// public static string ToMetric( this double input, int decimals = 2 ) { return Humanizer.MetricNumeralExtensions.ToMetric( input, decimals: decimals ); } /// /// Convert 1100 to 1.1k /// public static string ToMetric( this float input, int decimals = 2 ) { return Humanizer.MetricNumeralExtensions.ToMetric( input, decimals: decimals ); } }