Most efficient/right practice to trim the meaningl

2019-09-14 16:47发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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')



回答4:

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.



回答5:

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.