Runtime: Dealing with decimal precision changes in netcoreapp3.1

Created on 10 Jan 2020  路  8Comments  路  Source: dotnet/runtime

The default precision of the Decimal type has changed between netcoreapp2.1 and netcoreapp3.1. It can be demonstrated with the following:

using System;
class Program
{
    static void Main(string[] args)
    {
        decimal d1 = 12345678900000000M;
        decimal d2 = 0.0000000012345678M;
        decimal d3 = d1 % d2;
        Console.WriteLine(d3);
    }
}

Multi-target that against net472, netcoreapp2.1 and netcoreapp3.1 and you will get the following:

P:\temp\console> P:\temp\console\bin\Debug\net472\console.exe
0.000000000983
P:\temp\console> dotnet exec P:\temp\console\bin\Debug\netcoreapp2.1\console.dll
0.000000000983
P:\temp\console> P:\temp\console\bin\Debug\netcoreapp3.1\console.exe
0.0000000009832122
P:\temp\console>

This change seems contrary to the announcement which specifically calls out Decimal as not changing

The standard does not impact the integer types, such as System.Int32 (int), nor does it impact the other floating-point types, such as System.Decimal (decimal).

Perhaps this change is unrelated.

In either case though what is the correct format specifier to provide to Decimal.ToString to get consistent output between the runtimes? Particularly how can I get netcoreapp3.1 to match the output from net472?

area-System.Numerics question

Most helpful comment

ToString changes for .NET Core, specifically says Decimal has changed.

The implementation for ToString hasn't changed for Decimal and x.ToString() will return the same y for both desktop and .NET Core.

What has changed is that x % y for decimal, can now return w instead of z (w containing more precision than z).

All 8 comments

This was an unrelated change: https://github.com/dotnet/coreclr/pull/20305 which was fixing a bug in the decimal remainder computation where a constant involved was 10x larger than necessary and cuasing unneeded truncation of the result: https://github.com/dotnet/coreclr/issues/12605

This was an unrelated change: dotnet/coreclr#20305

I get that we want to make progress but the continual churn in ToString output has served to be a significant adoption blocker for Roslyn.

What about this though:

In either case though what is the correct format specifier to provide to Decimal.ToString to get consistent output between the runtimes?

This is just one of the many ToString deviations that are blocking our upgrade to netcoreapp3.1.

As with the floating-point parsing changes, this was a change to produce more correct and there isn't a way to force decimal to use the old behavior.

In this particular case, you should be able to truncate the strings to the same length and see if they are the same (it looks like .ToString("F12") should work). Unlike double however, there isn't going to be any magic number that works in most cases. decimal has always been roundtrippable by default and this is a case where the underlying decimal has changed.

but the continual churn in ToString output has served to be a significant adoption blocker for Roslyn.

CC. @jkotas for this.

I don't think there is anything reasonable that can be done here. .NET Core and .NET Framework aren't aiming for bug compatibility with eachother and so .NET Core is going to naturally diverge over time, especially as compliance bugs or correctness bugs are resolved.

Many code bases (including CoreFX) have various tests duplicated to cover .NET Core and .NET Framework differences (testing the old output in one case and the new output in the other).

@tannergooding

In this particular case, you should be able to truncate the strings to the same length and see if they are the same (it looks like .ToString("F12")

That does not work for the more general case. It breaks a significant number of other cases.

Unlike double however, there isn't going to be any magic number that works in most cases

Is this documented anywhere? The blog post today, which is where you're going to land if you google for ToString changes for .NET Core, specifically says Decimal has changed. True it didn't change as a part of that effort but it has changed and it adds to the frustration here then dealing with this.

I don't think there is anything reasonable that can be done here.

My 2 cents would be to add a ToString format specifier that mandates IEEE compliance. Basically let the subset of customers who care about the scenario let themselves opt into the breaking change. Rather than forcing it on everyone.

That ship has sailed though. The break is here. I think the path forward for Roslyn is to just remove our desktop coverage. It's not worth the days of work to maintain it.

ToString changes for .NET Core, specifically says Decimal has changed.

The implementation for ToString hasn't changed for Decimal and x.ToString() will return the same y for both desktop and .NET Core.

What has changed is that x % y for decimal, can now return w instead of z (w containing more precision than z).

We do not consider fixes that fix and improve precision to be breaking changes (tm). For example, number of changes to fix and improve precision went into TimeSpan as well.

Many code bases (including CoreFX) have various tests duplicated to cover .NET Core and .NET Framework differences

The rule in CoreFX is to have coverage for all shipping configurations (it is ok to have more if there is a good reason to, but not less). If the component is supported on .NET Framework, we will have .NET Framework test coverage for it. If the behavior on .NET Framework is different due to underlying runtime differences, the tests are conditional as necessary. Yes, dealing with this extra matrix is an extra work.

This change seems contrary to the announcement which specifically calls out Decimal as not changing

The standard does not impact the integer types, such as System.Int32 (int), nor does it impact the other floating-point
types, such as System.Decimal (decimal).

That's not what that sentence means; it says that IEEE 754-2008 does not apply to ("does not impact") integer types or floating-point types other than System.Single or System.Double. Notably, that sentence doesn't mean that changes in .NET won't happen to System.Decimal or any other data types that IEEE 754-2008 (or its successor) does not apply to.

Was this page helpful?
0 / 5 - 0 ratings