runtime: wrong .ToString behavior for floating point types
double.ToString and float.ToString have a wrong behavior in the following cases
Console.WriteLine($"Expected 0, got : {((double)(-0.0)).ToString()}"); //prints "-0"
Console.WriteLine($"Expected 0, got : {((float)(-0.0)).ToString()}"); //prints "-0"
Console.WriteLine($"Expected 0, got : {((decimal)(-0.0)).ToString()}"); //Correct behavior, prints "0"
Console.WriteLine($"Expected 0, got : {(Math.Round(-0.0)).ToString()}"); //prints "-0"
Console.WriteLine($"Expected 0, got : {((int)(-0.0)).ToString()}"); //Correct behavior, prints "0"
Console.WriteLine($"Expected 0, got : {((int)Math.Round(-0.0)).ToString()}"); //Correct behavior, prints "0"
Console.WriteLine($"Expected 0, got : {((double)(+0.0)).ToString()}"); //Correct behavior, prints 0
Console.WriteLine($"Expected True, got : {0.0 == -0.0}"); //Correct behavior, prints True
this is on .net core version 3.0.100-preview3-010431
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 18 (5 by maintainers)
Yes,
+0.0 == -0.0, but that does not mean that they are behave the same when given as inputs to various operations. There are many operations (including simple ones like multiplication and division) where-0.0is either returned or impacts the result of an operation. For example1.0 / -0.0is defined to return-Infinity.The IEEE 754 specification, which defines the binary-floating point formats (such as
binary32andbinary64, which the runtime and C# language specifications map toSystem.SingleandSystem.Double, respectively) and their behaviors, specifies:So, we are aligning with the official standard which specifies that signs of zeros should be preserved both when converting to and from an external character sequence (i.e. a
string).You would already have had some of these special cases as the language compilers (such as C#, VB, or F#; as well as non .NET languages such as C/C++, Python, Javascript, Java, or Rust) will correctly preserve the sign when parsing either
-0.0or0.0. Most other frameworks (and now .NET in netcoreapp3.0 and higher) would likewise have correctly preserved the sign when parsing these values using the built-in floating-point parsing functions (in the case of .NET these areSystem.Double.ParseandSystem.Single.Parse)These values can be useful in a number of scenarios and hiding the fact that you are dealing with a value that may change the behavior of your underlying algorithm makes it more difficult to debug or diagnose various issues. Attempting to normalize these values such that they are always +0.0 would be costly and would prevent them from being used where they do provide additional benefits.
.NET Core is willing to make breaking changes (when they make sense) in major versions. This is one of the cases where such a break makes sense as it aligns us with the IEEE 754 specification, aligns us with what other languages/frameworks are doing when handling these values, and allows us to resolve a number of bugs that have been filed due to the parsing/formatting behavior being non-compliant/differing from the behavior of other languages/frameworks that implement IEEE 754. We are trying to ensure that cases like this are made publicly visible and elaborated to users such as via: https://devblogs.microsoft.com/dotnet/floating-point-parsing-and-formatting-improvements-in-net-core-3-0/
@tannergooding Created issue: https://github.com/dotnet/dotnet-api-docs/issues/2031
No, some users simply need to be better educated. dotnet is not written for failing college students.
@bigworld12 thanks, I was just a little concerned by the direction that things were heading. We’re on the same page! Documentation is always important. Cheers! 👍
i think the discussion has deviated from what i originally started, so i am closing this
@bigworld12 this is just introducing IEEE754 compliance, and being pragmatic like you requested is the wrong way to go IMHO. If users are surprised, it’s because they’re not familiar with the standard, and the framework shouldn’t do things ‘wrong’ just because it’s less confusing to people who don’t know how it’s supposed to work.
These are all cases that are spec’d as returning
-0and which can be shown to return-0(even on .NET Framework). Take for example the following program. It is very easy to miss that some of these are returning-0on .NET Framework becausedouble.ToStringjust prints “0” and you can only detect that it is negative zero by explicitly checking the sign or printing the raw bits..NET Framework (all versions) and .NET Core (prior to 3.0) prints the following (added comments and spacing for clarity):
.NET Core 3.0 and later (and any other framework using the shared sources) prints the following: