dotnet / msbuild

The Microsoft Build Engine (MSBuild) is the build platform for .NET and Visual Studio.

Home Page:https://docs.microsoft.com/visualstudio/msbuild/msbuild

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug]: Inconsistent floating point behaviour changed between msbuild 16 and 17

oskarb opened this issue · comments

Issue Description

Running in Swedish regional settings, certain expressions that involve dividing and rounding values from msbuild properties return different results in msbuild 17 compared to msbuild 16. I suspect it's related to #8710 from 17.8.

Steps to Reproduce

In summary, I take TotalSeconds (double) from a TimeSpan and pit that in an msbuild property. Depending on how I then combined dividing and rounding in separate or combined expressions, the output is different.

The follow msbuild file show the differences:

<?xml version="1.0" encoding="utf-8"?>
<Project
 xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
         ToolsVersion="Current"
         DefaultTargets="Build">

    <Target Name="Build">
    
        <PropertyGroup>
            <TotalSeconds>$([System.DateTime]::UtcNow.Subtract($([System.DateTime]::UtcNow.Date)).TotalSeconds)</TotalSeconds>
    
            <Divided>$([MSBuild]::Divide($(TotalSeconds), 2))</Divided>
            <ThenRounded>$([System.Math]::Round($(Divided)))</ThenRounded>
    
            <CombinedDivideAndRound>$([System.Math]::Round($([MSBuild]::Divide($(TotalSeconds), 2))))</CombinedDivideAndRound>
    
            <AllInOne>$([System.Math]::Round($([MSBuild]::Divide($([System.DateTime]::UtcNow.Subtract($([System.DateTime]::UtcNow.Date)).TotalSeconds), 2))))</AllInOne>
        </PropertyGroup>
  
         <!-- This will output something like 31161,3224946. -->
        <Message Text="TotalSeconds: $(TotalSeconds)" Importance="High"/>
        
        <!-- Sending the msbuild (string?) property $(TotalSeconds) to the msbuild Divide function.
             Seems msbuild 16 will understand the decimal comma, while msbuild 17 will ignore it (thousand sep?) -->
        <!-- MSBUILD 16 output: 15580,66112473 -->
        <!-- MSBUILD 17 output: 1558066112473 -->
        <Message Text="Divided: $(Divided)" Importance="High"/>
        
        <!-- Sending the msbuild (string?) property $(Divided) to the Math Round function.
             In this case also msbuild 16 will ignore the decimal comma that we got in $(Divided)
             on that version. -->
        <!-- MSBUILD 16 output: 1558066112473 -->
        <!-- MSBUILD 17 output: 1558066112473 -->
        <Message Text="ThenRounded: $(ThenRounded)" Importance="High"/>
        
        <!-- Sending the output from divide directly to round in a single expression. -->
        <!-- MSBUILD 16 output: 15581 -->
        <!-- MSBUILD 17 output: 1558066112473 -->
        <Message Text="CombinedDivideAndRound: $(CombinedDivideAndRound)" Importance="High"/>
        
        <!-- Everything in a single expression. Correct output on both versions. But the expression is long and difficult to read... -->
        <!-- MSBUILD 16 output: 15581 -->
        <!-- MSBUILD 17 output: 15581 -->
        <Message Text="AllInOne: $(AllInOne)" Importance="High"/>
    </Target>

</Project>

Expected Behavior

I don't want to claim this is really the expected output, but it is the actual output on msbuild 16.11:

> "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\amd64\MSBuild.exe" dividetest.msbuild
  TotalSeconds: 34655,5998993
  Divided: 17327,79994965
  ThenRounded: 1732779994965
  CombinedDivideAndRound: 17328
  AllInOne: 17328

Actual Behavior

This is the actual output from msbuild 17.8:

"C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\amd64\MSBuild.exe" dividetest.msbuild
  TotalSeconds: 34738,1944274
  Divided: 173690972137
  ThenRounded: 173690972137
  CombinedDivideAndRound: 173690972137
  AllInOne: 17369

Analysis

It seems that on v16, msbuild will honour the decimal comma when it's present in a string msbuild property passed to Divide, but not when passed to Round. For Round I assume it is instead perceived as a thousands separator.

In v17, it is perceived as a thousands separator also for the Divide method.

When the different methods are combined into a single expression it seems to work and produce the correct result - I suppose in this case it never round-trips as a string msbuild properter. But the expression gets long and difficult to read.

I suspect this is related to the application of InvariantCulture for double TryParse as part of #8710.

I do hesitate to claim this is a bug... Perhaps it is as intended, but it does raise an interesting dilemma:

When a floating point value is put in string msbuild property, a decimal comma will be used. When later trying to use this property as an argument to something that takes a double, the decimal comma will be silently treated as a thousands separator instead. It is inconsistent. And to make matters worse, I suppose the same syntax will work fine in regions using a decimal point, but then produce incorrect results if run by someone in e.g. Swedish regional format settings.

If double parsing will insist on requiring decimal point, should msbuild not take care to use that also when converting doubles to strings?

So far I haven't found any docs talking about this subject in terms of "best practice" or similar.

Versions & Configurations

No response

The change in PR #8710 was for BUG #8798