Skip to content

ILDasm regression in relation to double (float64) and float (float32) values #111014

@pcf0

Description

@pcf0

Description

ILDasm (9.0) outputs the wrong format in float64(...) and float32(...) for double and float values without decimal places. The decimal point is missing.
ILAsm interprets these as int64 and therefore casts them to double or float values, which leads to incorrect values.

Reproduction Steps

  1. Compile the code to DLL
  2. ildasm Program.dll /out=Program.il
  3. ilasm Program.il /DLL
  4. ildasm Program.dll /out=Program.il (to see the effect)
public static class Program
{
    public const double ValueDoubleOne = 1.0d;
    public const double ValueDoubleOnePointTwo = 1.2d;
    public const double ValueDoubleMinusOne = -1.0d;
    public const double ValueDoubleMinusZero = -0.0d;
    public const float ValueFloatOne = 1.0f;
    public const float ValueFloatOnePointTwo = 1.2f;
    public const float ValueFloatMinusOne = -1.0f;
    public const float ValueFloatMinusZero = -0.0f;
}

or

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(1.)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(-1.)
  .field public static literal float64 ValueDoubleMinusZero = float64(-0.)
  .field public static literal float32 ValueFloatOne = float32(1.)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(-1.)
  .field public static literal float32 ValueFloatMinusZero = float32(-0.)
}

Expected behavior

Output should be the same as input:

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(1.)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(-1.)
  .field public static literal float64 ValueDoubleMinusZero = float64(-0.)
  .field public static literal float32 ValueFloatOne = float32(1.)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(-1.)
  .field public static literal float32 ValueFloatMinusZero = float32(-0.)
}

Actual behavior

The IL code from step 2 will be (note the missing .s):

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(1)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(-1)
  .field public static literal float64 ValueDoubleMinusZero = float64(-0)
  .field public static literal float32 ValueFloatOne = float32(1)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(-1)
  .field public static literal float32 ValueFloatMinusZero = float32(-0)
}

After a roundtrip in step 4 the IL code will be:

.class public abstract auto ansi sealed beforefieldinit Program
       extends [System.Runtime]System.Object
{
  .field public static literal float64 ValueDoubleOne = float64(4.9406564584124654e-324)
  .field public static literal float64 ValueDoubleOnePointTwo = float64(1.2)
  .field public static literal float64 ValueDoubleMinusOne = float64(0xFFFFFFFFFFFFFFFF) // -nan
  .field public static literal float64 ValueDoubleMinusZero = float64(0)
  .field public static literal float32 ValueFloatOne = float32(1.4012985e-45)
  .field public static literal float32 ValueFloatOnePointTwo = float32(1.2)
  .field public static literal float32 ValueFloatMinusOne = float32(0xFFFFFFFF)
  .field public static literal float32 ValueFloatMinusZero = float32(0)
}

Regression?

Yes, it is a regression, the following versions of ILDasm work:

  • 4.0.30319.0
  • 8.0.0
  • 9.0.0-preview.3.24172.9

The first version that no longer works is: 9.0.0-preview.4.24266.19
I think the behavior was changed with this PR: #98336

Known Workarounds

As a workaround, you can only correct the output using appropriate regex.

[GeneratedRegex(@"float(32|64)\((-?\d+)\)")]
private static partial Regex FloatNumber { get; }

if (FloatNumber.IsMatch(ilcode))
{
    ilcode = FloatNumber.Replace(ilcode, "float$1($2.)");
}

Configuration

  • NET 9.0
  • Windows 10 22H2 x64
  • It should be independent of the configuration, as the code is used for all configurations. However, I have not tested it.

Other information

I am not an expert in C++, but I think the problem should be fixed by adding this code snippet after the two places?

sprintf_s(szf, 32, "%.*g", 8, (double)MDDV.m_fltValue);

sprintf_s(szf, 32, "%.*g", 17, MDDV.m_dblValue);

if (strchr(szf, '.') == NULL && !IsSpecialNumber(szf))
    strcat_s(szf, 32, ".");

Although these places were also affected by the change, they do not seem to make any difference for ILAsm:

sprintf_s(str, 64, "%.*g", 8, (double)(*((float*)dataPtr)));

sprintf_s(str, 64, "%.*g", 17, *((double*)dataPtr));

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions