Browse Source

Prefer common machine-scale floating-point fractions

pull/4022/head
SychicBoy 1 month ago
parent
commit
59361d9de2
  1. 11
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs
  2. 129
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

11
ICSharpCode.Decompiler.Tests/TestCases/Pretty/WellKnownConstants.cs

@ -84,6 +84,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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;

129
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -1516,6 +1516,103 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -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 @@ -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);
}
}

Loading…
Cancel
Save