Round a double to x significant figures

I've been using pDaddy's sigfig function for a few months and found a bug in it. You cannot take the Log of a negative number, so if d is negative the results is NaN.

The following corrects the bug:

public static double SetSigFigs(double d, int digits)
{   
    if(d == 0)
        return 0;

    decimal scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);

    return (double) (scale * Math.Round((decimal)d / scale, digits));
}

The framework doesn't have a built-in function to round (or truncate, as in your example) to a number of significant digits. One way you can do this, though, is to scale your number so that your first significant digit is right after the decimal point, round (or truncate), then scale back. The following code should do the trick:

static double RoundToSignificantDigits(this double d, int digits){
    if(d == 0)
        return 0;

    double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
    return scale * Math.Round(d / scale, digits);
}

If, as in your example, you really want to truncate, then you want:

static double TruncateToSignificantDigits(this double d, int digits){
    if(d == 0)
        return 0;

    double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1 - digits);
    return scale * Math.Truncate(d / scale);
}

If it is for display purposes (as you state in the comment to Jon Skeet's answer), you should use Gn format specifier. Where n is the number of significant digits - exactly what you are after.

Here is the the example of usage if you want 3 significant digits (printed output is in the comment of each line):

    Console.WriteLine(1.2345e-10.ToString("G3"));//1.23E-10
    Console.WriteLine(1.2345e-5.ToString("G3")); //1.23E-05
    Console.WriteLine(1.2345e-4.ToString("G3")); //0.000123
    Console.WriteLine(1.2345e-3.ToString("G3")); //0.00123
    Console.WriteLine(1.2345e-2.ToString("G3")); //0.0123
    Console.WriteLine(1.2345e-1.ToString("G3")); //0.123
    Console.WriteLine(1.2345e2.ToString("G3"));  //123
    Console.WriteLine(1.2345e3.ToString("G3"));  //1.23E+03
    Console.WriteLine(1.2345e4.ToString("G3"));  //1.23E+04
    Console.WriteLine(1.2345e5.ToString("G3"));  //1.23E+05
    Console.WriteLine(1.2345e10.ToString("G3")); //1.23E+10

It sounds to me like you don't want to round to x decimal places at all - you want to round to x significant digits. So in your example, you want to round 0.086 to one significant digit, not one decimal place.

Now, using a double and rounding to a number of significant digits is problematic to start with, due to the way doubles are stored. For instance, you could round 0.12 to something close to 0.1, but 0.1 isn't exactly representable as a double. Are you sure you shouldn't actually be using a decimal? Alternatively, is this actually for display purposes? If it's for display purposes, I suspect you should actually convert the double directly to a string with the relevant number of significant digits.

If you can answer those points, I can try to come up with some appropriate code. Awful as it sounds, converting to a number of significant digits as a string by converting the number to a "full" string and then finding the first significant digit (and then taking appropriate rounding action after that) may well be the best way to go.