81

If I have a double (234.004223), etc., I would like to round this to x significant digits in C#.

So far I can only find ways to round to x decimal places, but this simply removes the precision if there are any 0s in the number.

For example, 0.086 to one decimal place becomes 0.1, but I would like it to stay at 0.08.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rocco
  • 1,395
  • 3
  • 11
  • 19
  • 1
    Do you want x no of digits after initial '0's of decimals. For example if you want to keep 2 no of digits to following number 0.00030908 is 0.00031 or do you want 0.00030? or something else? – lakshmanaraj Dec 17 '08 at 11:58
  • 2
    I'm not clear what you mean here. In your example, are you trying to round to 2 decimal places? Or leave just one digit? If the latter, it should be 0.09, surely, rounding up the 6... – The Archetypal Paul Dec 17 '08 at 11:59
  • Or are you looking for N * 10^X, where N has a specified number of digits? – The Archetypal Paul Dec 17 '08 at 11:59
  • Please give us some more examples of original numbers and what you want to see as output – The Archetypal Paul Dec 17 '08 at 12:04
  • Rounding to significant digits is not the same as rounding to decimal places. 0.3762 to 2 decimal places is 0.38 where as to 2 significant figures/digits it is 0.37 0.0037 to 2 decimal places will correctly be 0.00 but to 2 significant digits it is 0.0037 because 0s are not significant – Rocco Dec 17 '08 at 14:35
  • 10
    I disagree. Rounding to significant digits doesn't mean that you should automatically truncate instead of round. For example, see http://en.wikipedia.org/wiki/Significant_figures. "... if rounding 0.039 to 1 significant figure, the result would be 0.04." – P Daddy Dec 17 '08 at 20:22
  • Note, though, that I've provided both below. 0.039.RoundToSignificantDigits(1) would return 0.04, and 0.039.TruncateToSignificantDigits(1) would return 0.03. – P Daddy Dec 17 '08 at 20:27
  • That is correct, I was mistaken in thinking they are truncated. – Rocco Dec 18 '08 at 10:12
  • Similar question : http://stackoverflow.com/questions/304011/truncate-a-decimal-value-in-c You can simply add round() in the appropriate place. – strager Dec 19 '08 at 04:31

17 Answers17

106

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);
}
P Daddy
  • 28,912
  • 9
  • 68
  • 92
  • Math.round(...) doesn't take two parameters –  Nov 28 '09 at 12:54
  • 11
    @leftbrainlogic: Yes, it really does: http://msdn.microsoft.com/en-us/library/75ks3aby.aspx – P Daddy Dec 01 '09 at 18:03
  • 5
    Neither of these methods will work with negative numbers as Math.Log10 will return Double.NaN if d < 0. – Fraser Feb 26 '12 at 08:13
  • 1
    @Fraser: Good catch. I'll leave it as an exercise for the reader to add `Math.Abs`. – P Daddy Feb 26 '12 at 17:21
  • 4
    @PDaddy hmmm, you would need to check if d == 0 as this will result in Double.NaN too - both methods need a couple of guard clauses such as: if(d == 0) { return 0; } if(d < 0) { d = Math.Abs(d); } - otherwise you end up with a division by 0 in both. – Fraser Feb 28 '12 at 13:44
  • 4
    @Fraser: Well, there goes the exercise for the reader. Btw, Eric noticed (http://stackoverflow.com/a/1925170/36388) the negative numbers flaw over two years ago (not the zero one, though). Maybe I should actually fix this code so people stop calling me on it. – P Daddy Feb 29 '12 at 00:40
  • 4
    @PDaddy Yes please fix it. I'd +1 it if it was fixed. I guess a lot of folks wrongly take highly voted answers as copy-and-pasteable. – Evgeniy Berezovsky Apr 26 '13 at 21:59
  • 1
    Okay, edited to handle numbers ≤ 0. Note, though, that there remains one *tiny* inconsistency. If you call the `Round...` function with a value for `digits` that's outside the range [0, 15], `Math.Round` will throw an argument exception. If `d` is zero, then `Math.Round` isn't called, so there is no exception. Insignificant, really. Note that this doesn't apply to the `Truncate...` function. – P Daddy May 03 '13 at 12:23
  • @PDaddy your solution works like a charm, but in few cases it gives results with 01 in the end 119.212024802412, 121.198454653809. can you please tell me why 01 comes at the last instead of 00 – Akshay Mar 07 '14 at 13:37
  • @aarn: I'm not sure if the numbers you gave are supposed to be inputs or outputs, and if they're inputs, you didn't list the "digits" input, so I can't say with 100% certainty what you're experiencing, but it's likely that you're seeing artifacts of translating between base-10 and base-2 floating-point representations. Please see questions like http://stackoverflow.com/questions/1089018 and http://stackoverflow.com/questions/18389455. – P Daddy Mar 07 '14 at 20:57
  • @PDaddy sorry for insufficient info, the numbers are inputs and the digits are 15 – Akshay Mar 08 '14 at 06:55
  • 1
    @aarn: When I use your first number (119.212...), the output is the exact same as the input. When I use your second number (121.198...), the output is 1.4E-14 higher. This problem is that when the number is scaled, the mantissa changed (since the exponent is base 2), and some precision is lost off the end. Perhaps scaling the other direction would eliminate this loss of precision. But keep in mind that neither your input nor your output equals 121.198454653809 exactly. For more information, see the other questions I linked to before, or perhaps open a new question. – P Daddy Mar 10 '14 at 00:49
  • above code producing the value 5.8999999999999995E-12 for 5.9E-12 in .Net Core 3.0 – Ramakrishna Reddy Apr 22 '20 at 07:52
  • 1
    @RamakrishnaReddy: Please see questions like [stackoverflow.com/questions/1089018](https://stackoverflow.com/questions/1089018/why-cant-decimal-numbers-be-represented-exactly-in-binary) and [stackoverflow.com/questions/18389455](https://stackoverflow.com/questions/18389455/if-the-double-type-can-handle-the-numbers-4-35-and-435-why-do-4-35-100-evalua). – P Daddy Apr 22 '20 at 16:33
  • 1
    `Console.WriteLine("{0:G17}", RoundToSignificantDigits(5.015 * 100, 15))` gives me 501.49999999999994 which is incorrect. The right answer should be 501.5 – Amr Ali Sep 17 '20 at 14:49
  • 1
    @AmrAli: This is because `5.015` can't be represented exactly in IEEE 754 floating point representation. Try `5.015.ToString("G17")`, and you'll get `5.0149999999999997`. So that's the number that you're multiplying by `100` and then rounding to 15 digits. Please see the links in my previous comment on April 22 for more details. – P Daddy Sep 17 '20 at 16:18
  • @PDaddy, Not sure if you want to handle these "bugs" in your code: 1) passing a negative number into "digits" parameter crashes it. 2) passing a number in the "digits" parameter that is larger than number of digits in the "number" parameter crashes it. (So like 346273.397586473754746, 234.) Crashes are System.ArgumentOutOfRangeException Rounding digits must be between 0 and 15, inclusive. from the System.Math.Round() function. – Eric Wood Jul 13 '21 at 21:36
  • @EricWood: Thanks for doing some QA testing! But addressing these issues to make this function bulletproof is rather beyond the scope of an SO answer, IMO. My intention isn't to provide production-ready code, but to teach concepts. I feel that adding input validation would only distract from that goal. – P Daddy Jul 14 '21 at 14:38
  • I would not recommend using this. Try to truncate double number 2.3 to two significant digits and you'll get only 2.2! That's because 2.3 is represented as 2.2999999999999998 due to insufficient double type precision, sadly. – ai_enabled Aug 04 '21 at 22:29
  • @ai_enabled: Your comment is correct about _truncating_ floating point numbers, and is an important point for anybody who's considering doing so. Do note, though, that the wording used by the OP, and the first function I offered, is for **rounding**, not truncating. I only included a function to truncate because in the example the OP gave, his result was that of truncation. I think that was just a simple mistake on his part, and what he actually wanted was rounding, and that's what most people get from this answer, I think. – P Daddy Aug 05 '21 at 22:24
  • @ai_enabled: Still, it **is** important to note the precision problem of floating point numbers, which can affect all sorts of calculations in unexpected ways. Please keep up the good fight. – P Daddy Aug 05 '21 at 22:25
  • @PDaddy, you're correct, the rounding method works fine. I was in a rush and didn't test both methods as I needed truncation only. Truncating double numbers is always such a headache! – ai_enabled Aug 06 '21 at 23:54
24

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));
}
Eric
  • 1,392
  • 17
  • 37
22

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • It is for display purposes, I haven't considered a Decimal at all to be honest. How would I go about converting to string with the relevant number of significant digits as you say? I have been unable to find an example in the Double.ToString() method spec. – Rocco Dec 17 '08 at 13:40
  • 5
    @Rocco: I know I am 4 years late, but I just came across your question. I think you should use Double.ToString("Gn"). See my answer of Nov 6 2012 :-) – farfareast Nov 06 '12 at 23:07
20

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
farfareast
  • 2,179
  • 1
  • 23
  • 22
  • 7
    Although close, this doesn't always return sigfigs... for instance, `G4` would remove the zeros from `1.000` --> `1`. Also, it forces scientific notation at its discretion, whether you like it or not. – u8it Jul 29 '16 at 20:58
  • 2
    Should probably agree with you on dropping significant zeros in 1.0001. As for the second statement -- the use of scientific notation is decided based on the fact which notation will take less space on print (it's an old FORTRAN rule for G format). So, in a way it is predictable, but if somebody generally prefers scientific format - it is not nice for them. – farfareast Aug 16 '16 at 22:06
  • This is definitely the best solution for my problem. I submitted 30/31 with a precision of 28 digits to an API, and the API confirmed it by returning a 16-digit value which didn't match my original value. To match the values, I'm now comparing `submittedValue.ToString("G12")` with `returnedValue.ToString("G12")` (which is enough precision in my case). – fero Dec 10 '21 at 13:47
  • Can't believe I had to come this far down to see one person who understood the question. Thank You. – Behrooz May 18 '23 at 07:25
12

I found two bugs in the methods of P Daddy and Eric. This solves for example the precision error that was presented by Andrew Hancox in this Q&A. There was also a problem with round directions. 1050 with two significant figures isn't 1000.0, it's 1100.0. The rounding was fixed with MidpointRounding.AwayFromZero.

static void Main(string[] args) {
  double x = RoundToSignificantDigits(1050, 2); // Old = 1000.0, New = 1100.0
  double y = RoundToSignificantDigits(5084611353.0, 4); // Old = 5084999999.999999, New = 5085000000.0
  double z = RoundToSignificantDigits(50.846, 4); // Old = 50.849999999999994, New =  50.85
}

static double RoundToSignificantDigits(double d, int digits) {
  if (d == 0.0) {
    return 0.0;
  }
  else {
    double leftSideNumbers = Math.Floor(Math.Log10(Math.Abs(d))) + 1;
    double scale = Math.Pow(10, leftSideNumbers);
    double result = scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero);

    // Clean possible precision error.
    if ((int)leftSideNumbers >= digits) {
      return Math.Round(result, 0, MidpointRounding.AwayFromZero);
    }
    else {
      return Math.Round(result, digits - (int)leftSideNumbers, MidpointRounding.AwayFromZero);
    }
  }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rowanto
  • 121
  • 1
  • 3
  • 3
    Fails for RoundToSignificantDigits(.00000000000000000846113537656557, 6) because Math.Round will not allow its second parameter to go beyond 15. – Oliver Bock Dec 14 '12 at 02:48
  • I would argue, 1050 rounded to two significant digits is 1000. Round to even is a very common rounding method. – Derrick Moeller Dec 15 '17 at 14:14
7

Here is a solution that is - in one word - reliable. It just always works, and the reason for this is that it works with string formatting. With Log and Pow you will always be bitten by special values - there is no working around that, it's intricate.

Table of example values:

value               digits    returns
=========================================================
0.086                    1    "0.09"
1.0                      3    "1.00"
0.1                      3    "0.100"
0.00030908               2    "0.00031"
1239451                  3    "1240000"
5084611353               4    "5085000000"
8.46113537656557E-18     6    "0.00000000000000000846114"
50.8437                  4    "50.84"
50.846                   4    "50.85"
990                      1    "1000"
-5488                    1    "-5000"
-990                     1    "-1000"
7.89E-05                 2    "0.000079"
50.84611353765656        6    "50.8461"
0.073699979              7    "0.07369998"
0                        2    "0"

Here is the code:

public static class SignificantDigits
{
    public static readonly string DecimalSeparator;

    static SignificantDigits()
    {
        System.Globalization.CultureInfo ci = System.Threading.Thread.CurrentThread.CurrentCulture;
        DecimalSeparator = ci.NumberFormat.NumberDecimalSeparator;
    }

    /// <summary>
    /// Format a double to a given number of significant digits.
    /// </summary>
    public static string Format(double value, int digits, bool showTrailingZeros = true, bool alwaysShowDecimalSeparator = false)
    {
        if (double.IsNaN(value) ||
            double.IsInfinity(value))
        {
            return value.ToString();
        }

        string sign = "";
        string before = "0"; // Before the decimal separator
        string after = ""; // After the decimal separator

        if (value != 0d)
        {
            if (digits < 1)
            {
                throw new ArgumentException("The digits parameter must be greater than zero.");
            }

            if (value < 0d)
            {
                sign = "-";
                value = -value;
            }

            // Custom exponential formatting using '#' will give us exactly our digits plus an exponent
            string scientific = value.ToString(new string('#', digits) + "E0");
            string significand = scientific.Substring(0, digits);
            int exponent = int.Parse(scientific.Substring(digits + 1));
            // (significand now already contains the requested number of digits with no decimal separator in it)

            var fractionalBreakup = new StringBuilder(significand);

            if (!showTrailingZeros)
            {
                while (fractionalBreakup[fractionalBreakup.Length - 1] == '0')
                {
                    fractionalBreakup.Length--;
                    exponent++;
                }
            }

            // Place the decimal separator (insert zeros if necessary)

            int separatorPosition;

            if ((fractionalBreakup.Length + exponent) < 1)
            {
                fractionalBreakup.Insert(0, "0", 1 - fractionalBreakup.Length - exponent);
                separatorPosition = 1;
            }
            else if (exponent > 0)
            {
                fractionalBreakup.Append('0', exponent);
                separatorPosition = fractionalBreakup.Length;
            }
            else
            {
                separatorPosition = fractionalBreakup.Length + exponent;
            }

            before = fractionalBreakup.ToString();

            if (separatorPosition < before.Length)
            {
                after = before.Substring(separatorPosition);
                before = before.Remove(separatorPosition);
            }
        }

        string result = sign + before;

        if (after == "")
        {
            if (alwaysShowDecimalSeparator)
            {
                result += DecimalSeparator + "0";
            }
        }
        else
        {
            result += DecimalSeparator + after;
        }

        return result;
    }
}

As a side note - you may be interested in my engineering notation answer under another question, here.

Kay Zed
  • 1,304
  • 2
  • 21
  • 31
4

I agree with the spirit of Jon's assessment:

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.

I needed significant-digit rounding for approximate and non-performance-critical computational purposes, and the format-parse round-trip through "G" format is good enough:

public static double RoundToSignificantDigits(this double value, int numberOfSignificantDigits)
{
    return double.Parse(value.ToString("G" + numberOfSignificantDigits));
}
William
  • 1,993
  • 2
  • 23
  • 40
3

Math.Round() on doubles is flawed (see Notes to Callers in its documentation). The later step of multiplying the rounded number back up by its decimal exponent will introduce further floating point errors in the trailing digits. Using another Round() as @Rowanto does won't reliably help and suffers from other problems. However if you're willing to go via decimal then Math.Round() is reliable, as is multiplying and dividing by powers of 10:

static ClassName()
{
    powersOf10 = new decimal[28 + 1 + 28];
    powersOf10[28] = 1;
    decimal pup = 1, pdown = 1;
    for (int i = 1; i < 29; i++) {
        pup *= 10;
        powersOf10[i + 28] = pup;
        pdown /= 10;
        powersOf10[28 - i] = pdown;
    }
}

/// <summary>Powers of 10 indexed by power+28.  These are all the powers
/// of 10 that can be represented using decimal.</summary>
static decimal[] powersOf10;

static double RoundToSignificantDigits(double v, int digits)
{
    if (v == 0.0 || Double.IsNaN(v) || Double.IsInfinity(v)) {
        return v;
    } else {
        int decimal_exponent = (int)Math.Floor(Math.Log10(Math.Abs(v))) + 1;
        if (decimal_exponent < -28 + digits || decimal_exponent > 28 - digits) {
            // Decimals won't help outside their range of representation.
            // Insert flawed Double solutions here if you like.
            return v;
        } else {
            decimal d = (decimal)v;
            decimal scale = powersOf10[decimal_exponent + 28];
            return (double)(scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero));
        }
    }
}
Oliver Bock
  • 4,829
  • 5
  • 38
  • 62
2

This question is similiar to the one you're asking:

Formatting numbers with significant figures in C#

Thus you could do the following:

double Input2 = 234.004223;
string Result2 = Math.Floor(Input2) + Convert.ToDouble(String.Format("{0:G1}", Input2 - Math.Floor(Input2))).ToString("R6");

Rounded to 1 significant digit.

Community
  • 1
  • 1
Bravax
  • 10,453
  • 7
  • 40
  • 68
2

Let inputNumber be input that needs to be converted with significantDigitsRequired after decimal point, then significantDigitsResult is the answer to the following pseudo code.

integerPortion = Math.truncate(**inputNumber**)

decimalPortion = myNumber-IntegerPortion

if( decimalPortion <> 0 )
{

 significantDigitsStartFrom = Math.Ceil(-log10(decimalPortion))

 scaleRequiredForTruncation= Math.Pow(10,significantDigitsStartFrom-1+**significantDigitsRequired**)

**siginficantDigitsResult** = integerPortion + ( Math.Truncate (decimalPortion*scaleRequiredForTruncation))/scaleRequiredForTruncation

}
else
{

  **siginficantDigitsResult** = integerPortion

}
Alex Riley
  • 169,130
  • 45
  • 262
  • 238
lakshmanaraj
  • 4,145
  • 23
  • 12
2

Tested on .NET 6.0

In my opinion, the rounded results are inconsistent due to the defects of the framework and the error of the floating point. Therefore, be careful about use.

decimal.Parse(doubleValue.ToString("E"), NumberStyles.Float);

example:

using System.Diagnostics;
using System.Globalization;

List<double> doubleList = new();
doubleList.Add(    0.012345);
doubleList.Add(    0.12345 );
doubleList.Add(    1.2345  );
doubleList.Add(   12.345   );
doubleList.Add(  123.45    );
doubleList.Add( 1234.5     );
doubleList.Add(12345       );
doubleList.Add(10  );
doubleList.Add( 0  );
doubleList.Add( 1  );
doubleList.Add(-1  );
doubleList.Add( 0.1);

Debug.WriteLine("");
foreach (var item in doubleList)
{
    Debug.WriteLine(decimal.Parse(item.ToString("E2"), NumberStyles.Float));

    // 0.0123
    // 0.123
    // 1.23
    // 12.3
    // 123
    // 1230
    // 12300
    // 10.0
    // 0.00
    // 1.00
    // -1.00
    // 0.100
}

Debug.WriteLine("");
foreach (var item in doubleList)
{
    Debug.WriteLine(decimal.Parse(item.ToString("E3"), NumberStyles.Float));

    // 0.01235
    // 0.1235
    // 1.234
    // 12.35
    // 123.5
    // 1234
    // 12340
    // 10.00
    // 0.000
    // 1.000
    // -1.000
    // 0.1000
}
1

As pointed out by @Oliver Bock is that Math.Round() on doubles is flawed (see Notes to Callers in its documentation). The later step of multiplying the rounded number back up by its decimal exponent will introduce further floating point errors in the trailing digits. Generally, any multiplication by or division by a power of ten gives a non-exact result, since floating-point is typically represented in binary, not in decimal.

Using the following function will avoid floating point errors in the trailing digits:

static double RoundToSignificantDigits(double d, int digits)
{
    if (d == 0.0 || Double.IsNaN(d) || Double.IsInfinity(d))
    {
        return d;
    }
    // Compute shift of the decimal point.
    int shift = digits - 1 - (int)Math.Floor(Math.Log10(Math.Abs(d)));

    // Return if rounding to the same or higher precision.
    int decimalPlaces = 0;
    for (long pow = 1; Math.Floor(d * pow) != (d * pow); pow *= 10) decimalPlaces++;
    if (shift >= decimalPlaces)
        return d;

    // Round to sf-1 fractional digits of normalized mantissa x.dddd
    double scale = Math.Pow(10, Math.Abs(shift));
    return shift > 0 ?
           Math.Round(d * scale, MidpointRounding.AwayFromZero) / scale :
           Math.Round(d / scale, MidpointRounding.AwayFromZero) * scale;
}

However if you're willing to go via decimal then Math.Round() is reliable, as is multiplying and dividing by powers of 10:

static double RoundToSignificantDigits(double d, int digits)
{
    if (d == 0.0 || Double.IsNaN(d) || Double.IsInfinity(d))
    {
        return d;
    }
    decimal scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
    return (double)(scale * Math.Round((decimal)d / scale, digits, MidpointRounding.AwayFromZero));
}

Console.WriteLine("{0:G17}", RoundToSignificantDigits(5.015 * 100, 15)); // 501.5
Amr Ali
  • 3,020
  • 1
  • 16
  • 11
0

for me, this one works pretty fine and is also valid for negative numbers:

public static double RoundToSignificantDigits(double number, int digits)
{
    int sign = Math.Sign(number);

    if (sign < 0)
        number *= -1;

    if (number == 0)
        return 0;

    double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(number))) + 1);
    return sign * scale * Math.Round(number / scale, digits);
}
Pixel_95
  • 954
  • 2
  • 9
  • 21
0

My solution may be helpful in some cases, I use it to display crypto prices which vary greatly in magnitude - it always gives me a specified number of significant figures but unlike ToString("G[number of digits]") it doesn't show small values in scientific notation (don't know a way to avoid this with ToString(), if there is then please let me know!)

    const int MIN_SIG_FIGS = 6; //will be one more for < 0
    int numZeros = (int)Math.Floor(Math.Log10(Math.Abs(price))); //get number of zeros before first digit, will be negative for price > 0
    int decPlaces = numZeros < MIN_SIG_FIGS
                  ? MIN_SIG_FIGS - numZeros < 0 
                        ? 0 
                        : MIN_SIG_FIGS - numZeros 
                  : 0; //dec. places: set to MIN_SIG_FIGS + number of zeros, unless numZeros greater than sig figs then no decimal places
    return price.ToString($"F{decPlaces}");
ardmark
  • 81
  • 1
  • 9
0

Here's a version inspired by Peter Mortensen that adds a couple of safeguards for edge cases such as value being NaN, Inf or very small:

public static double RoundToSignificantDigits(this double value, int digits)
{
    if (double.IsNaN(value) || double.IsInfinity(value))
        return value;
    if (value == 0.0)
        return 0.0;
    double leftSideNumbers = Math.Floor(Math.Log10(Math.Abs(value))) + 1;
    int places = digits - (int)leftSideNumbers;
    if (places > 15)
        return 0.0;
    double scale = Math.Pow(10, leftSideNumbers);
    double result = scale * Math.Round(value / scale, digits, MidpointRounding.AwayFromZero);
    if (places < 0)
        places = 0;
    return Math.Round(result, places, MidpointRounding.AwayFromZero);
}
Sven Vranckx
  • 374
  • 4
  • 5
-6

I just did:

int integer1 = Math.Round(double you want to round, 
    significant figures you want to round to)
akjoshi
  • 15,374
  • 13
  • 103
  • 121
Javier
  • 1
-7

Here is something I did in C++

/*
    I had this same problem I was writing a design sheet and
    the standard values were rounded. So not to give my
    values an advantage in a later comparison I need the
    number rounded, so I wrote this bit of code.

    It will round any double to a given number of significant
    figures. But I have a limited range written into the
    subroutine. This is to save time as my numbers were not
    very large or very small. But you can easily change that
    to the full double range, but it will take more time.

    Ross Mckinstray
    rmckinstray01@gmail.com
*/

#include <iostream>
#include <fstream>
#include <string>
#include <math.h>
#include <cmath>
#include <iomanip>

#using namespace std;

double round_off(double input, int places) {
    double roundA;
    double range = pow(10, 10); // This limits the range of the rounder to 10/10^10 - 10*10^10 if you want more change range;
    for (double j = 10/range; j< 10*range;) {
        if (input >= j && input < j*10){
            double figures = pow(10, places)/10;
            roundA = roundf(input/(j/figures))*(j/figures);
        }
        j = j*10;
    }
    cout << "\n in sub after loop";
    if (input <= 10/(10*10) && input >= 10*10) {
        roundA = input;
        cout << "\nDID NOT ROUND change range";
    }
    return roundA;
}

int main() {
    double number, sig_fig;

    do {
        cout << "\nEnter number ";
        cin >> number;
        cout << "\nEnter sig_fig ";
        cin >> sig_fig;
        double output = round_off(number, sig_fig);

        cout << setprecision(10);
        cout << "\n I= " << number;
        cout << "\n r= " <<output;
        cout << "\nEnter 0 as number to exit loop";
    }
    while (number != 0);

    return 0;
}

Hopefully I did not change anything formatting it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131