[release/6.0-rc2] Generic Math Preview - Use correct NumberStyle when parsing double...
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Tue, 21 Sep 2021 00:40:41 +0000 (18:40 -0600)
committerGitHub <noreply@github.com>
Tue, 21 Sep 2021 00:40:41 +0000 (18:40 -0600)
* User correct NumberStyle when parsing double and float

IParseable<float> and IParseable<double> would fail when parsing
a decimal point number due to incorrect NumberStyles parameter.

E.g. IParseable<double>.TryParse would fail as opposed to
IParseable<double>.Parse because the former explicitly states to use
NumberStyles.Integer

* Adding tests covering parse for float/double/half

* Delete MSBuild_pid-37116_6904092b1c9a481da960c9e28eba2a27.failure.txt

Co-authored-by: Richard Sefr <richard.sefr@solarwinds.com>
Co-authored-by: Tanner Gooding <tagoo@outlook.com>
src/libraries/System.Private.CoreLib/src/System/Double.cs
src/libraries/System.Private.CoreLib/src/System/Half.cs
src/libraries/System.Private.CoreLib/src/System/Single.cs
src/libraries/System.Runtime.Experimental/tests/System.Runtime.Experimental.Tests.csproj
src/libraries/System.Runtime/tests/System.Runtime.Tests.csproj
src/libraries/System.Runtime/tests/System/DoubleTests.GenericMath.cs [new file with mode: 0644]
src/libraries/System.Runtime/tests/System/GenericMathHelpers.cs
src/libraries/System.Runtime/tests/System/HalfTests.GenericMath.cs [new file with mode: 0644]
src/libraries/System.Runtime/tests/System/SingleTests.GenericMath.cs [new file with mode: 0644]

index 6fde540..f087481 100644 (file)
@@ -1241,7 +1241,7 @@ namespace System
 
         [RequiresPreviewFeatures]
         static bool IParseable<double>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out double result)
-            => TryParse(s, NumberStyles.Integer, provider, out result);
+            => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
 
         //
         // ISignedNumber
@@ -1256,11 +1256,11 @@ namespace System
 
         [RequiresPreviewFeatures]
         static double ISpanParseable<double>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
-            => Parse(s, NumberStyles.Integer, provider);
+            => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider);
 
         [RequiresPreviewFeatures]
         static bool ISpanParseable<double>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out double result)
-            => TryParse(s, NumberStyles.Integer, provider, out result);
+            => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
 
         //
         // ISubtractionOperators
index c8791e1..ccee67b 100644 (file)
@@ -241,7 +241,7 @@ namespace System
         public static Half Parse(string s)
         {
             if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
-            return Number.ParseHalf(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo);
+            return Number.ParseHalf(s, DefaultParseStyle, NumberFormatInfo.CurrentInfo);
         }
 
         /// <summary>
@@ -266,7 +266,7 @@ namespace System
         public static Half Parse(string s, IFormatProvider? provider)
         {
             if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
-            return Number.ParseHalf(s, NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.GetInstance(provider));
+            return Number.ParseHalf(s, DefaultParseStyle, NumberFormatInfo.GetInstance(provider));
         }
 
         /// <summary>
@@ -1554,7 +1554,7 @@ namespace System
 
         [RequiresPreviewFeatures]
         static bool IParseable<Half>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out Half result)
-            => TryParse(s, NumberStyles.Integer, provider, out result);
+            => TryParse(s, DefaultParseStyle, provider, out result);
 
         //
         // ISignedNumber
@@ -1569,11 +1569,11 @@ namespace System
 
         [RequiresPreviewFeatures]
         static Half ISpanParseable<Half>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
-            => Parse(s, NumberStyles.Integer, provider);
+            => Parse(s, DefaultParseStyle, provider);
 
         [RequiresPreviewFeatures]
         static bool ISpanParseable<Half>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Half result)
-            => TryParse(s, NumberStyles.Integer, provider, out result);
+            => TryParse(s, DefaultParseStyle, provider, out result);
 
         //
         // ISubtractionOperators
index 26ea34a..456209b 100644 (file)
@@ -1233,7 +1233,7 @@ namespace System
 
         [RequiresPreviewFeatures]
         static bool IParseable<float>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out float result)
-            => TryParse(s, NumberStyles.Integer, provider, out result);
+            => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
 
         //
         // ISignedNumber
@@ -1248,11 +1248,11 @@ namespace System
 
         [RequiresPreviewFeatures]
         static float ISpanParseable<float>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider)
-            => Parse(s, NumberStyles.Integer, provider);
+            => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider);
 
         [RequiresPreviewFeatures]
         static bool ISpanParseable<float>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out float result)
-            => TryParse(s, NumberStyles.Integer, provider, out result);
+            => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
 
         //
         // ISubtractionOperators
index 0001351..d8ed841 100644 (file)
   <ItemGroup>
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\ByteTests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\CharTests.cs" />
+    <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\DoubleTests.cs" />
+    <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\HalfTests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\Int16Tests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\Int32Tests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\Int64Tests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\IntPtrTests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\SByteTests.cs" />
+    <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\SingleTests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\UInt16Tests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\UInt32Tests.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\UInt64Tests.cs" />
   <ItemGroup Condition="'$(FeatureGenericMath)' == 'true'">
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\ByteTests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\CharTests.GenericMath.cs" />
+    <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\DoubleTests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\GenericMathHelpers.cs" />
+    <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\HalfTests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\Int16Tests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\Int32Tests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\Int64Tests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\IntPtrTests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\SByteTests.GenericMath.cs" />
+    <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\SingleTests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\UInt16Tests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\UInt32Tests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\UInt64Tests.GenericMath.cs" />
     <Compile Include="$(LibrariesProjectRoot)System.Runtime\tests\System\UIntPtrTests.GenericMath.cs" />
   </ItemGroup>
   <ItemGroup>
+    <PackageReference Include="System.Runtime.Numerics.TestData" Version="$(SystemRuntimeNumericsTestDataVersion)" GeneratePathProperty="true" />
     <!-- it's a reference assembly, but the project system doesn't know that - include it during compilation, but don't publish it -->
     <ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.Experimental\ref\System.Runtime.Experimental.csproj" IncludeAssets="compile" Private="false" />
     <ProjectReference Include="$(CommonTestPath)TestUtilities.Unicode\TestUtilities.Unicode.csproj" />
index 6d888e3..913da87 100644 (file)
     <DefineConstants Condition="'$(FeatureGenericMath)' == 'true'">$(DefineConstants);FEATURE_GENERIC_MATH</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
-    <Compile Include="$(CommonTestPath)System\EnumTypes.cs"
-             Link="Common\System\EnumTypes.cs" />
-    <Compile Include="$(CommonTestPath)System\MockType.cs"
-             Link="Common\System\MockType.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\CollectionAsserts.cs"
-             Link="Common\System\Collections\CollectionAsserts.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\ICollection.Generic.Tests.cs"
-             Link="Common\System\Collections\ICollection.Generic.Tests.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\IEnumerable.Generic.Tests.cs"
-             Link="Common\System\Collections\IEnumerable.Generic.Tests.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\IList.Generic.Tests.cs"
-             Link="Common\System\Collections\IList.Generic.Tests.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\TestBase.Generic.cs"
-             Link="Common\System\Collections\TestBase.Generic.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\TestBase.NonGeneric.cs"
-             Link="Common\System\Collections\TestBase.NonGeneric.cs" />
-    <Compile Include="$(CommonTestPath)Tests\System\StringTests.cs"
-             Link="Common\System\StringTests.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\IDictionary.NonGeneric.Tests.cs"
-             Link="Common\System\Collections\IDictionary.NonGeneric.Tests.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\IList.NonGeneric.Tests.cs"
-             Link="Common\System\Collections\IList.NonGeneric.Tests.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\ICollection.NonGeneric.Tests.cs"
-             Link="Common\System\Collections\ICollection.NonGeneric.Tests.cs" />
-    <Compile Include="$(CommonTestPath)System\Collections\IEnumerable.NonGeneric.Tests.cs"
-             Link="Common\System\Collections\IEnumerable.NonGeneric.Tests.cs" />
+    <Compile Include="$(CommonTestPath)System\EnumTypes.cs" Link="Common\System\EnumTypes.cs" />
+    <Compile Include="$(CommonTestPath)System\MockType.cs" Link="Common\System\MockType.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\CollectionAsserts.cs" Link="Common\System\Collections\CollectionAsserts.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\ICollection.Generic.Tests.cs" Link="Common\System\Collections\ICollection.Generic.Tests.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\IEnumerable.Generic.Tests.cs" Link="Common\System\Collections\IEnumerable.Generic.Tests.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\IList.Generic.Tests.cs" Link="Common\System\Collections\IList.Generic.Tests.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\TestBase.Generic.cs" Link="Common\System\Collections\TestBase.Generic.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\TestBase.NonGeneric.cs" Link="Common\System\Collections\TestBase.NonGeneric.cs" />
+    <Compile Include="$(CommonTestPath)Tests\System\StringTests.cs" Link="Common\System\StringTests.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\IDictionary.NonGeneric.Tests.cs" Link="Common\System\Collections\IDictionary.NonGeneric.Tests.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\IList.NonGeneric.Tests.cs" Link="Common\System\Collections\IList.NonGeneric.Tests.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\ICollection.NonGeneric.Tests.cs" Link="Common\System\Collections\ICollection.NonGeneric.Tests.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\IEnumerable.NonGeneric.Tests.cs" Link="Common\System\Collections\IEnumerable.NonGeneric.Tests.cs" />
     <Compile Include="Helpers.cs" />
     <Compile Include="Microsoft\Win32\SafeHandles\CriticalHandleZeroOrMinusOneIsInvalid.cs" />
     <Compile Include="Microsoft\Win32\SafeHandles\SafeHandleZeroOrMinusOneIsInvalid.cs" />
     <Compile Include="System\Type\TypePropertyTests.cs" />
     <Compile Include="System\Type\TypeTests.cs" />
     <Compile Include="System\Type\TypeTests.Get.cs" />
-    <Compile Include="$(CommonTestPath)System\RandomDataGenerator.cs"
-             Link="Common\System\RandomDataGenerator.cs" />
+    <Compile Include="$(CommonTestPath)System\RandomDataGenerator.cs" Link="Common\System\RandomDataGenerator.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true'">
     <Compile Include="System\ExitCodeTests.Unix.cs" />
   <ItemGroup Condition="'$(FeatureGenericMath)' == 'true'">
     <Compile Include="System\ByteTests.GenericMath.cs" />
     <Compile Include="System\CharTests.GenericMath.cs" />
+    <Compile Include="System\DoubleTests.GenericMath.cs" />
     <Compile Include="System\GenericMathHelpers.cs" />
+    <Compile Include="System\HalfTests.GenericMath.cs" />
     <Compile Include="System\Int16Tests.GenericMath.cs" />
     <Compile Include="System\Int32Tests.GenericMath.cs" />
     <Compile Include="System\Int64Tests.GenericMath.cs" />
     <Compile Include="System\IntPtrTests.GenericMath.cs" />
     <Compile Include="System\SByteTests.GenericMath.cs" />
+    <Compile Include="System\SingleTests.GenericMath.cs" />
     <Compile Include="System\UInt16Tests.GenericMath.cs" />
     <Compile Include="System\UInt32Tests.GenericMath.cs" />
     <Compile Include="System\UInt64Tests.GenericMath.cs" />
     <Compile Include="System\Text\Unicode\Utf8Tests.cs" />
     <Compile Include="System\Text\Unicode\Utf8UtilityTests.ValidateBytes.cs" />
     <Compile Include="System\ArgIteratorTests.cs" />
-    <Compile Include="$(CommonPath)..\tests\System\RealFormatterTestsBase.cs"
-             Link="System\RealFormatterTestsBase.cs" />
+    <Compile Include="$(CommonPath)..\tests\System\RealFormatterTestsBase.cs" Link="System\RealFormatterTestsBase.cs" />
     <Compile Include="System\RealFormatterTests.cs" />
-    <Compile Include="$(CommonPath)..\tests\System\RealParserTestsBase.cs"
-             Link="System\RealParserTestsBase.cs" />
+    <Compile Include="$(CommonPath)..\tests\System\RealParserTestsBase.cs" Link="System\RealParserTestsBase.cs" />
     <Compile Include="System\RealParserTests.cs" />
 
     <TrimmerRootDescriptor Include="$(ILLinkDescriptorsPath)ILLink.Descriptors.Castle.xml" />
     <TrimmerRootDescriptor Include="$(MSBuildThisFileDirectory)ILLink.Descriptors.xml" />
   </ItemGroup>
   <ItemGroup>
-    <Compile Include="$(CommonTestPath)System\Collections\IEnumerable.Generic.Serialization.Tests.cs"
-             Link="Common\System\Collections\IEnumerable.Generic.Serialization.Tests.cs" />
+    <Compile Include="$(CommonTestPath)System\Collections\IEnumerable.Generic.Serialization.Tests.cs" Link="Common\System\Collections\IEnumerable.Generic.Serialization.Tests.cs" />
     <EmbeddedResource Include="System\Reflection\EmbeddedImage.png">
       <LogicalName>System.Reflection.Tests.EmbeddedImage.png</LogicalName>
     </EmbeddedResource>
     <EmbeddedResource Include="System\Reflection\EmbeddedTextFile.txt">
       <LogicalName>System.Reflection.Tests.EmbeddedTextFile.txt</LogicalName>
     </EmbeddedResource>
-    <Compile Include="$(CommonTestPath)System\Runtime\Serialization\Formatters\BinaryFormatterHelpers.cs"
-             Link="Common\System\Runtime\Serialization\Formatters\BinaryFormatterHelpers.cs" />
+    <Compile Include="$(CommonTestPath)System\Runtime\Serialization\Formatters\BinaryFormatterHelpers.cs" Link="Common\System\Runtime\Serialization\Formatters\BinaryFormatterHelpers.cs" />
   </ItemGroup>
   <ItemGroup>
     <PackageReference Include="Moq" Version="$(MoqVersion)" />
-    <PackageReference Include="System.Runtime.Numerics.TestData" Version="$(SystemRuntimeNumericsTestDataVersion)" GeneratePathProperty="true"/>
+    <PackageReference Include="System.Runtime.Numerics.TestData" Version="$(SystemRuntimeNumericsTestDataVersion)" GeneratePathProperty="true" />
     <ProjectReference Include="TestLoadAssembly\TestLoadAssembly.csproj" />
     <ProjectReference Include="TestCollectibleAssembly\TestCollectibleAssembly.csproj" />
     <ProjectReference Include="TestModule\System.Reflection.TestModule.ilproj" />
diff --git a/src/libraries/System.Runtime/tests/System/DoubleTests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/DoubleTests.GenericMath.cs
new file mode 100644 (file)
index 0000000..60a2005
--- /dev/null
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Globalization;
+using System.Runtime.Versioning;
+using Xunit;
+
+namespace System.Tests
+{
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))]
+    [RequiresPreviewFeaturesAttribute]
+    public class DoubleTests_GenericMath
+    {
+        [Theory]
+        [MemberData(nameof(DoubleTests.Parse_Valid_TestData), MemberType = typeof(DoubleTests))]
+        public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, double expected)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            double result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None)
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.True(NumberHelper<double>.TryParse(value, null, out result));
+                    Assert.Equal(expected, result);
+
+                    Assert.Equal(expected, NumberHelper<double>.Parse(value, null));
+                }
+
+                Assert.Equal(expected, NumberHelper<double>.Parse(value, provider));
+            }
+
+            // Use Parse(string, NumberStyles, IFormatProvider)
+            Assert.True(NumberHelper<double>.TryParse(value, style, provider, out result));
+            Assert.Equal(expected, result);
+
+            Assert.Equal(expected, NumberHelper<double>.Parse(value, style, provider));
+
+            if (isDefaultProvider)
+            {
+                // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider)
+                Assert.True(NumberHelper<double>.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result));
+                Assert.Equal(expected, result);
+
+                Assert.Equal(expected, NumberHelper<double>.Parse(value, style, null));
+                Assert.Equal(expected, NumberHelper<double>.Parse(value, style, NumberFormatInfo.CurrentInfo));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(DoubleTests.Parse_Invalid_TestData), MemberType = typeof(DoubleTests))]
+        public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            double result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite))
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.False(NumberHelper<double>.TryParse(value, null, out result));
+                    Assert.Equal(default(double), result);
+
+                    Assert.Throws(exceptionType, () => NumberHelper<double>.Parse(value, null));
+                }
+
+                Assert.Throws(exceptionType, () => NumberHelper<double>.Parse(value, provider));
+            }
+
+            // Use Parse(string, NumberStyles, IFormatProvider)
+            Assert.False(NumberHelper<double>.TryParse(value, style, provider, out result));
+            Assert.Equal(default(double), result);
+
+            Assert.Throws(exceptionType, () => NumberHelper<double>.Parse(value, style, provider));
+
+            if (isDefaultProvider)
+            {
+                // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider)
+                Assert.False(NumberHelper<double>.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result));
+                Assert.Equal(default(double), result);
+
+                Assert.Throws(exceptionType, () => NumberHelper<double>.Parse(value, style, null));
+                Assert.Throws(exceptionType, () => NumberHelper<double>.Parse(value, style, NumberFormatInfo.CurrentInfo));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(DoubleTests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(DoubleTests))]
+        public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, double expected)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            double result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None)
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.True(NumberHelper<double>.TryParse(value.AsSpan(offset, count), null, out result));
+                    Assert.Equal(expected, result);
+
+                    Assert.Equal(expected, NumberHelper<double>.Parse(value.AsSpan(offset, count), null));
+                }
+
+                Assert.Equal(expected, NumberHelper<double>.Parse(value.AsSpan(offset, count), provider: provider));
+            }
+
+            Assert.Equal(expected, NumberHelper<double>.Parse(value.AsSpan(offset, count), style, provider));
+
+            Assert.True(NumberHelper<double>.TryParse(value.AsSpan(offset, count), style, provider, out result));
+            Assert.Equal(expected, result);
+        }
+
+        [Theory]
+        [MemberData(nameof(DoubleTests.Parse_Invalid_TestData), MemberType = typeof(DoubleTests))]
+        public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType)
+        {
+            if (value != null)
+            {
+                Assert.Throws(exceptionType, () => NumberHelper<double>.Parse(value.AsSpan(), style, provider));
+
+                Assert.False(NumberHelper<double>.TryParse(value.AsSpan(), style, provider, out double result));
+                Assert.Equal(0, result);
+            }
+        }
+    }
+}
index 2895dd8..e1d4659 100644 (file)
@@ -157,8 +157,12 @@ namespace System.Tests
 
         public static TSelf Min(TSelf x, TSelf y) => TSelf.Min(x, y);
 
+        public static TSelf Parse(string s, IFormatProvider provider) => TSelf.Parse(s, provider);
+
         public static TSelf Parse(string s, NumberStyles style, IFormatProvider provider) => TSelf.Parse(s, style, provider);
 
+        public static TSelf Parse(ReadOnlySpan<char> s, IFormatProvider provider) => TSelf.Parse(s, provider);
+
         public static TSelf Parse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider) => TSelf.Parse(s, style, provider);
 
         public static TSelf Sign(TSelf value) => TSelf.Sign(value);
@@ -166,8 +170,12 @@ namespace System.Tests
         public static bool TryCreate<TOther>(TOther value, out TSelf result)
             where TOther : INumber<TOther> => TSelf.TryCreate<TOther>(value, out result);
 
+        public static bool TryParse(string s, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, provider, out result);
+
         public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, style, provider, out result);
 
+        public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, provider, out result);
+
         public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, style, provider, out result);
     }
 
diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/HalfTests.GenericMath.cs
new file mode 100644 (file)
index 0000000..c9fe77c
--- /dev/null
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Globalization;
+using System.Runtime.Versioning;
+using Xunit;
+
+namespace System.Tests
+{
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))]
+    [RequiresPreviewFeaturesAttribute]
+    public class HalfTests_GenericMath
+    {
+        [Theory]
+        [MemberData(nameof(HalfTests.Parse_Valid_TestData), MemberType = typeof(HalfTests))]
+        public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, Half expected)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            Half result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None)
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.True(NumberHelper<Half>.TryParse(value, null, out result));
+                    Assert.Equal(expected, result);
+
+                    Assert.Equal(expected, NumberHelper<Half>.Parse(value, null));
+                }
+
+                Assert.Equal(expected, NumberHelper<Half>.Parse(value, provider));
+            }
+
+            // Use Parse(string, NumberStyles, IFormatProvider)
+            Assert.True(NumberHelper<Half>.TryParse(value, style, provider, out result));
+            Assert.Equal(expected, result);
+
+            Assert.Equal(expected, NumberHelper<Half>.Parse(value, style, provider));
+
+            if (isDefaultProvider)
+            {
+                // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider)
+                Assert.True(NumberHelper<Half>.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result));
+                Assert.Equal(expected, result);
+
+                Assert.Equal(expected, NumberHelper<Half>.Parse(value, style, null));
+                Assert.Equal(expected, NumberHelper<Half>.Parse(value, style, NumberFormatInfo.CurrentInfo));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(HalfTests.Parse_Invalid_TestData), MemberType = typeof(HalfTests))]
+        public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            Half result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite))
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.False(NumberHelper<Half>.TryParse(value, null, out result));
+                    Assert.Equal(default(Half), result);
+
+                    Assert.Throws(exceptionType, () => NumberHelper<Half>.Parse(value, null));
+                }
+
+                Assert.Throws(exceptionType, () => NumberHelper<Half>.Parse(value, provider));
+            }
+
+            // Use Parse(string, NumberStyles, IFormatProvider)
+            Assert.False(NumberHelper<Half>.TryParse(value, style, provider, out result));
+            Assert.Equal(default(Half), result);
+
+            Assert.Throws(exceptionType, () => NumberHelper<Half>.Parse(value, style, provider));
+
+            if (isDefaultProvider)
+            {
+                // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider)
+                Assert.False(NumberHelper<Half>.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result));
+                Assert.Equal(default(Half), result);
+
+                Assert.Throws(exceptionType, () => NumberHelper<Half>.Parse(value, style, null));
+                Assert.Throws(exceptionType, () => NumberHelper<Half>.Parse(value, style, NumberFormatInfo.CurrentInfo));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(HalfTests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(HalfTests))]
+        public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, Half expected)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            Half result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None)
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.True(NumberHelper<Half>.TryParse(value.AsSpan(offset, count), null, out result));
+                    Assert.Equal(expected, result);
+
+                    Assert.Equal(expected, NumberHelper<Half>.Parse(value.AsSpan(offset, count), null));
+                }
+
+                Assert.Equal(expected, NumberHelper<Half>.Parse(value.AsSpan(offset, count), provider: provider));
+            }
+
+            Assert.Equal(expected, NumberHelper<Half>.Parse(value.AsSpan(offset, count), style, provider));
+
+            Assert.True(NumberHelper<Half>.TryParse(value.AsSpan(offset, count), style, provider, out result));
+            Assert.Equal(expected, result);
+        }
+
+        [Theory]
+        [MemberData(nameof(HalfTests.Parse_Invalid_TestData), MemberType = typeof(HalfTests))]
+        public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType)
+        {
+            if (value != null)
+            {
+                Assert.Throws(exceptionType, () => NumberHelper<Half>.Parse(value.AsSpan(), style, provider));
+
+                Assert.False(NumberHelper<Half>.TryParse(value.AsSpan(), style, provider, out Half result));
+                Assert.Equal((Half)0, result);
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Runtime/tests/System/SingleTests.GenericMath.cs b/src/libraries/System.Runtime/tests/System/SingleTests.GenericMath.cs
new file mode 100644 (file)
index 0000000..8e7c3e8
--- /dev/null
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Globalization;
+using System.Runtime.Versioning;
+using Xunit;
+
+namespace System.Tests
+{
+    [ActiveIssue("https://github.com/dotnet/runtime/issues/54910", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))]
+    [RequiresPreviewFeaturesAttribute]
+    public class SingleTests_GenericMath
+    {
+        [Theory]
+        [MemberData(nameof(SingleTests.Parse_Valid_TestData), MemberType = typeof(SingleTests))]
+        public static void ParseValidStringTest(string value, NumberStyles style, IFormatProvider provider, float expected)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            float result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None)
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.True(NumberHelper<float>.TryParse(value, null, out result));
+                    Assert.Equal(expected, result);
+
+                    Assert.Equal(expected, NumberHelper<float>.Parse(value, null));
+                }
+
+                Assert.Equal(expected, NumberHelper<float>.Parse(value, provider));
+            }
+
+            // Use Parse(string, NumberStyles, IFormatProvider)
+            Assert.True(NumberHelper<float>.TryParse(value, style, provider, out result));
+            Assert.Equal(expected, result);
+
+            Assert.Equal(expected, NumberHelper<float>.Parse(value, style, provider));
+
+            if (isDefaultProvider)
+            {
+                // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider)
+                Assert.True(NumberHelper<float>.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result));
+                Assert.Equal(expected, result);
+
+                Assert.Equal(expected, NumberHelper<float>.Parse(value, style, null));
+                Assert.Equal(expected, NumberHelper<float>.Parse(value, style, NumberFormatInfo.CurrentInfo));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(SingleTests.Parse_Invalid_TestData), MemberType = typeof(SingleTests))]
+        public static void ParseInvalidStringTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            float result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite))
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.False(NumberHelper<float>.TryParse(value, null, out result));
+                    Assert.Equal(default(float), result);
+
+                    Assert.Throws(exceptionType, () => NumberHelper<float>.Parse(value, null));
+                }
+
+                Assert.Throws(exceptionType, () => NumberHelper<float>.Parse(value, provider));
+            }
+
+            // Use Parse(string, NumberStyles, IFormatProvider)
+            Assert.False(NumberHelper<float>.TryParse(value, style, provider, out result));
+            Assert.Equal(default(float), result);
+
+            Assert.Throws(exceptionType, () => NumberHelper<float>.Parse(value, style, provider));
+
+            if (isDefaultProvider)
+            {
+                // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider)
+                Assert.False(NumberHelper<float>.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result));
+                Assert.Equal(default(float), result);
+
+                Assert.Throws(exceptionType, () => NumberHelper<float>.Parse(value, style, null));
+                Assert.Throws(exceptionType, () => NumberHelper<float>.Parse(value, style, NumberFormatInfo.CurrentInfo));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(SingleTests.Parse_ValidWithOffsetCount_TestData), MemberType = typeof(SingleTests))]
+        public static void ParseValidSpanTest(string value, int offset, int count, NumberStyles style, IFormatProvider provider, float expected)
+        {
+            bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo;
+            float result;
+            if ((style & ~(NumberStyles.Float | NumberStyles.AllowThousands)) == 0 && style != NumberStyles.None)
+            {
+                // Use Parse(string) or Parse(string, IFormatProvider)
+                if (isDefaultProvider)
+                {
+                    Assert.True(NumberHelper<float>.TryParse(value.AsSpan(offset, count), null, out result));
+                    Assert.Equal(expected, result);
+
+                    Assert.Equal(expected, NumberHelper<float>.Parse(value.AsSpan(offset, count), null));
+                }
+
+                Assert.Equal(expected, NumberHelper<float>.Parse(value.AsSpan(offset, count), provider: provider));
+            }
+
+            Assert.Equal(expected, NumberHelper<float>.Parse(value.AsSpan(offset, count), style, provider));
+
+            Assert.True(NumberHelper<float>.TryParse(value.AsSpan(offset, count), style, provider, out result));
+            Assert.Equal(expected, result);
+        }
+
+        [Theory]
+        [MemberData(nameof(SingleTests.Parse_Invalid_TestData), MemberType = typeof(SingleTests))]
+        public static void ParseInvalidSpanTest(string value, NumberStyles style, IFormatProvider provider, Type exceptionType)
+        {
+            if (value != null)
+            {
+                Assert.Throws(exceptionType, () => NumberHelper<float>.Parse(value.AsSpan(), style, provider));
+
+                Assert.False(NumberHelper<float>.TryParse(value.AsSpan(), style, provider, out float result));
+                Assert.Equal(0, result);
+            }
+        }
+    }
+}