57

Can anyone explain how the modulo operator works in Python? I cannot understand why 3.5 % 0.1 = 0.1.

beruic
  • 5,517
  • 3
  • 35
  • 59
  • Possible duplicate of: [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – user202729 Jul 27 '18 at 13:13
  • 1
    @user202729 It is true that the accepted answer contains relatable information to the one you suggest, but at the same time it has some Python specifics, as this is a Python question, and is centered on modulo. – beruic Jul 30 '18 at 08:12

3 Answers3

99

Actually, it's not true that 3.5 % 0.1 is 0.1. You can test this very easily:

>>> print(3.5 % 0.1)
0.1
>>> print(3.5 % 0.1 == 0.1)
False

In actuality, on most systems, 3.5 % 0.1 is 0.099999999999999811. But, on some versions of Python, str(0.099999999999999811) is 0.1:

>>> 3.5 % 0.1
0.099999999999999811
>>> repr(3.5 % 0.1)
'0.099999999999999811'
>>> str(3.5 % 0.1)
'0.1'

Now, you're probably wondering why 3.5 % 0.1 is 0.099999999999999811 instead of 0.0. That's because of the usual floating point rounding issues. If you haven't read What Every Computer Scientist Should Know About Floating-Point Arithmetic, you should—or at least the brief Wikipedia summary of this particular issue.

Note also that 3.5/0.1 is not 34, it's 35. So, 3.5/0.1 * 0.1 + 3.5%0.1 is 3.5999999999999996, which isn't even close to 3.5. This is pretty much fundamental to the definition of modulus, and it's wrong in Python, and just about every other programming language.

But Python 3 comes to the rescue there. Most people who know about // know that it's how you do "integer division" between integers, but don't realize that it's how you do modulus-compatible division between any types. 3.5//0.1 is 34.0, so 3.5//0.1 * 0.1 + 3.5%0.1 is (at least within a small rounding error of) 3.5. This has been backported to 2.x, so (depending on your exact version and platform) you may be able to rely on this. And, if not, you can use divmod(3.5, 0.1), which returns (within rounding error) (34.0, 0.09999999999999981) all the way back into the mists of time. Of course you still expected this to be (35.0, 0.0), not (34.0, almost-0.1), but you can't have that because of rounding errors.

If you're looking for a quick fix, consider using the Decimal type:

>>> from decimal import Decimal
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')
>>> print(Decimal('3.5') % Decimal('0.1'))
0.0
>>> (Decimal(7)/2) % (Decimal(1)/10)
Decimal('0.0')

This isn't a magical panacea — for example, you'll still have to deal with rounding error whenever the exact value of an operation isn't finitely representable in base 10 - but the rounding errors line up better with the cases human intuition expects to be problematic. (There are also advantages to Decimal over float in that you can specify explicit precisions, track significant digits, etc., and in that it's actually the same in all Python versions from 2.4 to 3.3, while details about float have changed twice in the same time. It's just that it's not perfect, because that would be impossible.) But when you know in advance that your numbers are all exactly representable in base 10, and they don't need more digits than the precision you've configured, it will work.

user2357112
  • 260,549
  • 28
  • 431
  • 505
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 1
    You are missing the most important fact that `0.1` creates a floating point number that is slightly larger than `0.1`. Therefore `35 % 0.100000... = 0.9999999...`. Your post never mentions that, but always assumes that `0.1` is really `0.9999...`. This is simply not the case as shown by my answer. – bikeshedder Feb 08 '13 at 02:32
  • @bikeshedder: Where does it assume that `0.1` is really `0.9999...`, or even `0.0999...`? If that _were_ the case, you'd an error in the other direction, which wouldn't be noticeable. (If you don't understand why, try it and see: `3.5%0.0999999999` vs. `3.5%0.0999999999`, and then `3.5/0.0999999999` vs. `3.5/0.0999999999`. The first obviously follows the standard modulo law, minus a bit of rounding error; the second obviously breaks it by an entire unit.) – abarnert Feb 08 '13 at 06:01
  • 1
    `3.5 / 0.1000000... = 34.99999....` but due to rounding errors you end up with `35`. So it looks like `3.5 / 0.1` gives the exact result of `35`. There are really two rounding errors that cancel out each other. Please see http://ideone.com/fTNVho which shows this behaviour quite nicely. – bikeshedder Feb 08 '13 at 11:22
  • You're still hung up on irrelevancies. See http://ideone.com/zISIdx. 3.5/0.1 is very close to 35.0, and moving a small distance away from 0.1 in either direction does not change that. But 3.5%0.1 is a different story—moving a small distance away in one direction gives you nearly 0, in the other, nearly 0.1. That's the key issue here. It has nothing to do with one being close enough to print out as exact even though it isn't, or with rounding errors canceling out. Meanwhile, you still haven't answered my question. Where does my answer assume that `0.1` is really `0.09999....`? – abarnert Feb 08 '13 at 11:28
  • You say that `str(0.099999999999999811) is 0.1`. You never tell that the real number might as well be bigger. `str(0.10000000000001)` is `0.1` as well. *That* explains why the modulo operator results in `0.1`. – bikeshedder Feb 08 '13 at 11:40
  • @bikeshedder: Yes, `str(0.099999999999999811) is 0.1` explains why the OP thought he had 0.1, when he didn't. It obviously doesn't mean that all numbers s.t. `str(x) == 0.1` are 0.099999999999999811, and I can't imagine anyone could ever take it to mean such a thing, and I don't believe you seriously think that it does. You're just looking for some rationalization because you're annoyed that someone downvoted your question, so you want to downvote some other person's question. I don't see any point continuing this. – abarnert Feb 08 '13 at 11:46
  • Add the *important* fact that a float which looks like 0.1 can be 0.10000... and 0.99999... and I'll be happy. Otherwise this answer is simply misleading and does not answer the original question. – bikeshedder Feb 08 '13 at 11:49
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/24187/discussion-between-bikeshedder-and-abarnert) – bikeshedder Feb 08 '13 at 12:02
  • 10
    No, I have no interest in continuing this discussion. Everyone else understands my answer and thinks that it answers the question. The fact that you've added a spiteful downvote and a bunch of nonsense doesn't matter. The site is about providing useful answers, not maximizing rep points or convincing trolls. – abarnert Feb 08 '13 at 12:04
  • 7
    Think I'll stick with integers for now. – beruic Feb 11 '13 at 11:55
  • @abarnert You said "So, `3.5/0.1 * 0.1 + 3.5%0.1` is `3.5999999999999996`, which isn't even close to `3.5`." I think you made a mistake here. `3.5/0.1 * 0.1 + 3.5%0.1` should evaluate to `3.6` which is pretty darn close to `3.5999999999999996`. – Mike Martin May 25 '21 at 05:25
  • "If you're looking for a quick fix, consider using the Decimal type:" Decimal's `%` will [take the sign of the left-hand side](https://stackoverflow.com/questions/48347515), unlike for floats. – Karl Knechtel Jan 12 '23 at 02:11
12

Modulo gives you the rest of a division. 3.5 divided by 0.1 should give you 35 with a rest of 0. But since floats are based on powers of two the numbers are not exact and you get rounding errors.

If you need your division of decimal numbers to be exact use the decimal module:

>>> from decimal import Decimal
>>> Decimal('3.5') / Decimal('0.1')
Decimal('35')
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')

As I am being bashed that my answer is misleading here comes the whole story:

The Python float 0.1 is slightly larger than one-tenth:

>>> '%.50f' % 0.1
'0.10000000000000000555111512312578270211815834045410'

If you divide the float 3.5 by such number you get a rest of almost 0.1.

Let's start with the number 0.11 and continue adding zeros in between the two 1 digits in order to make it smaller while keeping it larger than 0.1.

>>> '%.10f' % (3.5 % 0.101)
'0.0660000000'
>>> '%.10f' % (3.5 % 0.1001)
'0.0966000000'
>>> '%.10f' % (3.5 % 0.10001)
'0.0996600000'
>>> '%.10f' % (3.5 % 0.100001)
'0.0999660000'
>>> '%.10f' % (3.5 % 0.1000001)
'0.0999966000'
>>> '%.10f' % (3.5 % 0.10000001)
'0.0999996600'
>>> '%.10f' % (3.5 % 0.100000001)
'0.0999999660'
>>> '%.10f' % (3.5 % 0.1000000001)
'0.0999999966'
>>> '%.10f' % (3.5 % 0.10000000001)
'0.0999999997'
>>> '%.10f' % (3.5 % 0.100000000001)
'0.1000000000'

The last line gives the impression that we finally have reached 0.1 but changing the format strings reveals the true nature:

>>> '%.20f' % (3.5 % 0.100000000001)
'0.09999999996600009156'

The default float format of python simply does not show enough precision so that the 3.5 % 0.1 = 0.1 and 3.5 % 0.1 = 35.0. It really is 3.5 % 0.100000... = 0.999999... and 3.5 / 0.100000... = 34.999999..... In case of the division you even end up with the exact result as 34.9999... is ultimatively rounded up to 35.0.


Fun fact: If you use a number that is slightly smaller than 0.1 and perform the same operation you end up with a number that is slightly larger than 0:

>>> 1.0 - 0.9
0.09999999999999998
>>> 35.0 % (1.0 - 0.9)
7.771561172376096e-15
>>> '%.20f' % (35.0 % (1.0 - 0.9))
'0.00000000000000777156'

Using C++ you can even show that 3.5 divided by the float 0.1 is not 35 but something a little smaller.

#include <iostream>
#include <iomanip>

int main(int argc, char *argv[]) {
    // double/float, rounding errors do not cancel out
    std::cout << "double/float: " << std::setprecision(20) << 3.5 / 0.1f << std::endl;
    // double/double, rounding errors cancel out
    std::cout << "double/double: " << std::setprecision(20) << 3.5 / 0.1 << std::endl;
    return 0;
}

http://ideone.com/fTNVho

In Python 3.5 / 0.1 gives you the exact result of 35 because the rounding errors cancel out each other. It really is 3.5 / 0.100000... = 34.9999999.... And 34.9999... is ultimatively so long that you end up with exactly 35. The C++ program shows this nicely as you can mix double and float and play with the precisions of the floating point numbers.

GKFX
  • 1,386
  • 1
  • 11
  • 30
bikeshedder
  • 7,337
  • 1
  • 23
  • 29
  • But in Python (and most other languages), `35.0` divided by `0.1` gives you `35`, and yet it gives you a rest of `0.1`, which is not compatible with the definition of modulo. – abarnert Feb 08 '13 at 01:07
  • It is all about rounding. There is no such thing as `0.1` when using floats. The decimal number `0.1` simply can not be converted to an exact float due to the nature of it (See: [IEEE floating point](http://en.wikipedia.org/wiki/IEEE_floating_point)). You end up with something like `0.099999999999999811` or `0.10000000000000000555...`. If you want to check the number please do not use `str(0.1)` but something like `"%.100f" % 0.1`. – bikeshedder Feb 08 '13 at 01:22
  • 1
    I understand about rounding (I already explained it 15 minutes before you posted your answer). But that's not the whole story. `0.1` is not off of `0.0` by a small rounding error (and `35.0` is also not off of `34.0` by a small rounding error.) The usual solution of "just toss in an appropriate epsilon" does not help; `abs(0.1-0.0) < eps` will not be true for any reasonable epsilon. So, your answer is misleading. – abarnert Feb 08 '13 at 01:42
  • @abarnert The question is "why is 35 % 0.1 = 0.1" and that is what my answer is about. I updated my answer to give a little more insight what happens and why you end up with those weird results. Hope that explains it better. – bikeshedder Feb 08 '13 at 02:01
  • Your latest addition, the code at http://ideone.com/fTNVho, is _also_ misleading. It does not show that "you can mix double and float and play with the precision". All it shows is that `(double)0.1f != 0.1` (see http://ideone.com/lFgkd9). When you perform arithmetic on a double and a float, C++ widens the float to a double, and then does double-double arithmetic. – abarnert Feb 08 '13 at 11:34
  • @abarnert You say "All it shows is that `(double)0.1f != 0.1`" - Great, that is all I wanted to show. `3.5/0.1 = 0.1` is only true because the two rounding errors cancel out each other. `0.1` is really slightly bigger than `0.1` and the result of `35.0` is slightly smaller than `35.0`. It just happens that when working with the same float type for dividend and divisor that those errors cancel out each other and you end up with the *correct* result. – bikeshedder Feb 08 '13 at 11:46
  • You're _always_ working with the same float type for dividend and divisor. `(double)0.1f` is a `double`, just as `0.1` is. They're not different float types, just different values. Anyway, I don't think your understanding or lack thereof of C numeric promotion rules is relevant to this question, so I'm done commenting on your answer too. – abarnert Feb 08 '13 at 12:02
2

It has to do with the inexact nature of floating point arithmetic. 3.5 % 0.1 gets me 0.099999999999999811, so Python is thinking that 0.1 divides into 3.5 at most 34 times, with 0.099999999999999811 left over. I'm not sure exactly what algorithm is being used to achieve this result, but that's the gist.

jjlin
  • 4,462
  • 1
  • 30
  • 23
  • This is almost true, except that it implies that `3.5 / 0.1` is `34.0`, and it's not—it's `35.0`. (Also, it doesn't explain why he's seeing `0.1` rather than `0.099999999999999811`.) – abarnert Feb 08 '13 at 00:57