Spending/LargeNumberString.cs
using System;
using System.Globalization;
using System.Text;

namespace PlanetMeat;

public static class LargeNumberString
{
	private const string NEGATIVE_ZERO = "-0";
	private const string UNSIGNED_ZERO = "0";

	private const int EXPONENT_BATCH_SIZE = 3;
	private const string SCI_NOTATION_FORMAT = "0.00e0";
	private static readonly string[] EXPONENT_BATCH_SUFFIXES = ["", "k", "M", "B", "T", "Q", "X", "Z"];

	private static string ShiftDecimal( string value, int shiftBy )
	{
		var decimalPosition = value.IndexOf( '.' );
		if ( decimalPosition < 0 )
			return value;

		StringBuilder builder = new( value );
		builder.Remove( decimalPosition, 1 );

		if ( shiftBy < value.Length - 1 - decimalPosition )
			builder.Insert( decimalPosition + shiftBy, '.' );
		return builder.ToString();
	}

	public static string ToSciNotation<T>( T amt ) where T : IFormattable
	{
		return amt.ToString( SCI_NOTATION_FORMAT, CultureInfo.InvariantCulture );
	}

	private static string PreventNegativeZero( string z ) => z == NEGATIVE_ZERO ? UNSIGNED_ZERO : z;
	public static string ToCompact<T>( T amt, bool intifySmall = true ) where T : IFormattable
	{
		var scientific = ToSciNotation( amt );
		var values = scientific.Split( "e" );
		if ( values.Length < 2 )
			return PreventNegativeZero( amt.ToString() );

		var exponent = short.Parse( values[1] );

		if ( intifySmall && exponent < EXPONENT_BATCH_SIZE )
			return PreventNegativeZero( amt.ToString( "##0", CultureInfo.InvariantCulture ) );

		if ( exponent >= EXPONENT_BATCH_SIZE * EXPONENT_BATCH_SUFFIXES.Length )
			return scientific;

		for ( byte i = 0; i < EXPONENT_BATCH_SUFFIXES.Length; i++ )
		{
			if ( exponent < EXPONENT_BATCH_SIZE * (i + 1) )
				return ShiftDecimal( values[0], exponent - EXPONENT_BATCH_SIZE * i ) + EXPONENT_BATCH_SUFFIXES[i];
		}
		return scientific;
	}
}