1

I'm curious to know if you could convert decimal to double, but still be able to come back to the same decimal I started with. Of course, assuming you use 15 or less significant digits. Basically, can you count on this:

decimal original = someValue;
double converted = ReversibleToDouble(original);
decimal result = BackToDecimal(converted);
if (original == result)
    Console.WriteLine("All is well with the world.");

I don't mind if ReversibleToDouble threw an exception or something if there is no such conversion, as long as the result is guaranteed upon successful completion. Right now, this is the trivial solution I have:

public static double ReversibleToDouble(decimal input)
{
    double output = (double)input;

    if ((decimal)output != input)
        throw new ArgumentException("Impossible to convert reversibly.", "input");

    return output;
}

public static decimal BackToDecimal(double input)
{
    return (decimal)input;
}

I don't really know much about numerical analysis so I have a couple of questions about this.

First question: is this trivial method guaranteed to work without raising exceptions if there are less than 15 significant digits before trying any conversions?

If not, second question: is there a way to make this work with greater probability?

If yes, one last question: is this doable without majoring in a field of science? :)

relatively_random
  • 4,505
  • 1
  • 26
  • 48
  • 2
    I think this is a good question (numerical analysis is hard), but I don't understand why it would be OK to lose information from the `decimal` by rounding but not OK to lose it by performing a naive conversion to `double`. – Michael Burr Oct 18 '16 at 14:43
  • 1
    @MichaelBurr Good point. I actually added that bit in because I know doubles have more limited precision, but in reality I won't really work with so many digits. Basically I'm more interested in the properties of these conversions and whether I can trust this equality check for relatively "simple" numbers that might look bad when represented in binary. It's completely true I can work around this problem, I'm just wondering if the problem exists at all. If this makes sense. – relatively_random Oct 18 '16 at 15:29
  • May I ask why you need this? – paparazzo Oct 18 '16 at 16:03
  • 2
    That's not in general possible. It is the conversion from base 10 to base 2 that changes the value. That conversion can only ever perfect when the decimal number is a finite sum of powers of 2. So there is a finite number of 1s and 0s in the binary value. If it is not then the conversion produces an infinite number of digits. Which necessarily gets truncated because *double* can store only 53 bits. Converting that value back to decimal will produce different decimal digits after the 15th digit. – Hans Passant Oct 18 '16 at 16:19
  • @Paparazzi I don't really *need* this. I'll edit the answer to remove that too strong of a statement. I was just wondering if I can use a decimal type of a setting on multiple classes, some of which may work with doubles. I'd like to be able to see if the class is still using the same setting. I can store the originally assigned setting for each of those classes or think of another workaround, but thinking about it just got me wondering if there was a way to actually make this round-trip lossless, similar to ToString("R") for doubles. – relatively_random Oct 18 '16 at 16:41
  • Hans Passant has it right. You can't get exact conversions except for certain numbers because Decimal is base 10 and Double is base 2. – Robert Harvey Oct 18 '16 at 16:48
  • @HansPassant If I then round this decimal back to 15 digits, does that guarantee the same number? Basically, by changing the BackToDecimal method to deduce the original value, not trying to force the ReversibleToDouble one to do miracles with base 2. – relatively_random Oct 18 '16 at 16:56
  • Hmya, it would make a lot more sense to round the decimal number *before* you convert it to double. If you do it afterwards then you are guaranteed to never get a match :) Maybe it is better to do it your way since you seem entirely too convinced that this should be possible. – Hans Passant Oct 18 '16 at 17:02
  • 1
    @HansPassant: It *is* true that (assuming underflow and overflow are avoided) a 15-digit decimal can be exactly recovered by (1) converting to IEEE 754 binary64 floating-point using round-to-nearest, then (2) converting back to a 15-digit decimal format using round-to-nearest. What the OP proposes is slightly more complicated than this, since it involves converting back to a higher-precision decimal format then rounding that result back to 15 digits, introducing potential problems with double rounding. But it's at least plausible that there's enough wiggle-room that this should still work. – Mark Dickinson Oct 18 '16 at 17:15
  • 3
    More info here: http://www.exploringbinary.com/number-of-digits-required-for-round-trip-conversions/ – Mark Dickinson Oct 18 '16 at 17:17
  • @MarkDickinson Was just about to post a comment linking to the same blog post :-) – njuffa Oct 18 '16 at 18:12
  • @HansPassant Yes, I meant both restricting to 15 digits *before* converting to double, then also rounding to 15 digits *after* converting to decimal again. – relatively_random Oct 18 '16 at 18:15
  • @MarkDickinson I just read the article you posted. Thanks. So according to that, it should be possible to convert 15 digits to double and back again. But why would double-rounding cause a problem? If I correctly skimmed through the article your article links to (http://www.exploringbinary.com/7-bits-are-not-enough-for-2-digit-accuracy/), the formulas are supposed to prevent ambiguous midpoints as well. Did I get the wrong idea? – relatively_random Oct 18 '16 at 18:47
  • If the original decimal has trailing zeros, they can be lost. For example `3.140000m` would round-trip to just `3.14m`. One way of seeing if there are trailing zeros is to inspect `result.ToString()`, the `decimal` override of the virtual method from `object`. – Jeppe Stig Nielsen Oct 18 '16 at 23:11

2 Answers2

0

You can't.

Decimal is a 128 bit type, double is a 64 bits type, so you're going to lose information when converting from decimal to double

Rik
  • 28,507
  • 14
  • 48
  • 67
0

What you request is like finding the shortest decimal value that would be rounded to the same float.

That's exactly the algorithm used to print a floating point in scheme, java, python, Squeak/Pharo Smalltalk, etc...

So the answer is yes, we generally can, as long as there aren't too many decimals. Now don't ask the difference between generally and allways, I'm not sure I can answer that easily.

In Pharo Smalltalk, you would use the message asMinimalDecimalFraction to do the job. https://github.com/pharo-project/pharo-core/blob/6.0/Kernel.package/Float.class/instance/printing/asMinimalDecimalFraction.st

A Fraction in Smalltalk is not exactly a Decimal in c#, since it has arbitrary precision and arbitrary denominator, but some principles above apply to both.

See also

Count number of digits after `.` in floating point numbers?

Convert float to rounded decimal equivalent

Community
  • 1
  • 1
aka.nice
  • 9,100
  • 1
  • 28
  • 40