1

The code below runs modulus 0.01 on the numbers 0.01, 0.02, 0.03, ... , 0.99

var testVals = Enumerable.Range(1, 99).Select(n => double.Parse("0." + n.ToString("D2")));
var moduloTest = testVals.Select(v => v % 0.01).ToList();

(note: parsing doubles from string is intentional, that way the only math operation made on the floats is the modulus)

I would expect the moduleTest list here to contain several floating point values that are very close to 0. However many of the values in the list are instead very close to 0.1.

I understand that floating point math is not exact, and introduces rounding errors but 0.1 here is not even close to 0.

innominate227
  • 11,109
  • 1
  • 17
  • 20
  • Does this answer your question? [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – phuclv Nov 08 '20 at 09:01
  • @phuclv I dont think modulus is mentioned at all on that page, so I dont consider it a duplicate. I was able to figure out an answer myself, and i added it below. – innominate227 Nov 09 '20 at 03:52
  • the modulus operator is completely irrelevant here. The thing is that binary floating-point types can't represent all values in decimal correctly, so the result will be "incorrect" regardless of the operator you're using, whether +, -, * or / – phuclv Nov 09 '20 at 03:57
  • I think modulus is relevant. Most built in math operators have a smooth curve, meaning your result will at least round to the expected value. Modulus is an exception it has a sawtooth curve (see chux answer) so you can get an answer that doesn't round to what you expect. – innominate227 Nov 09 '20 at 20:34

2 Answers2

1

I realized the answer as I was typing this. It does happen due to the inexactness of floats. Its just that for the modulus operation unlike most mathematical operations a small inexactness in the value can cause a large difference in the result.

0.03 for instance is most closely represented in floating point as something like 0.2999999999999999999 which if you were to do modulus 0.01 on would give you a number like 0.0999999999999

innominate227
  • 11,109
  • 1
  • 17
  • 20
1

I understand that floating point math is not exact, but in many cases here it seems to be not even close.

Modulus here did not add error/inexactness, just made quite visible the effect of prior inexactness.


With a quality implementation of modulus, there is no inexactness. See Is fmod() exact when y is an integer?.

The issues lie with the assuming math with decimal values like 0.01 always behaves close to those values converted to the closest representable float. Finite float values are all exact. It is the operations that formed their values are where the "error" comes in - with some exceptions like modulus.

Printing values in hexadecimal or with sufficient decimal precision (like 17 significant decimal places) is often enough to show the expected 0.01 is not encoded as a float as 0.01, but a value near that. Do this with computed results too for greater insight.

The nature of a modulus operation is a sawtooth curve like the green line here.

Given the error generated in 0.01 conversion and /, (not modulus), the result of the modulus of OP's values are expectantly just on either side of the discontinuity: about 0.0 or near 0.01.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • This answer mentions "/" (division operator) which was not in my original question (someone else altered the code in my question, I reverted it). This answer is still correct though, and does a better job explaining the cause than mine. – innominate227 Nov 09 '20 at 20:43