Very simple all-round camera controller. Weird jamming.
- Edited
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
- Edited
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.
- Edited
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.
- Edited
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.
- Edited
packrat Here's a little plaything that may help understanding how the stuff works:
https://www.h-schmidt.net/FloatConverter/IEEE754.html
Although the problem here was a little bit more nuanced than the most common issue of precision loss when converting between decimal and binary.
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.
- Edited
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.
- Edited
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.
- Edited
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.
- Edited
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.
xyz Watch out not to confuse trinary logic with trinary numeral system.
That's about what I was asking:
Setun (Russian: ะกะตััะฝั) was a computer developed in 1958 at Moscow State University. It was built under the leadership of Sergei Sobolev and Nikolay Brusentsov. It was the most modern ternary computer, using the balanced ternary numeral system and three-valued ternary logic instead of the two-valued binary logic prevalent in other computers.