2

Given a double value:

double value = 123.456;

How would I obtain an unscaled representation of the value (123456) and scale(3)?

This was fairly trivial with decimal because it's not an IEEE-754 floating point implementation, but it seems much harder to do with double and float values.

All I have so far is methods that obtain the sign, exponent and significand/mantissa, but I've no idea what I need to do with these values to obtain the unscaled representation and scale:

private static int GetSign(double value)
{
    long bits = BitConverter.DoubleToInt64Bits(value);
    return bits >> 63 == 0 ? 1 : -1;
}

private static int GetExponent(double value)
{
    long bits = BitConverter.DoubleToInt64Bits(value);
    return (int)((bits >> 52) & 0x7FF) - 1023;
}

private static long GetMantissa(double value)
{
    long bits = BitConverter.DoubleToInt64Bits(value);
    return bits & 0xFFFFFFFFFFFFF;
}

(Of course, these methods could be simplified, passing in the bits, instead of obtaining them every time from double)

As per Sweeper's comment:

So would you expect double value = 123.4560 to have the unscaled value 1234560 and scale 4? if so, that is impossible because 123.456 and 123.4560 are the same value of type double. Note however, 123.456m and 123.4560m are different.

Insignificant trailing zeros can be ignored; i.e. 123.4560 should still have a scale of 3.

As per Sweeper's second comment:

This is not just about "insignificant trailing zeros". 123.456000000000003 has the same double representation as 123.456 too. It is exactly 123.456000000000003069544618484. Are you sure you want to get this value unscaled?

I gave this a test...

double a = 123.456;
double b = 123.456000000000003069544618484;
        
Console.WriteLine(a); // 123.456
Console.WriteLine(b); // 123.456

In this case, why does .NET ignore the trailing zeros and digits when printing the value?

(No, I would not want 123456000000000003069544618484 as an unscaled value).

Matthew Layton
  • 39,871
  • 52
  • 185
  • 313
  • 3
    So would you expect `double value = 123.4560` to have the unscaled value `1234560` and scale 4? if so, that is impossible because `123.456` and `123.4560` are the same value of type `double`. Note however, `123.456m` and `123.4560m` are different. – Sweeper Apr 26 '23 at 12:45
  • 1
    @Sweeper no, insignificant trailing zeros can be ignored in this case (question updated) – Matthew Layton Apr 26 '23 at 12:46
  • 1
    Note that `123.456` is just one of the possible _textual representations_ of the double. The double itself always has the same number of digits (the number of mantissa bits is constant) – PMF Apr 26 '23 at 12:47
  • 1
    @PMF absolutely; just as 123456/3 (unscaled/scale) could be another representation, which is what I'm trying to obtain...and failing miserably :( – Matthew Layton Apr 26 '23 at 12:48
  • 2
    This is not just about "insignificant trailing zeros". 123.456000000000003 has the same double representation as `123.456` too. It is exactly `123.456000000000003069544618484`. Are you sure you want to get this value unscaled? – Sweeper Apr 26 '23 at 12:51
  • @Sweeper are you softly suggesting to shift the problem to _before_ OP gets the `double` as `double` and demand being provided with either a `decimal` or combination of unscaled value + scale in the first place? – Fildor Apr 26 '23 at 12:56
  • @Sweeper I've updated the question again. – Matthew Layton Apr 26 '23 at 12:58
  • Does this answer your question? [Extracting mantissa and exponent from double in c#](https://stackoverflow.com/questions/389993/extracting-mantissa-and-exponent-from-double-in-c-sharp) – Charlieface Apr 26 '23 at 12:58
  • @Charlieface not quite. I've got the mantissa and exponent. I don't know how those relate to an unscaled value and scale. – Matthew Layton Apr 26 '23 at 13:00
  • I'm not sure what that means. How do you define scale then? There is no further information in the value than the sign, mantissa and exponent – Charlieface Apr 26 '23 at 13:01
  • @Charlieface 123.456 has a scale of 3 decimal places, but that's it's decimal representation, whereas it's binary representation seems to lose this information because it's actually 123.456000000000003069544618484 – Matthew Layton Apr 26 '23 at 13:03
  • Well there you go, there is no way in floating point to represent all possible decimals perfectly, even if they are within range. It sounds like you want to convert them to decimal wih a certain rounding, then get the scale from that – Charlieface Apr 26 '23 at 13:04
  • @Charlieface The discussion here was useful at least. I've learned some new things about IEEE-754 that I didn't already know (and that AI we're not allowed to talk about here didn't tell me either ;)) – Matthew Layton Apr 26 '23 at 13:07
  • 2
    If you think Console.WriteLine shows the interpretation you want then maybe simply go to conversion to a string an check the string instead of the Mantissa/Exponent thingy. Looked up what double.ToString does (a dragon4Double algorithm) and noone wants to reimplement that. The [algo](https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs,94cf2daf169d1901,references) – Ralf Apr 26 '23 at 13:08
  • "why does .NET ignore the trailing zeros and digits when printing the value" - Because including all decimals will make the value harder to read, so by default it uses a compromise between exactness and readability. Use `.ToString("R")` to format the value as "RoundTrip", if you want to ensure it can be parsed to the exact same value. – JonasH Apr 26 '23 at 15:01

1 Answers1

2

This might sound ugly, but I think the easiest approach is a string conversion to scientific notation:

double x = 123.456;
string s = x.ToString("e"); // Convert to scientific notation, so this will be 1.23456e+002

and then disassemble that string (the unscaled part is before the e, the scale is the exponent).

By using x.ToString("eXX") you can specify the (maximum) number of digits to print.

As per your last update: Console.WriteLine(x) internally calls x.ToString() to get a string representation of x. This uses a default number format of (I think) 6 digits. You can do Console.WriteLine(x.ToString("F15")) to get the full number.

PMF
  • 14,535
  • 3
  • 23
  • 49
  • 1
    I had considered strings, but I'm trying to steer clear of this approach...which may not be possible, I think. – Matthew Layton Apr 26 '23 at 13:01
  • 3
    Since the conversion to string is also just an algorithm (although a rather complex one), it is certainly _possible_ to avoid the explicit conversion to string, but the effort will be high. – PMF Apr 26 '23 at 13:03
  • 1
    I appreciate the answer anyway. It might be the last resort if there's no other easy way to do it, which it sounds like there isn't. Upvoted :) – Matthew Layton Apr 26 '23 at 13:05