runtime: System.Half ToString() bug

Description

System.Half ToString() produces incorrect numbers.

Example: Half.MaxValue shows 65500, Half.MinValue -65500 but should be 65504 and -65504. Binary, the values are correct as I have checked:

image

Reproduction Steps

      var s1 = Half.MaxValue.ToString(); // 65500
      var s2 = ((double)Half.MaxValue).ToString(); // 65504

Expected behavior

      var s1 = Half.MaxValue.ToString(); // 65504

Actual behavior

      var s1 = Half.MaxValue.ToString(); // 65500

Regression?

No response

Known Workarounds

No response

Configuration

7.0.0-rc.1.22422.12

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 26 (13 by maintainers)

Most upvoted comments

You’re stating there are issues here and that it is trivial to produce them. You have not provided any concrete examples or explicitly stated which specific methods you believe to have issues.

You are likewise making statements that fixing these, while maintaining perf, is likewise trivial to do. This is done without example, proof, or references to existing papers that go over this in detail.

I am simply requesting you provide concrete evidence and statements. This is not a forum where “exercise left to the reader” is desired. We want and prefer concrete details and reproduction steps.

It’s all about the Round() functions:

So no new issues, only issues that have been stated to already be known and which are problematic to fix for many reasons.

For the string formatting functions it has the same reason and result. Not wrong but less precise in general and different string representations on different platforms what could be avoid.

This has already been covered as “by design”. We return the shortest roundtrippable string. 6.55e4 is shorter than 6.5504e4 and both round to the same underyling binary value. We then use the “F” format for numbers with less than 6 significant digits, so this expands to 65500. This is the shortest string because we only include 3 significant digits and then the rest of the data is trailing zeros. This is not going to change and is deterministic on all platforms/architectures currently supported by .NET.

This is actually identical what all processors does but without rounding for error-diffusion.

This is not going to have the desired handling and won’t be identical to double.Parse(double.ToString("G#")) (with the specified rounding mode differing from the current hardcoded ToEven).

When rounding any given number, we have to take the exact value represented. For Math.PI, this is 3.141592653589793115997963468544185161590576171875.

We then have to find the first digits significant digits, so if the user specified 10, we’d need to find 3.1415926535. Depending on the rounding mode, we may then need to consider remaining digits beyond n. The trailing here is 0.000000000089793115997963468544185161590576171875. If the trailing value is exactly 0.5 we need to consider midpoints, otherwise we need to round up/down. In this case, its above the midpoint so we’d typically round up (except for Truncate) and so the rounded value would be 3.1415926536, which can be represented as a double as 3.1415926536000000623971573077142238616943359375.

Simply dividing the two mantissas here, even with an adjustment, won’t correctly account for the base-10 vs base-2 representations.

Take Math.PI which is represented as 0x4009_21FB_5444_2D18 and 1e10 which is represented as 0x4202_A05F_2000_0000. Extracting the mantissas and adding the leading implicit 1 gives 0x19_21FB_5444_2D18 and 0x12_A05F_2000_0000. If you right shift the dividend by 53 you get 0xA300_0000_0000_0000, dividing these then gives 0x8C0 where right shifting will give 0.

Scaling up by a power of 10 in the first place is where the rounding issues first become present in the current impl because certain inputs will end up having a loss of data. Consider 1.15 which is actually represented by 1.149999999999999911182158029987476766109466552734375 and therefore rounding to 1 digit scales up to 11.5, thereby seemingly becoming a midpoint and losing the fact that this is under the midpoint and should therefore round down.

Thus the suggestion simply does not provide correct IEEE 754 compliant rounding as required and will continue producing incorrect results. It will likewise be more incorrect than the current implementation for other scenarios.

@tannergooding hi, there is a better and more simple algorithm that produces precise rounded string results. Not only for Half, not only for border cases. Check it out, you know where you can find it.