diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs index b72dafed0..ea5f444c2 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs @@ -71,6 +71,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public const double Double_One = 1.0; public const float Float_Two = 2f; public const double Double_Two = 2.0; + public const float Float_Sixth = 1f / 6f; + public const double Double_Sixth = 1.0 / 6.0; + public const float Float_Tenth = 0.1f; + public const double Double_Tenth = 0.1; public const float Float_PI = (float)Math.PI; public const float Float_HalfOfPI = (float)Math.PI / 2f; @@ -152,4 +156,4 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public const double Double_Negated_BeforeE = -2.7182818284590446; public const double Double_Negated_AfterE = -2.7182818284590455; } -} \ No newline at end of file +} diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 334ca0e6a..a850c6362 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -22,6 +22,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.TypeSystem; @@ -1307,19 +1308,36 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return Math.Abs(num) < den && new int[] { 2, 3, 5 }.Any(x => den % x == 0); } + [MethodImpl(MethodImplOptions.NoInlining)] + static bool EqualDoubles(in double val1, in double val2) + { + // We use `in double` to pass the floats through memory, + // which ensures we won't get more than 64bits of precision + return val1 == val2; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static bool EqualFloats(in float val1, in float val2) + { + // We use `in float` to pass the floats through memory, + // which ensures we won't get more than 32bits of precision + return val1 == val2; + } + static bool IsEqual(long num, long den, object constantValue, bool isDouble) { if (isDouble) { - return (double)constantValue == num / (double)den; + return EqualDoubles((double)constantValue, num / (double)den); } else { - return (float)constantValue == num / (float)den; + return EqualFloats((float)constantValue, num / (float)den); } } - const int MAX_DENOMINATOR = 1000; + const int MAX_DENOMINATOR_DOUBLE = 1000; + const int MAX_DENOMINATOR_FLOAT = 360; Expression ConvertFloatingPointLiteral(IType type, object constantValue) { @@ -1353,6 +1371,23 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax bool useFraction = (str.Length - (str.StartsWith("-", StringComparison.OrdinalIgnoreCase) ? 2 : 1) > 5); + if (useFraction && expr == null) + { + Debug.Assert(200 < MAX_DENOMINATOR_FLOAT); + // For fractions not involving PI, use a smaller MAX_DENOMINATOR + // to avoid coincidences such as (1f/MathF.PI) == (113f/355f) + (long num, long den) = isDouble + ? FractionApprox((double)constantValue, MAX_DENOMINATOR_DOUBLE) + : FractionApprox((float)constantValue, 200); + + if (IsValidFraction(num, den) && IsEqual(num, den, constantValue, isDouble) && Math.Abs(den) != 1) + { + var left = MakeConstant(type, num); + var right = MakeConstant(type, den); + expr = new BinaryOperatorExpression(left, BinaryOperatorType.Divide, right); + } + } + if (useFraction && expr == null && UseSpecialConstants) { IType mathType; @@ -1374,21 +1409,6 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax ?? TryExtractExpression(mathType, type, constantValue, "E", isDouble); } - if (useFraction && expr == null) - { - (long num, long den) = isDouble - ? FractionApprox((double)constantValue, MAX_DENOMINATOR) - : FractionApprox((float)constantValue, MAX_DENOMINATOR); - - if (IsValidFraction(num, den) && IsEqual(num, den, constantValue, isDouble) && Math.Abs(num) != 1 && Math.Abs(den) != 1) - { - var left = MakeConstant(type, num); - var right = MakeConstant(type, den); - return new BinaryOperatorExpression(left, BinaryOperatorType.Divide, right).WithoutILInstruction() - .WithRR(new ConstantResolveResult(type, constantValue)); - } - } - if (expr == null) expr = new PrimitiveExpression(constantValue); @@ -1458,14 +1478,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { double field = memberName == "PI" ? Math.PI : Math.E; double approxValue = field * n / d; - if (approxValue == (double)literalValue) + if (EqualDoubles(approxValue, (double)literalValue)) return expr; } else { float field = memberName == "PI" ? MathF_PI : MathF_E; float approxValue = field * n / d; - if (approxValue == (float)literalValue) + if (EqualFloats(approxValue, (float)literalValue)) return expr; } @@ -1488,14 +1508,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { double field = memberName == "PI" ? Math.PI : Math.E; double approxValue = (double)n / ((double)d * field); - if (approxValue == (double)literalValue) + if (EqualDoubles(approxValue, (double)literalValue)) return expr; } else { float field = memberName == "PI" ? MathF_PI : MathF_E; float approxValue = (float)n / ((float)d * field); - if (approxValue == (float)literalValue) + if (EqualFloats(approxValue, (float)literalValue)) return expr; } @@ -1503,17 +1523,19 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } (long num, long den) = isDouble - ? FractionApprox((double)literalValue / (memberName == "PI" ? Math.PI : Math.E), MAX_DENOMINATOR) - : FractionApprox((float)literalValue / (memberName == "PI" ? MathF_PI : MathF_E), MAX_DENOMINATOR); + ? FractionApprox((double)literalValue / (memberName == "PI" ? Math.PI : Math.E), MAX_DENOMINATOR_DOUBLE) + : FractionApprox((float)literalValue / (memberName == "PI" ? MathF_PI : MathF_E), MAX_DENOMINATOR_FLOAT); if (IsValidFraction(num, den)) { - return ExtractExpression(num, den); + var expr = ExtractExpression(num, den); + if (expr != null) + return expr; } (num, den) = isDouble - ? FractionApprox((double)literalValue * (memberName == "PI" ? Math.PI : Math.E), MAX_DENOMINATOR) - : FractionApprox((float)literalValue * (memberName == "PI" ? MathF_PI : MathF_E), MAX_DENOMINATOR); + ? FractionApprox((double)literalValue * (memberName == "PI" ? Math.PI : Math.E), MAX_DENOMINATOR_DOUBLE) + : FractionApprox((float)literalValue * (memberName == "PI" ? MathF_PI : MathF_E), MAX_DENOMINATOR_FLOAT); if (IsValidFraction(num, den)) {