Tomcat Vector3 components are 32 bit floats while naked float variables are 64 bit floats. So when you assign/cast from a naked float to a vector component, some truncating and rounding will occur. Depending on the exact bit representation of a particular value, you may end up with a slightly smaller or larger value. In your case it's a larger value which breaks the logic of your code when you test for the limits in the next frame.

    xyz Depending on the exact bit representation of a particular value, you may end up with a slightly smaller or larger value.

    So, the same situation can happen again at other values? And this behavior is not considered an error?

    • xyz replied to this.

      Tomcat Yes unless you cast your limit value to a 32 bit float beforehand, i.e. keep it as a Vector3 component.

        Tomcat And this behavior is not considered an error?

        This is a very good cautionary example of what can happen when you don't care about limitations of the floating point number representation. I always warn people that it will sneak and bite them in the ass sooner or later if they're not aware of how floating point works. But I mostly get waved off ๐Ÿ™‚

          xyz Yes unless you cast your limit value to a 32 bit float beforehand, i.e. keep it as a Vector3 component.

          My imagination is not working well at the moment, so I couldn't think of anything better:

          @export var high_max_v3: = Vector3(0.0, 1.57, 0.0)
          
          var high_max = high_max_v3.y

          And, yeah, the jamming has stopped.

          I always warn people that it will sneak and bite them in the ass sooner or later if they're not aware of how floating point works.

          I already knew that trying to use a game engine to simulate real physics is naive and futile. Well this is an unnecessary confirmation of that. Games are not simulation, but only imitation.

          But I mostly get waved off

          I'll memorialize your warning ๐Ÿ”–

          • xyz replied to this.

            Tomcat

            var high_max = high_max_v3.y

            ^ This still may be risky as it again involves casting between float32 and float64. Best to use high_max_v3.y directly whenever you actually need to compare or assign to another float32. Do not assign it to an intermediary float64 at all.

            Doing it with clamp() is actually the best way to handle this.

              xyz Best to use high_max_v3.y directly

              I wanted to do that at first, but got too lazy to edit all the lines. ๐Ÿ˜น But I guess it makes sense.

              Doing it with clamp() is actually the best way to handle this.

              Yeah, I have this option, but I wanted to figure it out because there may be other situations where clamp() may not be very convenient.

              Huge thanks for the clarification! ๐Ÿป

              It's sad that the engine doesn't have a choice of accuracy limits.

              xyz i.e. keep it as a Vector3 component.

              I have a suspicion that Vector3 is not necessary here. Vector2 has the same accuracy, doesn't it? Then we can take Vector2 and compare its components โ€” I only need 2 values of min and max.

              8 @export var high := Vector2(0.0, 1.57) # where in Vector2(x, y): x = high_min, y = high_max.
              โ€ฆ
              44 # with jamming.
              45		if tilt.position.y >= high.x and tilt.position.y <= high.y:
              46			tilt.position.y += event.relative.y * SENSITIVITY
              47			if tilt.position.y < high.x:
              48				tilt.position.y = high.x
              49			if tilt.position.y >= high.y:
              50				tilt.position.y = high.y

              โ€ฆwell, it works.

              xyz But I mostly get waved off

              i always found this weird. you'd think more would be weirded out by the fact a float extracted from a vector is always a little off. 0.0000432 is bound to turn into something like 0.004 eventually and cause problems.

              • xyz replied to this.

                Actually, there are vague mentions of the problem in the documentation, but very nebulous and the proposed solution is not very elegant:

                Note: Due to floating-point precision errors, consider using is_equal_approx instead, which is more reliable.

                • xyz replied to this.

                  Tomcat It's closely related but it's not exactly the problem you've experienced here. People generally heard about the caveat of "never comparing two floats using the operator ==". But your "bug" is a bit more insidious because your code never actually uses operator == to compare floats.

                  The confusing thing about this is the fact that some rational numbers that have finite numbers of decimals in base 10 representation, may have infinite number of decimals in base 2 representation and thus cannot be stored in the binary form without some small error. And this error will differ depending on the number of bits used to store the number. It's similar to how e.g. 1/3 cannot be accurately stored in finite number of decimals in base 10 representation but in base 3 representation (trenary system) it can be fully stored with just one decimal (1/3 is exactly 0.1 when written in trenary system)

                  How risky is this code:

                  @export var high_min: = 0.0
                  @export var high_max: = 1.57
                  
                  var high: = Vector2(high_min, high_max)

                  it is for separate input of vector components values. Then high.x and high.y will be compared component by component.

                  • xyz replied to this.

                    Tomcat It on depends what you do with it afterwards. If you continue to deal only with float32 numbers i.e. vector components, you should be fine. But in practice, it's really easy to make an oversight and assign a vector component to some plain float. So that's the situation you need to watch out for. It may not be a problem in most calculations, but as you've seen in your example, there are cases in which it can break your code.

                    Of course if you do equality checks using operator ==, you still may have problems even if all numbers are float32. So always use is_equal_approx() instead of == for equality checks.

                    But once you know this, all such gotchas can be easily detected and debugged by printing out your values every step of the way.

                      xyz If you continue to deal only with float32 numbers i.e. vector components

                      Yeah, that's right.

                      So that's the situation you need to watch out for.

                      Unfortunately, as I realized, programming is generally a field where you have to be extremely careful. ๐Ÿ˜พ

                      • xyz replied to this.

                        Tomcat Unfortunately, as I realized, programming is generally a field where you have to be extremely careful. ๐Ÿ˜พ

                        Yeah, it's not for the easily distracted ๐Ÿ™‚

                        Tomcat Oh and just as a side node, if you redo your logic to eliminate this redundant check:

                        if tilt.position.y >= high_min and tilt.position.y <= high_max:

                        mixing 32 and 64 bit floats wouldn't matter.
                        So you can simply do:

                        tilt.position.y += event.relative.y * SENSITIVITY
                        if tilt.position.y < high_min:
                        	tilt.position.y = high_min
                        if tilt.position.y > high_max:
                        	tilt.position.y = high_max

                        Which is exactly the same thing the clamp() does.

                          xyz if you redo your logic to eliminate this redundant check:

                          Yeah, I try to shorten the code as much as possible.

                          Which is exactly the same thing the clamp() does.

                          Using clamp() is of course shorter and more elegant, but now I wanted to understand the logic of setting and using vector components. And I think I almost understand it (or so it seems to me) with your help.

                          xyz than the most common issue of precision loss when converting between decimal and binary.

                          Just curious, do ternary logic computers have the same problem?

                          • xyz replied to this.

                            Tomcat I don't think such computers exist in practice. Watch out not to confuse trinary logic with trinary numeral system. Those are two different concepts. I was talking about the latter. But this is a "problem" of the floating point representation in general, regardless of the base. Floating point representation doesn't necessarily need to be binary. Theoretically, it can have any base. We just commonly use base 2 in computers because computers internally store data in binary form.