3

In my C# program I have a double obtained from some computation and its value is something like 0,13999 or 0,0079996 but this value has to be presented to a human so it's better displayed as 0,14 or 0,008 respectively.

So I need to round the value, but have no idea to which precision - I just need to "throw away those noise digits".

How could I do that in my code?

To clarify - I need to round the double values to a precision that is unknown at compile time - this needs to be determined at runtime. What would be a good heuristic to achieve this?

Oded
  • 489,969
  • 99
  • 883
  • 1,009
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 2
    How do you define "noise digits"? – Oded Jan 31 '12 at 12:59
  • 3
    If you can't tell us what you want, we can't tell you how to do it. Round `0,13999` to `0,13` (round down to 2 decimals) and `0,0079996` to `0,008` (round up to 3 decimals) sounds like completely different rules. – C.Evenhuis Jan 31 '12 at 12:59
  • @Oded: Perhaps something like "digits that together represent no more than one percent of the value". – sharptooth Jan 31 '12 at 13:00
  • @C.Evenhuis: That should be `0.14` - my bad. – sharptooth Jan 31 '12 at 13:01
  • @Jamiec: Yes, you're right, that was my typo. – sharptooth Jan 31 '12 at 13:01
  • Is this a floating point precision question or a simple rounding question? – magritte Jan 31 '12 at 13:02
  • @Tony Leeper: That's a rounding question, I don't see any floating-point inexactness problems yet. – sharptooth Jan 31 '12 at 13:05
  • It sounds like you want a human friendly way of displaying the double in scientific notation `1.4e-001` which displays the first few non-zero digits followed by the "number of zeroes". – C.Evenhuis Jan 31 '12 at 13:07
  • @C.Evenhuis: That would do too, I guess. – sharptooth Jan 31 '12 at 13:10
  • I can't think of a good heuristic that might not cause you to possibly lose data (repeat digits may be correct...). Can you not use `decimal` instead of `double`? – Oded Jan 31 '12 at 13:10
  • why not a regex to detect a pattern, get the number of digits in right side of the decimal point till the pattern, and then do Math.Round? – nawfal Jan 31 '12 at 13:10
  • @nawfal: Maybe it would work, but which pattern do I search for? – sharptooth Jan 31 '12 at 13:11
  • @sharptooth its difficult without knowing what makes a pattern out of your double. Had it been decimal, it would have been much easier. – nawfal Jan 31 '12 at 13:17
  • 1
    @sharptooth what if you get a double 23.123328 ? Does the two 3's make a pattern so that you need to round it from the start of 3? What about this: 23.1233328. In this case, there are three 3's in succession. Now the question is it two 3's or three 3's that make a pattern? Only you can decide. – nawfal Jan 31 '12 at 13:20
  • 1
    So many negitive rated comments here, perhaps you arnt explaining your problem well? – craig1231 Jan 31 '12 at 14:35
  • possible duplicate of [Formatting numbers with significant figures in C#](http://stackoverflow.com/questions/158172/formatting-numbers-with-significant-figures-in-c-sharp) – AakashM Jan 31 '12 at 14:46
  • Do you want a fixed number of significant figures or a fixed number of decimal places? Those are different rules, though they produce apparently similar results with much test data. When deciding, think about whether this is for use or for display: the former produces a number that is displayable and the latter produces something that looks better in a user interface. – Donal Fellows Feb 07 '12 at 12:18
  • @Donal Fellows: I added an answer that pretty much describes the desired solution. – sharptooth Feb 07 '12 at 12:20
  • [Math.Round](http://msdn.microsoft.com/en-us/library/75ks3aby.aspx) – craig1231 Jan 31 '12 at 13:00
  • I think we can safely assume that the OP knows of the existance of Math.Round :) – Jamiec Jan 31 '12 at 13:01
  • My problem is I don't know what to pass as `digits`. – sharptooth Jan 31 '12 at 13:01

8 Answers8

1
private double PrettyRound(double inp)
{
    string d = inp.ToString();
    d = d.Remove(0,d.IndexOf(',') + 1);
    int decRound = 1;
    bool onStartZeroes = true;
    for (int c = 1; c < d.Length; c++ )
    {
        if (!onStartZeroes && d[c] == d[c - 1])
            break;
        else
            decRound++;
        if (d[c] != '0')
            onStartZeroes = false;
    }

    inp = Math.Round(inp, decRound);
    return inp;
}

Test:

    double d1 = 0.13999; //no zeroes
    double d2 = 0.0079996; //zeroes
    double d3 = 0.00700956; //zeroes within decimal

    Response.Write(d1 + "<br/>" + d2 + "<br/>" + d3 + "<br/><br/>");

    d1 = PrettyRound(d1);
    d2 = PrettyRound(d2);
    d3 = PrettyRound(d3);

    Response.Write(d1 + "<br/>" + d2 + "<br/>" + d3 +"<br/><br/>");

Prints:

0,13999
0,0079996
0,00700956

0,14
0,008
0,007

Rounds your numbers as you wrote in your example..

f2lollpll
  • 997
  • 6
  • 15
  • this still wouldnt work, but just for the given examples. What about 0.428571428571? Your code would not round off the repetition. – nawfal Jan 31 '12 at 13:27
  • @nawfal: that wasn't asked in the question. Its about rounding off, not detecting recursion (as I read it). – Chris Jan 31 '12 at 13:31
  • Though I do think it treats 0.007300003 incorrectly. I believe this should be 0.0073 but it returns the full precision value. – Chris Jan 31 '12 at 13:33
  • @Chris he mentions about noise. Anything that repeats could be considered as noise in my book. Hence not just 0.3333, even 0.121212 contains noise. – nawfal Jan 31 '12 at 13:34
  • @nawfal I read it as when you reach those re-occuring numbers (10/3 = 0.3333) then it should stop it (0.3). Since your test doesn't have any double occuring numbers it wouldn't round :) I read it with my eyes.. not sure that's what he want, though. – f2lollpll Jan 31 '12 at 13:36
  • @nawfal: that's not what I consider noise... They seem like significant numbers to me. I assume its the problem with floating point numbers not recording decimal numbers perfectly and thus ending with the 99996 type bits when viewed in decimal form. I assume the noise was this variation from the "true" value. To be honest though its impossible to say which of our interpretations is correct, we'll just have to wait for the OP. I am curious though, does the existance of the pattern make it noise? Would you give more precision to what looks like a random string of digits? eg 0.1278384712435... – Chris Jan 31 '12 at 13:40
  • @Chris mm it depends on OP. Yes stripping off decimal places do trade precision. So there has to be some understanding on what should be the minimum precision. And to your question, yes there requires a limit on maximum precision as well, both which only the OP knows – nawfal Jan 31 '12 at 13:55
1

You seem to want to output a value which is not very different to the input value, so try increasing numbers of digits until a given error is achieved:

    static double Round(double input, double errorDesired)
    {
        if (input == 0.0) 
            return 0.0;

        for (int decimals = 0; decimals < 17; ++decimals)
        {
            var output = Math.Round(input, decimals);
            var errorAchieved = Math.Abs((output - input) / input);

            if (errorAchieved <= errorDesired)
                return output;
        }

        return input;
    }
}


    static void Main(string[] args)
    {
        foreach (var input in new[] { 0.13999, 0.0079996, 0.12345 })
        {
            Console.WriteLine("{0} -> {1}         (.1%)", input, Round(input, 0.001));
            Console.WriteLine("{0} -> {1}         (1%)", input, Round(input, 0.01));
            Console.WriteLine("{0} -> {1}         (10%)", input, Round(input, 0.1));
        }
    }
Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
0

You can do it without converting to string. This is what I created fast:

private static double RoundDecimal(double number)
{
   double temp2 = number;
   int temp, counter = 0;

   do
   {
      temp2 = 10 * temp2;
      temp = (int)temp2;

      counter++;
   } while (temp < 1);

   return Math.Round(number, counter < 2 ? 2 : counter);
}

or

private static double RoundDecimal(double number)
{
       int counter = 0;

       if (number > 0) { 
          counter = Math.Abs((int) Math.Log10(number)) + 1;

       return Math.Round(arv, counter < 2 ? 2 : counter);
}
evilone
  • 22,410
  • 7
  • 80
  • 107
0

I can think of a solution though it isn't very efficient...

My assumption is that you can tell when a number is in the "best" human readable format when extra digits make no difference to how it is rounded.

eg in the example of 0,13999 rounding it to various numbers of decimal places gives:

0
0.1
0.14
0.14
0.14
0.13999

I'd suggest that you could loop through and detect that stable patch and cut off there.

This method seems to do this:

public double CustomRound(double d)
{
    double currentRound = 0;
    int stability = 0;
    int roundLevel = 0;
    while (stability < 3)
    {
        roundLevel++;
        double current = Math.Round(d, roundLevel);
        if (current == currentRound)
        {
            stability++;
        }
        else
        {
            stability = 1;
            currentRound=current;
        }
    }
    return Math.Round(d, roundLevel);
}

This code might be cleanable but it does the job and is a sufficient proof of concept. :)

I should emphasise that that initial assumption (that no change when rounding) is the criteria we are looking at which means that something like 0.3333333333 will not get rounded at all. With the examples given I'm unable to say if this is correct or not but I assume if this is a double issues that the problem is with the very slight variations from the "right" value and the value as a double.

Chris
  • 27,210
  • 6
  • 71
  • 92
0

Heres what I tried:

public decimal myRounding(decimal number)
{
   double log10 = Math.Log10((double) number);
   int precision = (int)(log10 >= 0 ? 0 : Math.Abs(log10)) + (number < 0.01m ? 1 : 2);
   return  Math.Round(number, precision);
}

test:

Console.WriteLine(myRounding(0.0000019999m)); //0.000002
Console.WriteLine(myRounding(0.0003019999m)); //0.0003
Console.WriteLine(myRounding(2.56777777m));   //2.57
Console.WriteLine(myRounding(0.13999m));      //0.14
Console.WriteLine(myRounding(0.0079996m));    //0.008
Renatas M.
  • 11,694
  • 1
  • 43
  • 62
  • Just FYI: you code will throw `System.ArgumentOutOfRangeException: Rounding digits must be between 0 and 15, inclusive.` for numbers >= 1000 (Log10(number) >= 3) – Igor Korkhov Jan 31 '12 at 15:00
0

After giving it another thought I did the following and looks like it does what I want so far.

I iterate over the number of digits and compare Round( value, number ) and Round( value, number + 1 ). Once they are equal (not == of course - I compare the difference against a small number) then number is the number of digits I'm looking for.

sharptooth
  • 167,383
  • 100
  • 513
  • 979
-1

Double.ToString() can take a string format as an argument. This will display as many characters as you require, rounding to the decimal place. E.G:

double Value = 1054.32179;
MessageBox.Show(Value.ToString("0.000"));

Will display "1054.322".

Source

Generic formats (i.e, pre-generated)

How to generate custom formats

James Boyden
  • 454
  • 4
  • 8
-4

You can use no of digits with Math.Round Function

Double doubleValue = 4.052102;
Math.Round(doubleValue, 2);

This will return 4.05 as your required answer.

This is tested code, can u explain me how i am wrong. So i need to change.

abc
  • 73
  • 2
  • 6
  • This does not address the user's problem. He does not know how accurate the rounding will be, so always round to two decmial values, is not acceptable. – Security Hound Jan 31 '12 at 13:24