Monday, June 28, 2004

System.Decimal Appears Horribly Broken

Run this code:

using System;

public class App
{
  public static void Main()
  {
    Decimal a = 57675350989891243676868034224m;
    ShowModulus(a, 7);
    Console.WriteLine();

    ShowModulus(a, 8);
  }

  public static void ShowModulus(Decimal a, int m)
  {
    for (int i = 0; i < m; ++i)
    {
      a++;
      Console.WriteLine("{0} % {1} = {2}", a, m, a % m);
    }
  }
}

And you'll get this output:

57675350989891243676868034225 % 7 = -2
57675350989891243676868034226 % 7 = -1
57675350989891243676868034227 % 7 = 0
57675350989891243676868034228 % 7 = 1
57675350989891243676868034229 % 7 = 2
57675350989891243676868034230 % 7 = 3
57675350989891243676868034231 % 7 = -3

57675350989891243676868034225 % 8 = 1
57675350989891243676868034226 % 8 = 2
57675350989891243676868034227 % 8 = 3
57675350989891243676868034228 % 8 = 4
57675350989891243676868034229 % 8 = 5
57675350989891243676868034230 % 8 = 6
57675350989891243676868034231 % 8 = 7
57675350989891243676868034232 % 8 = 0

I may not know much, but I know that something is wrong there: the way negative numbers are showing up is inconsistent. Frankly, I don't think they should be there at all, but if they are, you'd expect similar patterns between mod 7 and mod 8. Try it with numbers other than 57675350989891243676868034225, and for moduluses (moduli?) other than 7 and 8, too. It breaks for those as well.

This appears to be a bug in the Decimal.Modulus routine - caveat emptor. Oh, and it's still broken in the Whidbey version I'm running (40607). I tried to submit the bug over on the new MSDN Product Feedback Center, but I get an error every time I try to submit. D'oh!

Frankly, this would scare the crap out of me if I were writing financial or scientific software that relied on System.Decimal. In my case the apparent workaround is easy (add the modulus to the result if the result is less than zero), but my confidence in the algorithm is pretty low.

15 comments:

  1. Send me email and tell me more about the error you get when you try to submit. Obviously, if we saw what you're seeing, we wouldn't have shipped!

    ReplyDelete
  2. For the record, I followed up with Sara in an email. Turns out that my "problem" is that I was using Mozilla Firefox. The web page appears not to work with Firefox - but I was able to submit using IE.

    ReplyDelete
  3. C:\User\SnippetCompiler> "c:\windows\microsoft.net\framework\v1.1.4322\csc.exe" /t:exe /utf8output /R:"C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\mscorlib.dll" /R:"C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\System.dll"

    ReplyDelete
  4. The above post is the results of a run I made using SnippetCompiler that confirms Craig's results. Sorry for the extra post but I accidentally overwrote my introductory commentary when I did my paste of the output. John

    ReplyDelete
  5. Sorry to hear you had problems with Product Feedback Center... it's not as simple as Firefox, since I was able to post a bug using FireFox 0.91 this morning. I know Sara has offered help, but if there's anything I can do, I'm happy to help!

    ReplyDelete
  6. Looking at Rotor, the remainder calculation seems to be implemented by using the formula d1 - (floor(d1/d2) * d2). If you try decimal d1 = 57675350989891243676868034225m; decimal d2 = 7; Console.WriteLine(d1 - (Decimal.Floor(Decimal.Divide(d1, d2)) * d2));

    ReplyDelete
  7. I agree that a large integer library is a better idea than floating point math. But since Decimal *is* the CLR's large integer library (sort of) I'd expect it to work. In any event, if what you describe is true, then I'd say there's a bug in Decimal.Divi

    ReplyDelete
  8. Regarding Decimal.Divide, I tried the following code: decimal d1 = 57675350989891243676868034225m; decimal d2 = 7; decimal d3 = Decimal.Divide(d1, d2); Console.WriteLine("{0} / {1} == {2}", d1, d2, d3); This prints "57675350989891243676868

    ReplyDelete
  9. Decimal is not a floating point number. It is a fixed point number. From the docs: =============== The Decimal value type represents decimal numbers ranging from positive 79,228,162,514,264,337,593,543,950,335 to negative 79,228,162,514,264,337,593,543,9

    ReplyDelete
  10. Although, after considering your comments some more, I think you're right, and in this case it's the docs (and my previous comment) that are wrong: Decimal is *called* a fixed point type, but I think it's *actually* a floating point type, as you say. In

    ReplyDelete
  11. Is the bug in Whidbey only? Or even in the Visual Studio 2003?

    ReplyDelete
  12. Both. Try it for yourself - though: only takes a second to write the code.

    ReplyDelete
  13. Yup... I just verified it on VS 2003. Any news on what Microsoft is doing about it?

    ReplyDelete
  14. Nope. Given that it's been broken for a long time, I wouldn't hold out too much hope on it getting fixed, either. :p

    ReplyDelete
  15. I have just tried this on an early version of Whidbey Bet2, and it is working correctly for me.

    ReplyDelete