NaN Vector3 Comparison Broken (CFrame, too)

0/0 == 0/0 should return false, yet when comparing a NaN Vector3 to itself, it now returns true.
Yesterday, Vector3 and CFrame did not behave this way.

local v = Vector3.new(0/0, 0/0, 0/0)
local c = CFrame.new(0/0, 0/0, 0/0)
print(v == v) -- true
print(c == c) -- true
print(v.x == v.x and v.y == v.y and v.z == v.z) -- false
8 Likes

This is related to Call __eq even when tables are rawequal - #6 by zeuxcg

Briefly:

  • Since about forever, == returned true when the same userdata was compared to itself
  • This was accidentally broken by Luau release in mid-2019
  • A change made just now restored this to its previous behavior

Does this impact your game and if so, how?

5 Likes

There is no other way to detect NaN that I know of than comparing it to itself. Many developers do this.

3 Likes

Almost anytime I unitize a vector, I do a NaN check by comparing it against itself. I guess I have been doing this since 2019.
So now I’m leaking NaN Vector3s everywhere.

1 Like

Ok - interesting. I guess what I’m hearing is that devs started relying on the new behavior for NaN checks. I’ll revert this in a few minutes.

4 Likes

Wow I never organically came across this case when programming in vanilla Lua.
In the long term it’s probably better to keep more parity with vanilla Lua.
I will be changing all of my NaN checking code over to be in line with vanilla functionality.

I’ll revert this change and we are going to reevaluate internally.

4 Likes

Perhaps there should be explicit functions to check for NaN components.

This change has been reverted, sorry for the chaos - this wasn’t expected to go south :slight_smile: We’re going to reevaluate this decision internally. It’s possible that it’s worthwhile to implement the feature request I linked instead of going back to the old Lua behavior.

9 Likes

If the old behavior has a meaningful performance benefit, then maybe post a notice and try restoring it another time. I would push for whatever behavior has the best performance overall, because NaN will be confusing either way.

Use case 1: You add an arbitrary userdata to a table and expect to be able to remove it using table.find. One could compare this to inserting NaN itself, so it’s not obvious what the behavior should be.
Use case 2: You need to check if a userdata contains NaN. It may also be important to check for math.huge and -math.huge.

Equal-but-not-rawequal edge cases also surface when using userdata keys in tables, so that might be something to consider when deciding on __eq behaviors:

local t = {}
local v = Vector3.new(0, 0, 0)
t[v] = true
print(t[v]) -- true
print(t[Vector3.new(0, 0, 0)]) -- nil
2 Likes

We need to fix something either way - one fun fact is that table.find actually does use the rawequal check:

  > local v = Vector3.new(0/0, 0, 0) local t = {v} print(table.find(t, v), t[1] == v)
  1  false

There’s not really a performance difference either way unless you’re in the habit of comparing objects to themselves and expect this to be super fast… My initial inclination was to restore compatibility with Lua but since it’s now something that games depend on we could go back and instead honor the metatable-provided __eq regardless of raw equality which gives a more consistent behavior.

3 Likes