Use the original precision (prior-dotnet-core-3) for double/fload-to-string conversion (#9893)

.NET Core changes to return "shortest roundtrippable string" by default for the ToString() method of double and float types. This results in ToString() for double/float values sometimes return a string in 17-digit/9-digit precision format. This PR updated the double/float-to-string conversion in PowerShell to continue using the old precision specifier before the change in .NET Core 3.0.
This commit is contained in:
Dongbo Wang
2019-06-19 08:56:43 +05:00
committed by Ilya
parent 0673a481bb
commit c684902fba
5 changed files with 92 additions and 41 deletions
@@ -305,6 +305,8 @@ namespace System.Management.Automation
internal delegate void MemberSetValueError(SetValueException e);
internal const string OrderedAttribute = "ordered";
internal const string DoublePrecision = "G15";
internal const string SinglePrecision = "G7";
internal static void CreateMemberNotFoundError(PSObject pso, DictionaryEntry property, Type resultType)
{
@@ -3311,6 +3313,17 @@ namespace System.Management.Automation
try
{
// Ignore formatProvider here, the conversion should be culture invariant.
var numberFormat = CultureInfo.InvariantCulture.NumberFormat;
if (valueToConvert is double dbl)
{
return dbl.ToString(DoublePrecision, numberFormat);
}
if (valueToConvert is float sgl)
{
return sgl.ToString(SinglePrecision, numberFormat);
}
return (string)Convert.ChangeType(valueToConvert, resultType, CultureInfo.InvariantCulture.NumberFormat);
}
catch (Exception e)
@@ -1269,6 +1269,42 @@ namespace System.Management.Automation
/// </exception>
internal static string ToString(ExecutionContext context, object obj, string separator, string format, IFormatProvider formatProvider, bool recurse, bool unravelEnumeratorOnRecurse)
{
bool TryFastTrackPrimitiveTypes(object value, out string str)
{
switch (Convert.GetTypeCode(value))
{
case TypeCode.String:
str = (string)value;
break;
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.UInt16:
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.Int64:
case TypeCode.UInt64:
case TypeCode.DateTime:
case TypeCode.Decimal:
var formattable = (IFormattable)value;
str = formattable.ToString(format, formatProvider);
break;
case TypeCode.Double:
var dbl = (double)value;
str = dbl.ToString(format ?? LanguagePrimitives.DoublePrecision, formatProvider);
break;
case TypeCode.Single:
var sgl = (float)value;
str = sgl.ToString(format ?? LanguagePrimitives.SinglePrecision, formatProvider);
break;
default:
str = null;
return false;
}
return true;
}
PSObject mshObj = obj as PSObject;
#region plain object
@@ -1279,35 +1315,9 @@ namespace System.Management.Automation
return string.Empty;
}
// Fast-track the primitive types...
Type objType = obj.GetType();
TypeCode code = objType.GetTypeCode();
switch (code)
if (TryFastTrackPrimitiveTypes(obj, out string objString))
{
case TypeCode.String:
return (string)obj;
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.UInt16:
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.Int64:
case TypeCode.UInt64:
return obj.ToString();
case TypeCode.DateTime:
DateTime dt = (DateTime)obj;
return dt.ToString(formatProvider);
case TypeCode.Decimal:
Decimal dec = (Decimal)obj;
return dec.ToString(formatProvider);
case TypeCode.Double:
double dbl = (double)obj;
return dbl.ToString(formatProvider);
case TypeCode.Single:
float sgl = (float)obj;
return sgl.ToString(formatProvider);
return objString;
}
#region recurse
@@ -1482,6 +1492,12 @@ namespace System.Management.Automation
// Since we don't have a brokered ToString and the enumerations were not necessary or failed
// we try the BaseObject's ToString
object baseObject = mshObj._immediateBaseObject;
if (TryFastTrackPrimitiveTypes(baseObject, out string baseObjString))
{
return baseObjString;
}
IFormattable msjObjFormattable = baseObject as IFormattable;
try
{
@@ -2,28 +2,24 @@
# Licensed under the MIT License.
Describe "DotNetAPI" -Tags "CI" {
$posh_E = 2.718281828459045
$posh_pi = 3.14159265358979
It "Should be able to use static .NET classes and get a constant" {
[System.Math]::E | Should -Match $posh_E.ToString()
[System.Math]::PI | Should -Match $posh_pi.ToString()
[System.Math]::E | Should -Be 2.718281828459045
[System.Math]::PI | Should -Be 3.141592653589793
}
It "Should be able to invoke a method" {
[System.Environment]::GetEnvironmentVariable("PATH") | Should -Be $env:PATH
[System.Environment]::GetEnvironmentVariable("PATH") | Should -Be $env:PATH
}
It "Should not require 'system' in front of static classes" {
[Environment]::CommandLine | Should -Be ([System.Environment]::CommandLine)
[Math]::E | Should -Be ([System.Math]::E)
[Environment]::CommandLine | Should -Be ([System.Environment]::CommandLine)
[Math]::E | Should -Be ([System.Math]::E)
}
It "Should be able to create a new instance of a .Net object" {
[System.Guid]$guidVal = [System.Guid]::NewGuid()
$guidVal | Should -BeOfType Guid
[System.Guid]$guidVal = [System.Guid]::NewGuid()
$guidVal | Should -BeOfType Guid
}
It "Should access types in System.Console" {
@@ -1,5 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Describe 'conversion syntax' -Tags "CI" {
# these test suite covers ([<type>]<expression>).<method>() syntax.
# it mixes two purposes: casting and super-class method calls.
@@ -518,3 +519,28 @@ Describe 'method conversion' -Tags 'CI' {
$Result | Should -BeNullOrEmpty
}
}
Describe 'float/double precision when converting to string' -Tags "CI" {
It "<SourceType>-to-[string] conversion in PowerShell should use the precision specifier <Format>" -TestCases @(
@{ SourceType = [double]; Format = "G15"; ValueScript = { 1.1 * 3 }; StringConversionResult = "3.3"; ToStringResult = "3.3000000000000003" }
@{ SourceType = [double]; Format = "G15"; ValueScript = { 1.1 * 6 }; StringConversionResult = "6.6"; ToStringResult = "6.6000000000000005" }
@{ SourceType = [double]; Format = "G15"; ValueScript = { [System.Math]::E }; StringConversionResult = [System.Math]::E.ToString("G15"); ToStringResult = [System.Math]::E.ToString() }
@{ SourceType = [double]; Format = "G15"; ValueScript = { [System.Math]::PI }; StringConversionResult = [System.Math]::PI.ToString("G15"); ToStringResult = [System.Math]::PI.ToString() }
@{ SourceType = [float]; Format = "G7"; ValueScript = { [float]$f = 1.1; ($f * 3).ToSingle([cultureinfo]::InvariantCulture) }; StringConversionResult = "3.3"; ToStringResult = "3.3000002" }
@{ SourceType = [float]; Format = "G7"; ValueScript = { [float]$f = 1.1; ($f * 6).ToSingle([cultureinfo]::InvariantCulture) }; StringConversionResult = "6.6"; ToStringResult = "6.6000004" }
@{ SourceType = [float]; Format = "G7"; ValueScript = { [float]::MaxValue }; StringConversionResult = [float]::MaxValue.ToString("G7"); ToStringResult = [float]::MaxValue.ToString() }
@{ SourceType = [float]; Format = "G7"; ValueScript = { [float]::MinValue }; StringConversionResult = [float]::MinValue.ToString("G7"); ToStringResult = [float]::MinValue.ToString() }
) {
param($SourceType, $ValueScript, $StringConversionResult, $ToStringResult)
$value = & $ValueScript
$value | Should -BeOfType $SourceType
$value.ToString() | Should -BeExactly $ToStringResult
$value -as [string] | Should -BeExactly $StringConversionResult
[string]$value | Should -BeExactly $StringConversionResult
[System.Management.Automation.LanguagePrimitives]::ConvertTo($value, [string]) | Should -BeExactly $StringConversionResult
"$value" | Should -BeExactly $StringConversionResult
$value | Out-String | ForEach-Object -MemberName Trim | Should -BeExactly $StringConversionResult
}
}
@@ -650,8 +650,8 @@ foo``u{2195}abc
@{ Script = "-6.5"; ExpectedValue = "-6.5"; ExpectedType = [double] }
@{ Script = "9.12"; ExpectedValue = "9.12"; ExpectedType = [double] }
@{ Script = ".01"; ExpectedValue = "0.01"; ExpectedType = [double] }
@{ Script = $([single]::MinValue); ExpectedValue = $([float]::MinValue).ToString(); ExpectedType = [double] }
@{ Script = $([float]::MaxValue); ExpectedValue = $([float]::MaxValue).ToString(); ExpectedType = [double] }
@{ Script = $([single]::MinValue); ExpectedValue = $([float]::MinValue).ToString("G7"); ExpectedType = [double] }
@{ Script = $([float]::MaxValue); ExpectedValue = $([float]::MaxValue).ToString("G7"); ExpectedType = [double] }
#Exponential
@{ Script = "0e0"; ExpectedValue = "0"; ExpectedType = [double] }
@{ Script = "0e1"; ExpectedValue = "0"; ExpectedType = [double] }