2
votes

I'm looking for a String.Format() specifier in the .NET Framework that will permit this:

-- For values ranging between 0 and 1, a leading number (0 or 1), the decimal separator, and three digits after, for example. 0.995, 1.000, 0.015

-- Ranging between 0 and 100, two decimal places: 0.00, 5.46, 10.0

-- Ranging between 100 and 10000, no decimal places: 543, 886, 2576

Essentially, I think want four significant digits, where the decimal places appear only if the number is very small. None of my outputs have negative values. EDIT: I need to avoid scientific notation.

Is there a format specifier that can give me something close to that? Or, alternatively, a regular expression that would modify a double-precision string (like "43667.73625003946255") to produce it?

3
A bit of a nitpick, but just to make sure: should we just go by your examples? Four significant figures would result in 0.9950 and 10.00, etc. Your examples have varying numbers of significant digits. - Mike Precup
Do you need a single format string or could you implement this in a custom IFormatProvider? - D Stanley
Yes, by my examples. The different ranges require different significant digits. I'd like it in a single format string if possible; XAML front ends will consume it and I wanted to specify the formatting in the binding syntax. If bindings can specify an IFormatProvider I'll use that, though! - Rob Perkins
I've modified the question to clarify the ranges. - Rob Perkins

3 Answers

3
votes

I don't think you'll find a single format string that will give you those results, but here's a custom IFormatProvider that should work:

public class CustomFormatter : IFormatProvider, ICustomFormatter
{
    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        if(IsNumber(arg))
        {
            double number = Convert.ToDouble(arg);
            if(number < 1)
                return string.Format("{0:0.000}", arg);
            else if(number < 10)
                return string.Format("{0:0.00}", arg);
            return string.Format("{0:0}", arg);
        }
        else return string.Format(format,arg);  // default formatting for other types
    }

    public object GetFormat(Type formatType)
    {
    return (formatType == typeof(ICustomFormatter)) ? this : null;
    }

    public static bool IsNumber(object value)
    {
        return value is sbyte
                || value is byte
                || value is short
                || value is ushort
                || value is int
                || value is uint
                || value is long
                || value is ulong
                || value is float
                || value is double
                || value is decimal;
    }
}

void Main()
{
    foreach(object val in (new object[] {0, 0.05, 1, 1.0, 1.5, 9.9, 10, 10m,0XFF}))
        Console.WriteLine(val + " : "+string.Format(new CustomFormatter(),"{0}",val));
}

output:

0    : 0.000
0.05 : 0.050
1    : 1.00
1.0  : 1.00
1.5  : 1.50
9.9  : 9.90
10   : 10
10m  : 10
255  : 255
1
votes

doubeValue.ToString("g3") will give you a text representation with 3 significant digits.

Note that it will use scientific notation if there are more than 3 digits to the left of the decimal point.

0.57464.ToString("g3") //---> 0.575
1234.ToString("g3") //---> 1.23e+03
13.ToString("g3") //---> 13
string.Format("{0:g3}", 999) //---> 999
string.Format("{0:g3}", 0.000324) //---> 0.000324
0
votes

I would implement a simple extension method:

public static class DoubleExtensions {
    public static string ToCustomString(this double value) {
        var absValue = Math.Abs(value);
        if (absValue < 1)
            return string.Format("{0:N3}", value);
        if (absValue < 100)
            return string.Format("{0:N2}", value);
        return string.Format("{0:N0}", value);
    }
}