Add Decimal.TryFormat span-based method (#15145)
authorStephen Toub <stoub@microsoft.com>
Wed, 22 Nov 2017 05:13:37 +0000 (00:13 -0500)
committerGitHub <noreply@github.com>
Wed, 22 Nov 2017 05:13:37 +0000 (00:13 -0500)
* Move decimal formatting to managed code

- Move decimal formatting to shared (decimal itself is still not shared)
- Delete VM's decimal formatting code

* Add Decimal.TryFormat span-based method

* Workaround ProjectN compiler bug

Apply the same attribution that's applied to the code in corert.

* Address PR feedback

* Temporarily undo explicit layout change

Seeing whether it's the cause of these failures on Unix (Ubuntu, CentOS, and OSX):
```
JIT.Directed.coverage.oldtests.lclfldadd_cs_r.lclfldadd_cs_r
JIT.Directed.perffix.primitivevt.mixed1_cs_ro.mixed1_cs_ro
JIT.Methodical.fp.exgen.1000w1d_cs_r.1000w1d_cs_r
JIT.Methodical.fp.exgen.1000w1d_cs_ro.1000w1d_cs_ro
JIT.Methodical.fp.exgen.1000w1d_cs_do.1000w1d_cs_do
JIT.Methodical.fp.exgen.1000w1d_cs_d.1000w1d_cs_d
JIT.Methodical.fp.exgen.10w5d_cs_d.10w5d_cs_d
JIT.Methodical.fp.exgen.10w5d_cs_do.10w5d_cs_do
JIT.Methodical.fp.exgen.10w5d_cs_ro.10w5d_cs_ro
JIT.Methodical.fp.exgen.200w1d-02_cs_do.200w1d-02_cs_do
JIT.Methodical.fp.exgen.10w5d_cs_r.10w5d_cs_r
JIT.Methodical.fp.exgen.200w1d-02_cs_ro.200w1d-02_cs_ro
JIT.Directed.perffix.primitivevt.mixed1_cs_do.mixed1_cs_do
```

Example failure:
```
FAILED   - JIT/Methodical/fp/exgen/200w1d-02_cs_ro/200w1d-02_cs_ro.sh
               BEGIN EXECUTION
               /mnt/j/workspace/dotnet_coreclr/master/checked_ubuntu_tst_prtest/bin/tests/Windows_NT.x64.Checked/Tests/coreoverlay/corerun 200w1d-02_cs_ro.exe

               Unhandled Exception: System.OverflowException: Value was either too large or too small for an Int32.
                  at System.Convert.ThrowInt32OverflowException()
                  at System.Convert.ToInt32(Int64 value)
                  at testout1.Func_0()
                  at testout1.Main()
               ./200w1d-02_cs_ro.sh: line 243: 101339 Aborted                 (core dumped) $_DebuggerFullPath "$CORE_ROOT/corerun" $ExePath $CLRTestExecutionArguments
               Expected: 100
               Actual: 134
               END EXECUTION - FAILED
```

src/classlibnative/bcltype/decimal.cpp
src/classlibnative/bcltype/decimal.h
src/classlibnative/bcltype/number.cpp
src/classlibnative/bcltype/number.h
src/mscorlib/System.Private.CoreLib.csproj
src/mscorlib/shared/System/Number.Formatting.cs
src/mscorlib/src/System/Decimal.DecCalc.cs [new file with mode: 0644]
src/mscorlib/src/System/Decimal.cs
src/mscorlib/src/System/Number.CoreCLR.cs
src/vm/ecalllist.h

index ed63391..c338e5d 100644 (file)
@@ -288,33 +288,6 @@ FCIMPL1(void, COMDecimal::DoTruncate, DECIMAL * d)
 }
 FCIMPLEND
 
-
-void COMDecimal::DecimalToNumber(DECIMAL* value, NUMBER* number)
-{
-    WRAPPER_NO_CONTRACT
-    _ASSERTE(number != NULL);
-    _ASSERTE(value != NULL);
-
-    wchar_t buffer[DECIMAL_PRECISION+1];
-    DECIMAL d = *value;
-    number->precision = DECIMAL_PRECISION;
-    number->sign = DECIMAL_SIGN(d)? 1: 0;
-    wchar_t* p = buffer + DECIMAL_PRECISION;
-    while (DECIMAL_MID32(d) | DECIMAL_HI32(d)) {
-        p = COMNumber::Int32ToDecChars(p, DecDivMod1E9(&d), 9);
-        _ASSERTE(p != NULL);
-    }
-    p = COMNumber::Int32ToDecChars(p, DECIMAL_LO32(d), 0);
-    _ASSERTE(p != NULL);
-    int i = (int) (buffer + DECIMAL_PRECISION - p);
-    number->scale = i - DECIMAL_SCALE(d);
-    wchar_t* dst = number->digits;
-    _ASSERTE(dst != NULL);
-    while (--i >= 0) *dst++ = *p++;
-    *dst = 0;
-    
-}
-
 int COMDecimal::NumberToDecimal(NUMBER* number, DECIMAL* value)
 {
     WRAPPER_NO_CONTRACT
index 6ce1fbe..b932420 100644 (file)
@@ -43,7 +43,6 @@ public:
     static FCDECL1(INT32, ToInt32, FC_DECIMAL d);      
     static FCDECL1(Object*, ToString, FC_DECIMAL d);
     
-    static void DecimalToNumber(DECIMAL* value, NUMBER* number);
     static int NumberToDecimal(NUMBER* number, DECIMAL* value);
     
 
index e399c82..eea2b2e 100644 (file)
@@ -2009,45 +2009,6 @@ ParseSection:
 #pragma warning(pop)
 #endif
 
-FCIMPL3_VII(Object*, COMNumber::FormatDecimal, FC_DECIMAL value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
-{
-    FCALL_CONTRACT;
-
-    NUMBER number;
-
-    wchar fmt;
-    int digits;
-
-    STRINGREF  refRetVal = NULL;
-    HELPER_METHOD_FRAME_BEGIN_RET_1(refRetVal);
-
-    struct _gc
-    {
-        STRINGREF   format;
-        NUMFMTREF   numfmt;
-    } gc;
-
-    gc.format = (STRINGREF) formatUNSAFE;
-    gc.numfmt = (NUMFMTREF) numfmtUNSAFE;
-
-    if (gc.numfmt == 0)
-        COMPlusThrowArgumentNull(W("NumberFormatInfo"));
-
-    COMDecimal::DecimalToNumber(&value, &number);
-
-    fmt = ParseFormatSpecifier(gc.format, &digits);
-    if (fmt != 0) {
-        refRetVal = NumberToString(&number, fmt, digits, gc.numfmt, TRUE);
-    } else {
-        refRetVal = NumberToStringFormat(&number, gc.format, gc.numfmt);
-    }
-
-    HELPER_METHOD_FRAME_END();
-
-    return OBJECTREFToObject(refRetVal);
-}
-FCIMPLEND
-
 FCIMPL3_VII(Object*, COMNumber::FormatDouble, double value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE)
 {
     FCALL_CONTRACT;
index 77f902f..e9651b6 100644 (file)
@@ -31,7 +31,6 @@ struct NUMBER {
 class COMNumber
 {
 public:
-    static FCDECL3_VII(Object*, FormatDecimal, FC_DECIMAL value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE);
     static FCDECL3_VII(Object*, FormatDouble,  double  value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE);
     static FCDECL3_VII(Object*, FormatSingle,  float   value, StringObject* formatUNSAFE, NumberFormatInfo* numfmtUNSAFE);
     static FCDECL2(FC_BOOL_RET, NumberBufferToDecimal, BYTE* number, DECIMAL* value);
index 9dd49af..6fa4900 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\Buffer.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Currency.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Decimal.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Decimal.DecCalc.cs" />
     <Compile Include="$(BclSourcesRoot)\System\DefaultBinder.CanConvert.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Empty.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Enum.cs" />
index 9cf13e6..919c06b 100644 (file)
@@ -50,6 +50,87 @@ namespace System
             "(#)", "-#", "- #", "#-", "# -",
         };
 
+        public static string FormatDecimal(decimal value, string format, NumberFormatInfo info)
+        {
+            char fmt = ParseFormatSpecifier(format, out int digits);
+
+            NumberBuffer number = default;
+            DecimalToNumber(value, ref number);
+
+            ValueStringBuilder sb;
+            unsafe
+            {
+                char* stackPtr = stackalloc char[CharStackBufferSize];
+                sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+            }
+
+            if (fmt != 0)
+            {
+                NumberToString(ref sb, ref number, fmt, digits, info, isDecimal:true);
+            }
+            else
+            {
+                NumberToStringFormat(ref sb, ref number, format, info);
+            }
+
+            return sb.GetString();
+        }
+
+        public static bool TryFormatDecimal(decimal value, string format, NumberFormatInfo info, Span<char> destination, out int charsWritten)
+        {
+            char fmt = ParseFormatSpecifier(format, out int digits);
+
+            NumberBuffer number = default;
+            DecimalToNumber(value, ref number);
+
+            ValueStringBuilder sb;
+            unsafe
+            {
+                char* stackPtr = stackalloc char[CharStackBufferSize];
+                sb = new ValueStringBuilder(new Span<char>(stackPtr, CharStackBufferSize));
+            }
+
+            if (fmt != 0)
+            {
+                NumberToString(ref sb, ref number, fmt, digits, info, isDecimal: true);
+            }
+            else
+            {
+                NumberToStringFormat(ref sb, ref number, format, info);
+            }
+
+            return sb.TryCopyTo(destination, out charsWritten);
+        }
+
+#if PROJECTN
+        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)]
+#endif
+        private static unsafe void DecimalToNumber(decimal value, ref NumberBuffer number)
+        {
+            decimal d = value;
+
+            char* buffer = number.digits;
+            number.precision = DecimalPrecision;
+            number.sign = d.IsNegative;
+
+            char* p = buffer + DecimalPrecision;
+            while ((d.Mid | d.High) != 0)
+            {
+                p = UInt32ToDecChars(p, decimal.DecDivMod1E9(ref d), 9);
+            }
+            p = UInt32ToDecChars(p, d.Low, 0);
+
+            int i = (int)(buffer + DecimalPrecision - p);
+            number.scale = i - d.Scale;
+
+            char* dst = number.digits;
+            while (--i >= 0)
+            {
+                *dst++ = *p++;
+            }
+            *dst = '\0';
+        }
+
         public static string FormatInt32(int value, string format, NumberFormatInfo info)
         {
             int digits;
diff --git a/src/mscorlib/src/System/Decimal.DecCalc.cs b/src/mscorlib/src/System/Decimal.DecCalc.cs
new file mode 100644 (file)
index 0000000..ded7558
--- /dev/null
@@ -0,0 +1,26 @@
+// 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.Runtime.CompilerServices;
+
+namespace System
+{
+    public partial struct Decimal
+    {
+        internal static uint DecDivMod1E9(ref decimal value)
+        {
+            return D32DivMod1E9(D32DivMod1E9(D32DivMod1E9(0,
+                                                          ref Unsafe.As<int, uint>(ref value.hi)),
+                                             ref Unsafe.As<int, uint>(ref value.mid)),
+                                ref Unsafe.As<int, uint>(ref value.lo));
+
+            uint D32DivMod1E9(uint hi32, ref uint lo32)
+            {
+                ulong n = (ulong)hi32 << 32 | lo32;
+                lo32 = (uint)(n / 1000000000);
+                return (uint)(n % 1000000000);
+            }
+        }
+    }
+}
index c2b67d6..2ea0ca5 100644 (file)
@@ -60,7 +60,7 @@ namespace System
     [Serializable]
     [System.Runtime.Versioning.NonVersionable] // This only applies to field layout
     [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
-    public struct Decimal : IFormattable, IComparable, IConvertible, IComparable<Decimal>, IEquatable<Decimal>, IDeserializationCallback
+    public partial struct Decimal : IFormattable, IComparable, IConvertible, IComparable<Decimal>, IEquatable<Decimal>, IDeserializationCallback
     {
         // Sign mask for the flags field. A value of zero in this bit indicates a
         // positive Decimal value, and a value of one in this bit indicates a
@@ -140,6 +140,9 @@ namespace System
         private int lo;
         private int mid;
 
+        internal uint High => (uint)hi;
+        internal uint Low => (uint)lo;
+        internal uint Mid => (uint)mid;
 
         // Constructs a zero Decimal.
         //public Decimal() {
@@ -363,6 +366,10 @@ namespace System
             return d1;
         }
 
+        internal bool IsNegative => (flags & SignMask) != 0;
+
+        internal int Scale => (byte)((uint)flags >> ScaleShift);
+
         // FCallAddSub adds or subtracts two decimal values.  On return, d1 contains the result
         // of the operation.  Passing in DECIMAL_ADD or DECIMAL_NEG for bSign indicates
         // addition or subtraction, respectively.
@@ -494,6 +501,10 @@ namespace System
             return Number.FormatDecimal(this, format, NumberFormatInfo.GetInstance(provider));
         }
 
+        public bool TryFormat(Span<char> destination, out int charsWritten, string format = null, IFormatProvider provider = null)
+        {
+            return Number.TryFormatDecimal(this, format, NumberFormatInfo.GetInstance(provider), destination, out charsWritten);
+        }
 
         // Converts a string to a Decimal. The string must consist of an optional
         // minus sign ("-") followed by a sequence of digits ("0" - "9"). The
index 24dcc3d..3de9940 100644 (file)
@@ -278,9 +278,6 @@ namespace System
     internal static partial class Number
     {
         [MethodImpl(MethodImplOptions.InternalCall)]
-        public static extern string FormatDecimal(decimal value, string format, NumberFormatInfo info);
-
-        [MethodImpl(MethodImplOptions.InternalCall)]
         public static extern string FormatDouble(double value, string format, NumberFormatInfo info);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
index 14ce9dd..8d82fdd 100644 (file)
@@ -744,7 +744,6 @@ FCFuncStart(gWaitHandleFuncs)
 FCFuncEnd()
 
 FCFuncStart(gNumberFuncs)
-    FCFuncElement("FormatDecimal", COMNumber::FormatDecimal)
     FCFuncElement("FormatDouble", COMNumber::FormatDouble)
     FCFuncElement("FormatSingle", COMNumber::FormatSingle)
     FCFuncElement("NumberBufferToDecimal", COMNumber::NumberBufferToDecimal)