4

I'm implementing a Q31.32 fixed-point numeric type based on System.Int64 in C#. Right now I'm trying to get the modulo operation correctly (%).

All implementations of fixed-point arithmetic I've seen define Qm.n modulo simply in terms of integer modulo, i.e. the modulo of two Qm.n numbers is the modulo of their underlying integer representation. This works in the general case but fails in two specific cases:

  • x % y throws an OverflowException if x == Int64.MinValue and y == -1. I can easily deal with this with an if statement and returning 0 in this case, although this is strange behavior (and unchecked is of no help here).

  • x % y incorrectly returns 0 for some small values of x and y. For example, if the integer representations of x and y are -413 and 59 (decimal: ~-0.000000096159 and ~0,000000013737), the modulo is 0 (decimal: 0) while the modulo of their decimal value is (according to System.Decimal) ~-0.000000013737. This error is about 60 times greater than the maximum precision of the type (2^-32), so it cannot be considered a rounding error.

What is the cause of this last error and is there anything I can do to obtain better accuracy?

Asik
  • 21,506
  • 6
  • 72
  • 131
  • what kind of modulo division you use ? maybe the problem is there. to be clear (integer or fixed/floating point division?) if you use shifted integer division there should be no error, on floating dividers there is always some error for small numbers – Spektre Oct 14 '13 at 09:44
  • Not sure you understand the question. I'm implementing fixed-point modulo based on Int64 modulo. In general, this just works, except in the aforementioned cases. You can see the implementation here: https://github.com/asik/FixedMath.Net/blob/master/Fix64.cs – Asik Oct 14 '13 at 17:34
  • I understood perfectly. I see you use long modulo so there are two options 1st. long modulo is wrong (improbable) or 2nd. your print is rounded try to print raw_values to check if modulo is OK. In that case you need to write your own print routine which will be more precise or reconfigure old one to match precision. – Spektre Oct 14 '13 at 18:27
  • Long modulo is not wrong, and my conversion operation from Fix64 to decimal or double is not wrong either. The problem is that implementing Q31.32 modulo in terms of long modulo creates large inaccuracies for specific values (while it works perfectly for the vast majority of them), and I don't quite get why. – Asik Oct 14 '13 at 20:53

1 Answers1

3

I found out the problem.

-413 % 59 = 0 is correct !!!

because -7 * 59 = -413

Your assumed correct result is mostly probable taken from 2s complement of -413 which leads to confusion.

[edit 1]

At Asik's suggestion I use calculator and my last comment to his question was right. The problem is in his print accuracy and not on above 2s complement or modulo see this:

413 >> 32 = 0.00000009615905582904815673828125  
 59 >> 32 = 0.00000001373700797557830810546875

0.00000009615905582904815673828125 / 0.00000001373700797557830810546875 = 7
0.00000009615905582904815673828125 % 0.00000001373700797557830810546875 = 0

for more info about printing numbers see this: https://stackoverflow.com/a/18401040/2521214

P.S. how exactly did you obtain result that modulo should be ~-0.000000013737 ? it is suspiciously equal to the -59>>32 ... maybe your reference can not handle signed numbers correctly (-413)<(59) and throw result of modulo simply 59 because of it (to avoid division) with signum combined from both numbers.

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • My guess is that the root of the confusion is the difference between *remainder* and *modulus*. C# has a *remainder* operator, not a modulus operator. Some other languages use `%` as the modulus operator, or some people simply expect `%` to perform a modulus. – Servy Oct 14 '13 at 18:41
  • I know that -413 % 59 == 0. I also know that the modulo of the fixed-point values that these numbers represent in my numeric type, i.e. ~-0.000000096159 and ~0,000000013737, is not 0. (Just use a calculator if you doubt it). The error is far larger than the precision of the type, suggesting that implementing Q31.32 in terms of the modulo of its underlying Int64 representation is not correct. My question is what would be a correct implementation in such cases. – Asik Oct 14 '13 at 20:58
  • I did as you wrote (calculator reveals my original suspect the print routine) and edited answer accordingly. See the edit at the end of my answer – Spektre Oct 15 '13 at 06:32
  • My print routine simply uses Decimal's. If you use decimal to calculate this you will arrive to the result I mentioned. I have assumed that the Decimal class is correct: is this possibly a bug in Decimal? – Asik Oct 22 '13 at 17:20
  • yes but that decimals are truncated values of your numbers !!! so if you want to modulo of truncated values you can not use integer modulo of the un-truncated numbers instead you must apply some kind of correction but i do not see any benefit from that – Spektre Oct 22 '13 at 17:35
  • So basically this is a precision issue: my results are correct but the numbers are too small to be represented exactly by Decimal so Decimal doesn't give the right result. Correct? – Asik Oct 22 '13 at 17:55
  • yes .. if you use the whole number as i write in my answer then the result is correct (0) ... although your numbers are in binary form not decimal so the precision issue is just with print routine and not the modulo itself – Spektre Oct 22 '13 at 21:40
  • When I print the result using my type it is "0". It's System.Decimal that calculates the wrong result. So this is a precision issue with Decimal. – Asik Oct 23 '13 at 17:53
  • well you should test the system decimals for sign error handling as i wrote in my answer try to solve the same numbers but with positive sign if it returns zero then the error is sign related... – Spektre Oct 23 '13 at 21:17
  • With positive operands the result is positive, but still wrong. Double gives the right answer. – Asik Oct 24 '13 at 19:08
  • strange... but at least now you know with what you dealing with – Spektre Oct 24 '13 at 19:14