1

How can we format a number to currency without trailing zero decimal numbers? Basically it should behave as the "C" format specifier but without trailing zeroes. Below are the test cases.

value   | en-US  | fr-FR
1       | $1     | 1 €
1.0     | $1     | 1 €
1.1     | $1.1   | 1,1 €
1.10    | $1.1   | 1,1 €
-1      | ($1)   | -1 €
-1.0    | ($1)   | -1 €
-1.1    | ($1.1) | -1,1 €
1000    | $1,000 | 1 000 €
1000.0  | $1,000 | 1 000 €

Is there a way to achieve this behavior by leveraging the "C" format specifier?

side note: I am continuing from this related wpf question but focusing on the formatting part and with more exhaustive test cases.

Jan Paolo Go
  • 5,842
  • 4
  • 22
  • 50
  • Couldn't you just do a `.replace(".0","")`? – 4ndy Dec 16 '18 at 01:55
  • @4ndy Thanks for the suggestion. I've added more test cases. see `1.10` – Jan Paolo Go Dec 16 '18 at 01:57
  • Have a look here: https://stackoverflow.com/a/4525983 - you would have to watch out for long trails (e.g. 0.0001). Have a read through the comments. – 4ndy Dec 16 '18 at 02:01
  • 1
    That only deals with numbers. `Currency` seems to be a different beast. – Jan Paolo Go Dec 16 '18 at 02:03
  • Since you are willing to write your own formatting code just get currency formatting configuration data from NumberFormat, use it to construct formatting string yourself and then insert number formatted with "g". – Alexei Levenkov Dec 16 '18 at 02:41
  • Have a look at this link: https://stackoverflow.com/a/53786048/2146626 – 4ndy Dec 16 '18 at 02:43
  • Side note: many locales have strict requirements on whether it is ok or not to drop numbers after decimal point in currency values. Make sure to check with whoever will be responsible for such violations before rolling your custom formatting worldwide. – Alexei Levenkov Dec 16 '18 at 02:45
  • @4ndy why you are suggesting to look at the question already asked by OP and linked from this question? Not exactly clear what to find there (code suggested in the answer does not take into account culture specific information about currency formatting and thus not enough to produce correct results but rather good starting point) – Alexei Levenkov Dec 16 '18 at 02:48
  • @AlexeiLevenkov Thanks for pointing out the strict requirements on some locales. We'll keep that in mind. The formatting will be on only on the UI to save some real estate on user's screen. – Jan Paolo Go Dec 16 '18 at 03:03
  • Take a loot at the NumberFormatInfo for the current culture. That should have what you're looking for: https://learn.microsoft.com/en-us/dotnet/api/system.globalization.numberformatinfo?view=netframework-4.7.2 – Flydog57 Dec 16 '18 at 03:27

2 Answers2

0

I think I was able to answer my question with the following:

public static class DecimalExtensions
{
    /// <summary>
    ///     Converts a numeric value to its equivalent currency string representation using the specified culture-specific format information.
    /// </summary>
    /// <param name="value">The value to be converted.</param>
    /// <param name="provider">An object that supplies culture-specific formatting information.</param>
    /// <returns>The currency string representation of the value as specified by <paramref name="provider" />.</returns>
    public static string ToCurrency(this decimal value, IFormatProvider provider) =>
        /// Use "1" (or "-1" if value is negative)
        /// as a placeholder for the actual value.
        (value < 0 ? -1 : 1)

        /// Format as a currency using the "C" format specifier.
        .ToString("C0", provider)

        /// Convert the absolute value to its string representation
        /// then replace the placeholder "1".
        /// We used absolute value since the negative sign
        /// is already converted to its string representation
        /// using the "C" format specifier.
        .Replace("1", Math.Abs(value).ToString("#,0.############################", provider));
}

Tests

public class ToCurrencyTests
{
    private string ToCurrency(decimal value, string cultureName) =>
        value.ToCurrency(CultureInfo.GetCultureInfo(cultureName));

    [Theory]
    [MemberData(nameof(enUS))]
    public void ToCurrency_enUS(decimal value, string expected) =>
    Assert.Equal(expected, ToCurrency(value, "en-US"));

    [Theory]
    [MemberData(nameof(frFR))]
    public void ToCurrency_frFR(decimal value, string expected) =>
        Assert.Equal(expected, ToCurrency(value, "fr-FR"));

    public static TheoryData<decimal, string> enUS =>
        new TheoryData<decimal, string>
        {
            { 1m, "$1" },
            { 1.0m, "$1" },
            { 1.1m, "$1.1" },
            { 1.10m, "$1.1" },
            { -1m, "($1)" },
            { -1.0m, "($1)" },
            { -1.1m, "($1.1)" },
            { 1000m, "$1,000" },
            { 1000.0m, "$1,000" },
            { 123456789.123456789m, "$123,456,789.123456789" },
            { .0000000000000000000000000001m, "$0.0000000000000000000000000001" }
        };

    /// <remarks>
    ///     Note that the group separator used here is a non-breaking space ' ' (i.e. &nbsp; or char 160)
    /// </remarks>
    public static TheoryData<decimal, string> frFR =>
        new TheoryData<decimal, string>
        {
            { 1m, "1 €" },
            { 1.0m, "1 €" },
            { 1.1m, "1,1 €" },
            { 1.10m, "1,1 €" },
            { -1m, "-1 €" },
            { -1.0m, "-1 €" },
            { -1.1m, "-1,1 €" },
            { 1000m, "1 000 €" },
            { 1000.0m, "1 000 €" },
            { 123456789.123456789m, "123 456 789,123456789 €" },
            { .0000000000000000000000000001m, "0,0000000000000000000000000001 €" }
        };
}

EDIT: copy non-breaking space here http://www.unicode-symbol.com/u/00A0.html

Jan Paolo Go
  • 5,842
  • 4
  • 22
  • 50
-2

The following format string removes all trailing zeros (or the whole fraction part if it contains only zero digits).

value.ToString("#,0.#####");

To handle negative numbers, combine that with conditional formatting

value.ToString("#,0.#####;(#,0.#####)");

Farzan Hajian
  • 1,799
  • 17
  • 27