C# String Format for Scientific Notation if greater than a certain number of digits

130 Views Asked by At

I have a series of numbers that can be either large integers, or very small decimals. I would like numbers with more than four digits to be displayed in scientific notation.

At first, I considered a custom number format 0.000E+0, but that puts E+0 at the end of any numbers with less than four digits.

While searching, I was suggested using the General formatter G4, but that doesn't turn small decimal numbers into Scientific Notation, for example, 0.001234 remains as 0.001234 instead of becoming 1.234E-3 as I would expect.

I need a number format string that fulfills the following conditions:

0 -> 0
1234 -> 1234
12348 -> 1.235E3
0.123 -> 0.123
0.1234 -> 1.234E-1
0.001234 -> 1.234E-3
2

There are 2 best solutions below

1
IV. On

A passive number format string that would produce that kind of active parsing might be a tall order. But if there's any way that using an extension method would work for your use case then it would be fairly simple to make one.

Usage
string s = 0.001234.ToStringAuto(); // The value of s will be "1.234E-3"

Here's a first pass that produces the format in your post:

public static class Extensions
{
    public static string ToStringAuto<T>(this T o, int maxDigits = 4) where T : IConvertible
    {
        try
        {
            if (Convert.ChangeType(o, typeof(double)) is double @double)
            {
                string general = @double.ToString("G"); // Most compact
                int digitsWithoutDecimal = general.Count(_ => _ != '.');
                if (digitsWithoutDecimal > maxDigits)
                {
                    var custom = @double.ToString("E3").Replace("E+", "E");
                    while (custom.Contains("E0")) custom = custom.Replace("E0", "E");
                    while (custom.Contains("-0")) custom = custom.Replace("-0", "-");
                    if((@double < 0) && custom[0] != '-') 
                    {
                        custom.Insert(0, "-");
                    }
                    return custom;
                }
                else
                {
                    return general;
                }
            }
        }
        catch { }
        return o.ToString();
    }
}

Console app output

Console.WriteLine($"0 -> {0.ToStringAuto()}");
Console.WriteLine($"1234 -> {1234.ToStringAuto()}");
Console.WriteLine($"12348 -> {12348.ToStringAuto()}");
Console.WriteLine($"0.123 -> {0.123.ToStringAuto()}");
Console.WriteLine($"0.1234 -> {0.1234.ToStringAuto()}");
Console.WriteLine($"0.001234 -> {0.001234.ToStringAuto()}");

Console.WriteLine("\nCase of leading '-' where number should remain negative");
Console.WriteLine($"-0.001234 -> {(-0.001234).ToStringAuto()}");

Console.WriteLine("\nError tolerance for non-convertible input");
Console.WriteLine($"Azgard -> {"Azgard".ToStringAuto()}");

Console.ReadLine();
2
Jaime Oro On

Here you have a simple solution.

Note-1: In tearms of performance, I think is better to repeat the function with each type you want to deal with rather than performing type conversions.

Note-2: Take into account that in your sample, you are considering the . as a significant number. I'm maintaining this approach.

Note-3: You should consider using decimal instead of double or float. Decimal is stored in base 10, double or float I think are in base 2. This might cause undesirable rounding effects.

Console.WriteLine(FormatHelpers.FormatNumber(0)); // 0
Console.WriteLine(FormatHelpers.FormatNumber(1234)); // 1234
Console.WriteLine(FormatHelpers.FormatNumber(12348)); // 1235E3
Console.WriteLine(FormatHelpers.FormatNumber(0.123)); // 0.123
Console.WriteLine(FormatHelpers.FormatNumber(0.1234)); // 1.234E-1
Console.WriteLine(FormatHelpers.FormatNumber(0.001234)); //1.234E-3


static class FormatHelpers
{
    public static string FormatNumber(double number)
    {
        string gFormat = number.ToString("G");
        int significantNums = gFormat.Length 
            - (number < 0 ? 1 : 0)  // Do not count the '-' sign
            - (Math.Truncate(number) == 0d ? 1 : 0); // Do not count the first '0'

        if (significantNums < 5) return gFormat;
        return number.ToString("0.000E0");
    }

    public static string FormatNumber(float number)
    {
        string gFormat = number.ToString("G");
        int significantNums = gFormat.Length 
            - (number < 0 ? 1 : 0) 
            - (Math.Truncate(number) == 0f ? 1 : 0);

        if (significantNums < 5) return gFormat;
        return number.ToString("0.000E0");
    }
}