From 59361d9de2c4cc52236fbecea98878e211aee5c1 Mon Sep 17 00:00:00 2001 From: SychicBoy Date: Sun, 16 Aug 2026 22:07:22 +0330 Subject: [PATCH] Prefer common machine-scale floating-point fractions --- .../TestCases/Pretty/WellKnownConstants.cs | 11 ++ .../CSharp/Syntax/TypeSystemAstBuilder.cs | 129 +++++++++++++++++- 2 files changed, 136 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs index 5a1733421..50c368714 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs @@ -84,6 +84,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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_Third = 1f / 3f; + public const float Float_PowerOfTwoFraction = 21f / 32f; + public const float Float_SmoothFraction = 2f / 15f; + public const float Float_UnitFraction = 1f / 85f; + public const float Float_ByteScale_225 = 225f / 255f; + public const float Float_ByteScale_200 = 200f / 255f; + public const float Float_ByteScale_150 = 150f / 255f; + public const float Float_NegativeByteScale = -200f / 255f; + public const double Double_ByteScale = 200.0 / 255.0; + public const float Float_KScale = 123f / 1024f; + public const float Float_MScale = 123f / 1048576f; #if ROSLYN2 && !NET40 public const float Float_PI = MathF.PI; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 231e0f41a..0a3e9eb14 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1516,6 +1516,103 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax const int MAX_DENOMINATOR_DOUBLE = 1000; const int MAX_DENOMINATOR_FLOAT = 360; + // Common machine-scale denominators: powers of two used for binary scaling, + // and 2^n-1 values used when normalizing integers (for example, byte colors / 255). + // Keep this as a targeted candidate set rather than increasing the generic denominator + // limit, which would reintroduce accidental fraction matches for ordinary floating-point values. + static readonly int[] preferredFractionDenominators = { + 127, 128, + 255, 256, + 1023, 1024, + 4095, 4096, + 8192, + 16384, + 32767, 32768, + 65535, 65536, + 1048576 + }; + + static int GetIntegerLiteralLength(long value) + { + int length = value < 0 ? 1 : 0; + do + { + length++; + value /= 10; + } while (value != 0); + return length; + } + + static int GetFractionDisplayLength(long num, long den, bool isDouble) + { + // Float integer constants use the `f` suffix; double constants need `.0`. + int numericSuffixLength = isDouble ? 2 : 1; + return GetIntegerLiteralLength(num) + GetIntegerLiteralLength(den) + + 3 + 2 * numericSuffixLength; // `num / den` + } + + // Unit fractions and denominators composed only of 2, 3 and 5 are already + // conventional forms; do not expand them just to reach a preferred scale. + static bool IsSimpleFraction(long num, long den) + { + Debug.Assert(den > 0); + if (num == 1 || num == -1) + return true; + while (den % 2 == 0) + den /= 2; + while (den % 3 == 0) + den /= 3; + while (den % 5 == 0) + den /= 5; + return den == 1; + } + + static int GetPreferredFractionScore(long num, int den, bool isDouble) + { + // Powers of two are native to binary floating point and therefore more likely to + // match by coincidence. Values of the form 2^n-1 are a stronger normalization + // signal, so allow them a slightly larger readability bonus. + bool isPowerOfTwo = (den & (den - 1)) == 0; + Debug.Assert(isPowerOfTwo || ((den + 1) & den) == 0); + int readabilityBonus = isPowerOfTwo ? 1 : 2; + return GetFractionDisplayLength(num, den, isDouble) - readabilityBonus; + } + + static bool TryGetPreferredFraction(object constantValue, bool isDouble, out long num, out long den, out int score) + { + num = 0; + den = 0; + score = int.MaxValue; + + double value = isDouble ? (double)constantValue : (float)constantValue; + if (!(Math.Abs(value) < 1.0)) + return false; + + foreach (int candidateDen in preferredFractionDenominators) + { + long candidateNum = (long)Math.Round(value * candidateDen); + if (candidateNum == 0 || candidateNum <= -candidateDen || candidateNum >= candidateDen) + continue; + if (!IsEqual(candidateNum, candidateDen, constantValue, isDouble)) + continue; + + int candidateScore = GetPreferredFractionScore(candidateNum, candidateDen, isDouble); + if (candidateScore < score || (candidateScore == score && candidateDen < den)) + { + num = candidateNum; + den = candidateDen; + score = candidateScore; + } + } + + return den != 0; + } + + Expression MakeFraction(IType type, long num, long den) + { + return new BinaryOperatorExpression(MakeConstant(type, num), BinaryOperatorType.Divide, MakeConstant(type, den)); + } + Expression ConvertFloatingPointLiteral(IType type, object constantValue) { // Coerce constantValue to either float or double: @@ -1557,11 +1654,35 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax ? FractionApprox((double)constantValue, MAX_DENOMINATOR_DOUBLE) : FractionApprox((float)constantValue, 200); - if (IsValidFraction(num, den) && IsEqual(num, den, constantValue, isDouble) && Math.Abs(den) != 1) + bool hasRegularFraction = IsValidFraction(num, den) + && IsEqual(num, den, constantValue, isDouble) + && Math.Abs(den) != 1; + + if (TryGetPreferredFraction(constantValue, isDouble, out long preferredNum, out long preferredDen, out int preferredScore)) + { + int baselineLength = hasRegularFraction + ? GetFractionDisplayLength(num, den, isDouble) + : str.Length + (isDouble ? 0 : 1); + // Do not replace an already-simple fraction (for example 21 / 32 or 2 / 15) + // just to reach one of the larger preferred scales. + bool regularFractionIsSimple = hasRegularFraction && IsSimpleFraction(num, den); + if (!regularFractionIsSimple && preferredScore <= baselineLength) + { + if (hasRegularFraction) + { + num = preferredNum; + den = preferredDen; + } + else + { + expr = MakeFraction(type, preferredNum, preferredDen); + } + } + } + + if (hasRegularFraction) { - var left = MakeConstant(type, num); - var right = MakeConstant(type, den); - expr = new BinaryOperatorExpression(left, BinaryOperatorType.Divide, right); + expr = MakeFraction(type, num, den); } }