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:

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)
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.
So no new issues, only issues that have been stated to already be known and which are problematic to fix for many reasons.
This has already been covered as “by design”. We return the shortest roundtrippable string.
6.55e4is shorter than6.5504e4and 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 to65500. 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 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 hardcodedToEven).When rounding any given number, we have to take the exact value represented. For
Math.PI, this is3.141592653589793115997963468544185161590576171875.We then have to find the first
digitssignificant digits, so if the user specified10, we’d need to find3.1415926535. Depending on the rounding mode, we may then need to consider remaining digits beyondn. The trailing here is0.000000000089793115997963468544185161590576171875. If the trailing value is exactly0.5we 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 forTruncate) and so the rounded value would be3.1415926536, which can be represented as a double as3.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.PIwhich is represented as0x4009_21FB_5444_2D18and1e10which is represented as0x4202_A05F_2000_0000. Extracting the mantissas and adding the leading implicit 1 gives0x19_21FB_5444_2D18and0x12_A05F_2000_0000. If you right shift the dividend by 53 you get0xA300_0000_0000_0000, dividing these then gives0x8C0where right shifting will give 0.Scaling up by a power of
10in 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. Consider1.15which is actually represented by1.149999999999999911182158029987476766109466552734375and therefore rounding to 1 digit scales up to11.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.