dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.89k stars 782 forks source link

`%+f` printf format specifier adds both plus and minus for negative zero (-0) #15557

Open jwosty opened 1 year ago

jwosty commented 1 year ago

printf formatting a value of -0.0 with a %f format specifier and setting the + flag adds a plus and a minus sign to the result.

sprintf "%f" +0.0 // 0.000000 (correct)
sprintf "%f" -0.0 // -0.000000 (correct)
sprintf "%f" -0.0000001 // -0.000000 (correct)
sprintf "%+f" +0.0 // +0.000000 (correct)
sprintf "%+f" -0.0 // +-0.000000 (incorrect)
sprintf "%+f" -0.0000001 // -0.000000 (correct)

Expected behavior

sprintf "%+f" -0.0 should display -0.000000

Actual behavior

Displays +-0.000000

Known workarounds

None known.

Related information

Potentially related to: https://github.com/dotnet/runtime/issues/70460

charlesroddie commented 1 year ago

From a mathematical point of view this is a compendium of horrors.

LHS: Invalid: +0.0 (there is no unary +) -0.0 (-0 = 0) Valid: -0.0000001

RHS: Invalid: -0.000000 (-0 = 0) +0.000000 (there is no unary +) +-0.000000 (there is no unary +, and -0=0) Valid: 0.000000 (a number rounded to 6 dp)

Some of these horrors come from dotnet (the useless idea of unary +, the idea of "negative zero") but we should try to protect against this nonsense and certainly not go out of our way to support it.

tannergooding commented 1 year ago

IEEE 754 strictly defines both +0 and -0 and it is a requirement that it be correctly supported by any system utilizing those types. .NET has ensured it follows this required behavior for many releases now and will correctly preserve the sign when formatting a -0 (noting that just because +0 == -0 in terms of comparison, doesn't mean they are behaviorally the same).

Notably, -0 does conceptually exist in "proper mathematics". It's usage in computer math mimics this concept in that it is namely used to preserve the sign of a negative value that rounded to zero due to precision limitations. Effectively, representing the approach to zero from the negative domain, or x → 0−. This then allows still correct computation with regards to other later operations that may produce infinities, inputs that may get used in the complex number domain, in statistical analysis, and more.

Many computer languages do also define an unary + and it is simply the identity operation. It is often used for formatting and readability and is the logical counterpart to unary -. That is, -x is effectively "shorthand" for 0 - x and therefore +x is effectively the same for 0 + x.

None of these are "useless" or "nonesense" and can help with readability, maintainability, clarity, and correctness of the overall code. Particularly in specialized domains/fields. But beyond all that, some form of support for the functionality is also strictly required by any language/runtime that touts IEEE 754 conformance.

jwosty commented 1 year ago

@charlesroddie I'm mostly just making the point that the current behavior observed in this corner isn't consistent with itself under any interpretation. I'd rather it print +0 for any zero value (both positive and negative zero) than get +-0, which is clearly not what was intended.

By the way, long time no see! I hope you are doing well.