The maximum angle between any two vectors in 3d space is 180 degrees, or math.pi when working in radians. The :Angle() method of Vector3 instances can return values higher than this however.
Take the following example:
local up = Vector3.yAxis
local down = -Vector3.yAxis
local angle = up:Angle(down)
print(angle <= math.pi)
print(math.deg(angle))
The code above prints:
false
180.00000500895632
This outcome is likely due to floating point imprecision. This is not great as it can lead to unexpected bugs in your code - as I experienced myself just now.
Expected behavior
Vector3:Angle() should never return a value higher than math.pi as it’s mathematically impossible. If it were about to, it should simply return math.pi instead.
Other APIs do avoid these rounding problems, for example when converting math.pi to degrees.
print(math.deg(math.pi) == 180) -- this prints 'true' despite pi being a number with infinite decimals.
As such, if vectorA == -vectorB is true, then vectorA:Angle(vectorB) should always return math.pi.
This is expected behavior, atleast I imagine it is. Floating point precision errors are common place and typically left to the developer to fix. What were you possibly doing that would result in this being an issue?
The majority of lua-based libraries (not luau/roblox) handle built in precision errors, however ROBLOX’s implementation of additional libraries (such as the Vector3 library) do not, which is a common reoccurance throughout the engine.
The vector operation introduces numerous additional possible areas for error, and it may not be practical for ROBLOX to add a soft fix for precision errors within those libraries, especially because everyone may have different use cases.
I’m curious where you got this information from, because I’m having trouble figuring out why “Floating point precision errors are common place” would justify for a method to potentially return values that are mathematically impossible, especially given how some common rounding situations are already handled in certain APIs (like with math.deg() as I explained in the original report).
That’s up to the Roblox engineers to decide, not its users. I’m just here to report the bug.
The API’s you forementioned are built within the standard lua library, and they handle floating point precision arbitrarily, iirc common place argument passes within the lua library are recognized. This is not the case for libraries in which ROBLOX has implemented.
If it were as simple as just rounding, then every language and library would do so. By “rounding” as you mentioned, you indirectly lose precision on results that may have no correlation to a floating point error, rather have similar results to one.
They have decided, hence why this is still a present issue, you are not the first nor the only one who has run in to this.
How do you know that… Either or if they have run into it/this has been reported before, they’ll close the ticket but not sure how you know how they’ve decided that it’s expected behaviour.
This is an issue inherent in the IEEE 754 floating point math specification. I have ran into problems with this in C when building trigonometric and hyperbolic functions for my math library. So, what I have done is when a “special” value is encountered, I just set the return equal to what it’s defined to be. For example, things like sine(pi / 4) = sqrt(2). I have those special values defined as constants so I don’t have to calculate them. Works pretty good.
With that being said, there are things you can do in C that you cannot do in LUA. Quite a few things in fact. It has to do with LUA being an interpreted language like Java/JavaScript/PHP/Python/Ruby/etc… verses actually being compiled into machine object code like C/C++/Pascal/Assembler/COBOL/etc…
Just my two cents, but Roblox should provide internal checks to make sure that invalid results are not returned. It’s faster to do it in C/C++ instead of LUA because LUA is interpreted and C is compiled.
One thing I forgot to mention. Since you know that the return value must be between 0 and 180 or 0 and pi, you can use math.clamp(value, 0, 180) or math.clamp(value, 0, math.pi to force it to be within the proper range.