summaryrefslogtreecommitdiff
path: root/src/System.Private.CoreLib/shared/System/Buffers/Text/Utf8Formatter/Utf8Formatter.Decimal.cs
blob: e44589ccb474b3ca5956a4f0f33fe57e9b6a55db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;

namespace System.Buffers.Text
{
    public static partial class Utf8Formatter
    {
        /// <summary>
        /// Formats a Decimal as a UTF8 string.
        /// </summary>
        /// <param name="value">Value to format</param>
        /// <param name="destination">Buffer to write the UTF8-formatted value to</param>
        /// <param name="bytesWritten">Receives the length of the formatted text in bytes</param>
        /// <param name="format">The standard format to use</param>
        /// <returns>
        /// true for success. "bytesWritten" contains the length of the formatted text in bytes.
        /// false if buffer was too short. Iteratively increase the size of the buffer and retry until it succeeds. 
        /// </returns>
        /// <remarks>
        /// Formats supported:
        ///     G/g  (default)  
        ///     F/f             12.45       Fixed point
        ///     E/e             1.245000e1  Exponential
        /// </remarks>
        /// <exceptions>
        /// <cref>System.FormatException</cref> if the format is not valid for this data type.
        /// </exceptions>
        public static unsafe bool TryFormat(decimal value, Span<byte> destination, out int bytesWritten, StandardFormat format = default)
        {
            if (format.IsDefault)
            {
                format = 'G';
            }

            switch (format.Symbol)
            {
                case 'g':
                case 'G':
                    {
                        if (format.Precision != StandardFormat.NoPrecision)
                            throw new NotSupportedException(SR.Argument_GWithPrecisionNotSupported);

                        byte* pDigits = stackalloc byte[Number.DecimalNumberBufferLength];
                        Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, Number.DecimalNumberBufferLength);

                        Number.DecimalToNumber(ref value, ref number);
                        bool success = TryFormatDecimalG(ref number, destination, out bytesWritten);
#if DEBUG
                        // This DEBUG segment exists to close a code coverage hole inside TryFormatDecimalG(). Because we don't call RoundNumber() on this path, we have no way to feed
                        // TryFormatDecimalG() a number where trailing zeros before the decimal point have been cropped. So if the chance comes up, we'll crop the zeroes
                        // ourselves and make a second call to ensure we get the same outcome.
                        if (success)
                        {
                            Span<byte> digits = number.Digits;
                            int numDigits = number.DigitsCount;
                            if (numDigits != 0 && number.Scale == numDigits && digits[numDigits - 1] == '0')
                            {
                                while (numDigits != 0 && digits[numDigits - 1] == '0')
                                {
                                    digits[numDigits - 1] = 0;
                                    numDigits--;
                                }

                                number.DigitsCount = numDigits;
                                number.CheckConsistency();

                                byte[] buffer2 = new byte[destination.Length];
                                bool success2 = TryFormatDecimalG(ref number, buffer2, out int bytesWritten2);
                                Debug.Assert(success2);
                                Debug.Assert(bytesWritten2 == bytesWritten);
                                for (int i = 0; i < bytesWritten; i++)
                                {
                                    Debug.Assert(destination[i] == buffer2[i]);
                                }
                            }

                        }
#endif // DEBUG
                        return success;
                    }

                case 'f':
                case 'F':
                    {
                        byte* pDigits = stackalloc byte[Number.DecimalNumberBufferLength];
                        Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, Number.DecimalNumberBufferLength);

                        Number.DecimalToNumber(ref value, ref number);
                        byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)2 : format.Precision;
                        Number.RoundNumber(ref number, number.Scale + precision);
                        return TryFormatDecimalF(ref number, destination, out bytesWritten, precision);
                    }

                case 'e':
                case 'E':
                    {
                        byte* pDigits = stackalloc byte[Number.DecimalNumberBufferLength];
                        Number.NumberBuffer number = new Number.NumberBuffer(Number.NumberBufferKind.Decimal, pDigits, Number.DecimalNumberBufferLength);

                        Number.DecimalToNumber(ref value, ref number);
                        byte precision = (format.Precision == StandardFormat.NoPrecision) ? (byte)6 : format.Precision;
                        Number.RoundNumber(ref number, precision + 1);
                        return TryFormatDecimalE(ref number, destination, out bytesWritten, precision, exponentSymbol: (byte)format.Symbol);
                    }

                default:
                    return FormattingHelpers.TryFormatThrowFormatException(out bytesWritten);
            }
        }
    }
}