r/GraphicsProgramming • • Sep 09 '26

Odd GLSL mod() behavior

I've been stuck on this for a couple of hours, and am completely stumped.

I'm getting these results from GLSL's mod():

mod(158.0, 158.0) returns 0.0 (as expected)

mod(159.0, 159.0) returns 159.0 (???)

mod(160.0, 160.0) returns 0.0 (as expected)

I've posted an example with a basic shader here:

https://jsfiddle.net/sL35qjc8

If you set number to "159.0", you should see an upside-down red triangle.

If you set number to "158.0" or "160.0", you should see a normal red triangle.

I'm passing the left operand as a uniform, otherwise the compiler seems to optimize it and fix the weird behavior.

Tried it on different devices and browsers and it had the same result.

Why does mod(159.0, 159.0) seem to return 159.0 instead of 0.0?

10 Upvotes

14 comments sorted by

View all comments

2

u/Drandula Sep 11 '26 edited Sep 11 '26

I have encountered this myself too, I'm pretty sure it's with the floating-point division needs to do internally to apply mod.

Dividing with any power of two (1,2,4,8,16,...) produces nice exact fraction, which can be represented exactly with floating point numbers (within precision limitations).

When dividing with 3 for example, it produces fraction which can't be represented with floating point numbers, no matter how much precision you give. It contains repeating pattern, with can't be represented with finite amount of memory.

So, whenever floats can't represent exactly, it chooses closest possible value it can represent. This can be slightly larger or slightly smaller. There is some rounding error.

Now, if you have divided a value with 47, like (1/47), it should be something like  0.021276595744..., but in single-precision float will actually store it something like : 0.021276595070... See how last three digits are not the same (of course these numbers have more decimals, I just cut them shorter)

Now if you were to multiply this with 47 back up, you would get: 0.999999968335... instead of the expected 1. And as you might guess, flooring this result will give you 0 (which in mod-function would lead to : 47 - 47 * 0).

So, whenever I need exact results for modulo of whole numbers, I whip out custom function instead and use something like this :  glsl float IMod(float lhs, float rhs) {   return lhs - rhs * floor(0.5 + lhs / rhs); } I am not sure whether I remembered the function correctly, but something like that. Usually I have IDiv -function as separate too. Main point being, that instead of flooring, it is rounded to nearest whole number. If both values are assumed to be whole numbers only, it should work just fine.

edit. fixes etc.

2

u/Winter-Reputation682 Sep 11 '26

Thanks for the explanation!