0

This is so many times repeated at SO, but I would want to state my question explicitly.

How is a decimal which would look like 2.0100 "rightly" presented to the user as 
another "decimal" 2.01?

I see a lot of questions on SO where the input is a string "2.0100" and need a decimal 2.01 out of it and questions where they need decimal 2.0100 to be represented as string "2.01". All this can be achieved by basic string.Trim, decimal.Parse etc. And these are some of the approaches followed:

  1. decimal.Parse(2.0100.ToString("G29"))

  2. Using # literal

  3. Many string.Format options.

  4. Various Regex options

  5. My own one I used till now:

    if (2.0100 == 0)
        return 0;
    decimal p = decimal.Parse(2.0100.ToString().TrimEnd('0'));
    return p == 2.0100 ? p : 2.0100;
    

But I believe there has to be some correct way of doing it in .Net (at least 4) which deals with numeric operation and not string operation. I am asking for something that is not dealing with the decimal as string because I feel that ain't the right method to do this. I'm trying to learn something new. And would fancy my chances of seeing at least .1 seconds of performance gain since I'm pulling tens of thousands of decimal values from database :)

Question 2: If it aint present in .Net, which is the most efficient string method to get a presentable value for the decimal?

Edit: I do not just want a decimal to be presented it to users. In that case I can use it as a string. I do want it as decimal back. I will have to process on those decimal values later. So going by ToString approach, I first needs to convert it to string, and then again parse it to decimal. I am looking for something that doesn't deal with String class. Some option to convert decimal .20100 to decimal .201?

nawfal
  • 70,104
  • 56
  • 326
  • 368
  • 2
    you should consider splitting your questions into three separate questions, SO doesn't work well asking multiple questions in one question – Anthony Shaw Jan 17 '12 at 14:04
  • Question 1 and 2 are just the same. Accepted about question 3. I'll do accordingly. Thanks. – nawfal Jan 17 '12 at 14:07

5 Answers5

2

The "extra zeroes" that occur in a decimal value are there because the System.Decimal type stores those zeroes explicitly. For a System.Decimal, 1.23400 is a different value from 1.234, even though numerically they are equal:

The scaling factor also preserves any trailing zeroes in a Decimal number. Trailing zeroes do not affect the value of a Decimal number in arithmetic or comparison operations. However, trailing zeroes can be revealed by the ToString method if an appropriate format string is applied.

It's important to have the zeroes because many Decimal computations involve significant digits, which are a necessity of many scientific and high-precision calculations.

In your case, you don't care about them, but the appropriate answer is not "change Decimal for my particular application so that it doesn't store those zeroes". Instead, it's "present this value in a way that's meaningful to my users". And that's what decimal.ToString() is for.

John Feminella
  • 303,634
  • 46
  • 339
  • 357
  • I do not just want it to present it to users. In that case I can use it as a string. I do want it as decimal back. I will have to process on those decimal values later. So going by ToString approach, I first needs to convert it to string, and then again parse it to decimal. I'm surprised there isn't a cleaner method for this..I'll edit it in my question. – nawfal Jan 17 '12 at 15:12
  • Then don't store the decimal with the extra zeroes and use exactly the precision you want to use. – John Feminella Jan 17 '12 at 16:16
  • I do not store it with any extra precision. The value I store is .201 exactly. I can see it in ms access the value as .201 itself. I mentioned it in question – nawfal Jan 17 '12 at 16:26
  • Jon, I understood your comment now, I meant I do not know what precision it would be. Values can be .200000001 and even .201. Whatever the case is I do not want to pull .2000000001000000 and .2010000000000 back from database – nawfal Jan 17 '12 at 17:20
  • @JohnFeminella: Are there any circumstances in which changing the number of trailing zeroes on a Decimal used in computation will affect the *numerical value* of that computation? From a "scientific arithmetic" perspective, 123.4 divided by three is 41.13, not 41.133333333333333333333333333, but the Decimal type would yield the latter answer. I recognize that from a computational-efficiency standpoint, adding 12.05 to 0.95 and then adding that result to 0.13 will be faster if the trailing zeroes are kept after the first addition, but is there any semantic difference other than display format? – supercat Jan 19 '12 at 17:47
1

The easiest way to format a decimal in a given format for the user is to use decimal.ToString()'s formatting options.

As for representing the value, 2.01 is equal to 2.0100. As long as you're within decimal's precision, it shouldn't matter how the value is stored in the system. You should only be worried with properly formatting the value for the user.

Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
  • Accepted this would be the easiest, but does that mean there's not an approach without going for anything related to String class? – nawfal Jan 17 '12 at 14:14
  • 1
    @nawfal - Anything related to output formatting is going to format the decimal as a string (whether you do it directly through toString or you use formatting with DataBinding). – Justin Niessner Jan 17 '12 at 14:15
  • I still feel there has to be something to do without `String`. Let me say, how am I going to round 5.01 to its int value? I wouldnt be just string formatting to trim everything after the dot including the dot. I would use the Math class or something like that. I feel the same here too. There has to be something that doesn't convert to string once and then convert back to decimal.Let me wait for other answers and decide.. – nawfal Jan 17 '12 at 14:20
  • 2
    @nawfal - You're missing the point. Mathematically there's nothing different between 2.01 and 2.0100. You're just seeing the unused precision that decimal has. It could just as easily be 2.0100000000000. – Justin Niessner Jan 17 '12 at 14:26
1

Numbers are numbers and strings are strings. The concept of "two-ness" represented as a string in the English language is 2. The concept of "two-ness" represented as a number is not really possibly to show because when you observe a number you see it as a string. But for the sake of argument it could be 2 or 2.0 or 02 or 02.0 or even 10/5. These are all representations of "two-ness".

Your database isn't actually returning 2.0100, something that you are inspecting that value with is converting it to a string and representing it that way for you. Whether a number has zeros at the end of it is merely a preference of string formatting, always.

Also, never call Decimal.parse() on a decimal, it doesn't make sense. If you want to convert a decimal literal to a string just call (2.0100).ToString().TrimEnd('0')

Chris Haas
  • 53,986
  • 12
  • 141
  • 274
  • "Whether a number has zeros at the end of it is merely a preference of string formatting, always." -- That is not accurate. The System.Decimal type explicitly stores extra zeroes for significant-digits reasons. See http://stackoverflow.com/a/8896260/75170. – John Feminella Jan 17 '12 at 14:26
1

As noted, a decimal that internally stores 2.0100 could differ from one that stores 2.01, and the default behaviour of ToString() can be affected.

I recommend that you never make use of this.

Firstly, decimal.Parse("2.0100") == decimal.Parse("2.01") returns true. While their internal representations are different this is IMO unfortunate. When I'm using decimal with a value of 2.01 I want to be thinking:

2.01

Not:

struct decimal { private int flags; private int hi; private int lo; private int mid; /methods that make this actually useful/ }

While different means of storing 2.01 in the above structure might exist, 2.01 remains 2.01.

If you care about it being presented as 2.01 and not as 2.0 or 2.0100 then you care about a string representation. Your concern is about how a decimal is represented as a string, and that is how you should think about it at that stage. Consider the rule in question (minimum and maximum significant figures shown, and whether to include or exclude trailing zeros) and then code a ToString call appropriate.

And do this close to where the string is used.

When you care about 2.01, then deal with it as a decimal, and consider any code where the difference between 2.01 and 2.0100 matters to be a bug, and fix it.

Have a clear split in your code between where you are using strings, and where you are using decimals.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • Why is the difference between `2.01` and `2.0100` significant to your application? – Jon Hanna Jan 17 '12 at 15:27
  • I need to display it to users. I need populate those values in datagridview. I would need to reuse the value later. So I do not want .20100 to be shown in the gridview. Instead .201 would be a better value to be shown. – nawfal Jan 17 '12 at 16:30
  • So, call `ToString()` appropriately at that point. If you just pass in the decimal, it still results in `ToString()` being called, so it's not like there's any more perf cost, just make it the overload of `ToString()` that suits your purposes. – Jon Hanna Jan 17 '12 at 16:33
  • Not the only, you **could** play around setting values explicitly in decimals so the default `ToString()` happens to work. More work, and makes the code brittle and subject to surprises where you miss points where math was done on the decimal. You've a requirement for a particular string form of a decimal, you've a means to produce a particular string form of a decimal, these two are natural partners. – Jon Hanna Jan 17 '12 at 16:51
1

Ok, so I'm answering for myself, I got a solution.

return d / 1.00000000000000000000000000000m

That just does it. I did some benchmarking as well (time presented as comments are mode, not mean):

Method:

    internal static double Calculate(Action act)
    {
        Stopwatch sw = new Stopwatch();

        sw.Start();
        act();
        sw.Stop();

        return sw.Elapsed.TotalSeconds;
    }

Candidates:

  1. return decimal.Parse(string.Format("{0:0.#############################}", d));
    //0.02ms
    
  2. return decimal.Parse(d.ToString("0.#############################"));
    //0.014ms
    
  3. if (d == 0)
        return 0;
    
    decimal p = decimal.Parse(d.ToString().TrimEnd('0').TrimEnd('.'));
    return p == d ? p : d;
    //0.016ms
    
  4. return decimal.Parse(d.ToString("G29"));
    //0.012ms
    
  5. return d / 1.00000000000000000000000000000m;
    //0.007ms
    

Needless to cover regex options. I dont mean to say performance makes a lot of difference. I'm just pulling 5k to 20k rows at a time. But still it's nice to know a simpler and cleaner alternative to string approach exists.

If you liked the answer, pls redirect your votes to here or here or here.

Community
  • 1
  • 1
nawfal
  • 70,104
  • 56
  • 326
  • 368