dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.32k stars 4.74k forks source link

Formatting currency values without currency symbol #28913

Open mikernet opened 5 years ago

mikernet commented 5 years ago

Currently this is very difficult, error prone and the approach is terrible in terms of performance.

Related StackOverflow question:

https://stackoverflow.com/questions/1048643/format-a-double-value-like-currency-but-without-the-currency-sign-c/3976048

Jon Skeet's approach can be made to work but requires cloning the NumberFormatInfo object and trimming the final string, causing lots of overhead and unneccessary allocations. Since I'm doing this in an IFormattable.ToString() implementation using the provided formatProvider argument to get the currency format information, this would need to happen every single time ToString() is called.

I propose a new standard numeric format string, perhaps M (for money) would work, which uses the currency formatting information to format the number but leaves off the currency symbol.

I would be happy to do the PRs on it if it would be accepted. I've run into this many times over the years and usually I just settle for formatting the currency using the number format instead of the currency format as I've found most people do in this situation, which is less than ideal considering that in some cultures the formats are significantly different.

tarekgh commented 5 years ago

What exactly the output would be. If the ask to have the exact output of "C" format without the current symbol, then there is some cases which will not make any sense with the negative currency formats. for example, if the current format is # $- what you expect to get at that time? something like 123.45 -? this is obviously wrong because having the currency sign in such format make a difference.

If the ask just getting the number without any other character (e.g. ( ) or spaces), then this can be easily achieved in performed way without any extra allocation or cloning. Here is some code example just for the idea demonstration.

        public static string FormatMoney(Decimal d)
        {
            Span<char> format = stackalloc char[8];
            format[0] = 'N';
            CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalDigits.TryFormat(format.Slice(1), out int written);
            format = format.Slice(0, written + 1);

            Span<char> buffer = stackalloc char[40];
            d.TryFormat(buffer, out written, format);
            buffer = buffer.Slice(0, written);

            return buffer.ToString();
        }

Note that "C" formatting will always format the positive numbers as the code above does. if we think to support the negative in sensible way, we can still do that by adding a couple of lines to the code above.

mikernet commented 5 years ago

123.45- would be the output for cases where the format is n $-.

Unfortunately your code does not produce the same output. Regions can have different digit and group delimiters for currencies than they do for normal numbers. That’s part of what I was alluding to in the OP. Some regions flip the role of commas and periods in currencies. It's possible that digit groupings change as well in standard regions, and definitely possible if the user customized their region settings. It's a completely independent format with its own settings.

I just know from personal experience that this comes up all the time as a common requirement, but I realize that's anecdotal. I recently went through the pain of writing the methods needed to do this efficiently in a variety of scenarios so it's not really for my benefit anymore as much as it is for others just trying to get currency formatted numbers without the symbol.

Given the number of views on the question I think it's probably quite common and it's not just my circle of devs that regularly run into this. That number would probably be a lot higher if devs realized the number format is often completely different than the currency format but I think that fact is often overlooked.

tarekgh commented 5 years ago

123.45- would be the output for cases where the format is n $-.

Wasn't this wrong? we have other patterns like #$- which means the space has a meaning here.

Just to clarify, I am not pushing back on the proposal. I was just trying to provide a work around for now. and still we can come up with work around that work with no cloning or extra allocations. let me know if you want me provide this work around for now. Also, we need to look if there is any standard format specifiers for such case (formatted currencies without the currency sign).

mikernet commented 5 years ago

@tarekgh It's hard to say from just the negative currency format. Giving it some more thought, I'm thinking that in this special case we could look to the negative number format to see if they use format n- or n - and make a determination based on that. If they don't use either of those negative number formats then we can just pick one of them to default to.

I don't think any standard regions use n $- as the currency format and don't use n- or n - as the negative number format so falling back to a default would be very rare and only happen if the user customized their settings to something rather odd. I don't see that as a big deal.

tarekgh commented 5 years ago

@mikernet we can decide whatever we want, my whole point was, we may run into cases the caller expect something and we return something different according to our decision. In other word, we'll not be perfect anyway with the negative patterns.

mikernet commented 5 years ago

I think the solution presented adequately addresses this common problem. It does it “perfectly” 99.99% of the time, and 0.01% of the time (if that...I bet it’s much lower) when someone changed their regional settings to something very uncommon it still produces sufficiently good output that at worst has an extra space.

It appears to me to be the best possible solution to the issue and the way most people do it now is much worse. Do you have a better idea?

Really though, how often do you think someone will have customized their currency format settings to use n $- but have their number format be something other than n- or n -? 1 in 10,000? 100,000? A million? I don’t think the bar for a feature needs to be 100% perfection, it just has to address a common problem and do so well enough to be widely useful.

tarekgh commented 5 years ago

The question I have in my mid is, what are the scenarios people really need this feature? honestly I am not seeing this is main stream scenario. I understand we can get a couple of people using this but doesn't mean this is needed by majority of people especially there is a work around.

Again, I am not pushing back more than trying to understand how this feature is really going to be used by many people. and what are the real world scenarios used for. do you have more real info around that?

mikernet commented 5 years ago

Any time an application needs to support multiple currencies, which is quite common. Local currency display formats are useless in this scenario - we need to show them like USD -1234.99 or AUD (54.99) or EUR 1234.99 -, depending on the user's desired currency display.

There are other times customers don't want the currency symbol to show up everywhere for space reasons, and only show on the total line for example.

tarekgh commented 5 years ago

OK, we'll try to look at this one in the next release as we'll need to review it with the design committee. thanks for reporting the issue.