Skip to content

Double to decimal conversion loses precision #72135

@dakersnar

Description

@dakersnar

I'm making a separate issue to discuss what was discovered here in more detail.

Summary

The current double to decimal conversion code relies on some false assumptions. Namely, it assumes a double can only represent 15 base-10 digits of precision, which is incorrect. This leads to quite a number of inaccuracies when converting. I've included some examples below. I also included the result of constructing the decimal via string parsing to show that it is entirely possible for a decimal to precisely represent each value.

Reproduction

double doubleValue = [INSERT VALUE HERE];
Console.WriteLine("Expected Result:");
Console.WriteLine(doubleValue.ToString("G99"));

decimal decimalValue = (decimal)doubleValue;
Console.WriteLine("Constructing Decimal Via Casting:"); 
Console.WriteLine(decimalValue.ToString("G99"));


decimal decimalValueParsed = decimal.Parse(doubleValue.ToString("G99"), System.Globalization.NumberStyles.Float);
Console.WriteLine("Constructing Decimal Via Parsing:"); 
Console.WriteLine(decimalValueParsed.ToString("G99"));

Outputs:

Expected Result Constructing Decimal Via Casting Constructing Decimal Via Parsing
10000000000000.099609375 10000000000000.1 10000000000000.099609375
79228162514264328797450928128 79228162514264300000000000000 79228162514264328797450928128
1.229999999999999982236431605997495353221893310546875 1.23 1.229999999999999982236431606

Fix

I have a PR coming up soon with a fix to this. At a high level, we can take advantage of sections of our existing double to string conversion algorithms, which are performing that base-2 to base-10 conversion at much better precision.

Impact

This issue has been around forever (as far as I can tell), so it is hard to say how high of a priority this fix is. I did run into this very bug while using the PowerToys calculator, which makes me believe this fix has high priority. Not being able to trust the output of your calculator because the framework it is built on has conversion bugs is concerning.

However, because this behavior has existed for so long, it is important to recognize that users might have incorrect expectations that we will be breaking with this fix. For example, a test case I found was assuming the double literal 1.23 could be represented exactly and casting it to decimal only validated their false assumption. With the fix applied, the more precise decimal we now get is not what they were expecting.

using System;

double doubleValue = 1.23;
Console.WriteLine("Actually stored as:");
Console.WriteLine(doubleValue.ToString("G99")); // 1.229999999999999982236431605997495353221893310546875

decimal decimalValue = (decimal)doubleValue;

var builder = new System.Text.StringBuilder("Hello");
builder.Append(decimalValue);
Console.WriteLine(builder.ToString()); 
// This currently (incorrectly) prints "Hello1.23"
// With the fix it prints "Hello1.22299999999999999822364316059"
// User should have used decimal literal 1.23m

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions