It is not a "floating point error" or "rounding error", computers have a finite set of decimal values to represent an infinite set of decimal values, all values cannot be represented in binary format and it will choose the closest one available if it does not have the exact value, it has nothing to do with rounding errors or if it's a 32 or 64 bit floating point value, it simply cannot store all possible decimal values, it's a "flaw" that comes with the IEEE 754 standard (it's a floating point standard, not a fixed decimal system) and the fact that a finite set of bits are used to "store" an infinite set of values.
This is one of the reasons you should use ceil/floor/round in GM, 1/2 is usually not 0.5, it's 0.499999999 something, and it's not a rounding error or decimal error, the CPU can't store the value 0.5 no matter how much it tries, there is no binary representation for it.
And as said above, if the value is 1.0 it might be displayed in GM as "1", if it was 1.00000000001 it would be displayed as "1.00" because GM choose to only show 2 decimals.
Integers between -16M and +16M usually are stored exact as an integer but that only works with exact literals as soon as a value is used in a calculation of any kind it will change, so even 1080/1 may no longer be 1080, it might be 1080.00000000001 or something, the viewport values comes from some calculations so you can't expect it to be exact.
Solution ? never count on "integers" in GM to be integer just because it looks like an integer
it's not GM's fault, that's the way floating point values work, if you need to do x == b use floor() to make sure it works (as said, integers between -16M and +16M can be stored as exact values if you floor them), take advantage of >= <= instead of == if possible or abs( x - y) < 0.00000001.