Search code examples
c#formatdecimaltostring

decimal.ToString() prints a number with a question mark


I am trying to identify a weird behaviour of the ToString method when printing a decimal in Currency Mode in a console app. The following code (sb is a StringBuilder and all the variables are decimal):

            sb
                .Append("Fastest Release: ")
                .Append(TimeSpan.FromSeconds(fastestReleaseSeconds))
                .Append(Environment.NewLine)
                .Append("Average Release Time: ")
                .Append(TimeSpan.FromSeconds(avgReleaseSeconds))
                .Append(Environment.NewLine)
                .Append("Slowest release: ")
                .Append(TimeSpan.FromSeconds(slowestReleaseSeconds))
                .Append(Environment.NewLine);
            sb
                .Append("Total Euro value of Base Currency at engage: ")
                .Append(totValueOfBaseAtEngage.ToString("C", CultureInfo.CurrentCulture))
                .Append(Environment.NewLine)
                .Append("Total current Euro value of base currency: ")
                .Append(totCurrentEuroValueOfBase.ToString("C", CultureInfo.CurrentCulture))
                .Append(Environment.NewLine);
            sb
                .Append("Total Euro value of Quote Currency at engage: ")
                .Append(totValueOfQuoteAtEngage.ToString("C", CultureInfo.CurrentCulture))
                .Append(Environment.NewLine)
                .Append("Total current Euro value of quote currency: ")
                .Append(totCurrentEuroValueOfQuote.ToString("C", CultureInfo.CurrentCulture))
                .Append(Environment.NewLine);

prints the following text:

Fastest Release: 00:00:33

Average Release Time: 08:42:48.0952380

Slowest release: 3.17:39:03

Total Euro value of Base Currency at engage: 2.300,00 ?

Total current Euro value of base currency: 2.208,48 ?

Total Euro value of Quote Currency at engage: -2.300,00 ?

Total current Euro value of quote currency: -2.385,44 ?

Where is the question mark coming from? I tried different cultures and also leaving that out, but the question mark remains. If I use the default ToString() without specifiers ("C") the question mark disappears.

Thanks a lot Alex


Solution

  • Thanks to PMF for the great hint! My ToString was trying to print a Euro symbol without success. I just appended Console.OutputEncoding = Encoding.UTF8; after the instantiation of theStringBuilder and the output gets correctly as follows:

    Total Euro value of Base Currency at engage: 2.300,00 €

    Total current Euro value of base currency: 2208,9629780475026176247572537

    Total Euro value of Quote Currency at engage: -2.300,00 €

    Total current Euro value of quote currency: -2.386,93 €

    Base delta: -91,04 € Quote delta: -86,93 €

    The second line is a test (I removed the "C" format to see what was happening under the hood).

    I tested also Console.OutputEncoding = Encoding.Unicode;` and it works fine as well.

    So, to understand where the issue was I tried to print the original encoding like this:

    var originalEncoding = Console.OutputEncoding;
    
    Console.OutputEncoding = Encoding.Unicode;
     
    sb.Append($"Original Encoding: {originalEncoding}").Append($" Current Encoding: {Console.OutputEncoding}");
    

    and it prints

    Original Encoding: System.Text.OSEncoding Current Encoding: System.Text.UnicodeEncoding

    The page https://learn.microsoft.com/en-us/dotnet/api/system.text.encoding?view=net-6.0 Does not provide anything on OSEncoding so i need to dig deeper to find out exactly what all this means. Thanks again!

    Edit: I ended on Joel Spolsky website and found https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/ which is an all-in-one explanation of everything related to Unicode and UTF-8. I should join the submarine crew to peel onions as well.

    Moreover, MSDN provides clarifications at https://learn.microsoft.com/en-us/dotnet/api/system.console?view=net-6.0 (see para "Unicode support for the Console" and ".NET Core notes"). In particular, the right method to call to get the current code page used by the console is GetConsoleCP. Console is not said to load the current locale code page (Italian in my case) if it is not part of the limited subset immediately available and no reference is made to System.Text.Encoding.CodePages.dll assembly.