Basically the Same function, but Different Results?

Hi,

So I have a small question about the usage of .Magnitude and a Custom function, as while playing around with both, I noticed a bit of a Difference between the two.

If you dont know, getting the Distance of something from both 2D, and 3D spaces, you use the Pythagorean Thorem.
Usually the Pythagorean Thorem a² + b² = c² when using it for 2D spaces, but for 3D Spaces the equation is a² + b² + c² = d², after that you get the spare root of that number, and that would be your Distance.

I Decided to make my own function, which is this:

local function distance (p0, p1) -- custom function
    return sqrt((p0.X - p1.X)^2 + (p0.Y - p1.Y)^2 + (p0.Z - p1.Z)^2)
end

Or if you wanted to simplify it:

local function distance (p0, p1) -- also custom function
    local p = p0 - p1 -- subtracts p0 and p1 before pythagroean theroem
    return sqrt(p.X^2 + p.Y^2 + p.Z^2)
end

Which testing, it works as normal, but this is .Magnitude, which is normally used to get the Distance of a Vector3.

(p0 - p1).Magnitude -- Magnitude function

When testing, they work about the same, giving the exact same amount, but not really, the custom function compared to .Magnitude is usually off by .0001 or less which I also checked by printing them together, this is an example from some of the tests I did:

-- This came from printing the Data.
Is the same? false -- a print that returned a boolean (d1 == d2)
Point 0: (80, 80, 47) -- starting Position
Point 1: (78, 47, 27) -- end Position

Magnitude:  38.63935852050781
CustomFunc: 38.63935817272331 -- Custom Function is off by a small amount

When testing this code, I have it randomly set to a different amount everytime, which looks like this:

-- This is done before both of the codes fire
local p0 = Vector3.new(random(1, 100), random(1, 100), random(1, 100))
local p1 = Vector3.new(random(1, 200), random(1, 1200), random(1, 200))

Why does this happen? and which is more Accurate to use?

4 Likes

Could be due to rounding errors that occurs during the calculation of floating-point numbers?
Magnitude is a built-in method in the Vector3 class of Roblox, I suppose is more optimized for performance and accuracy.

But, the difference of your function is almost nothing, and looks great, I think theres no real difference in practical cases

2 Likes

I’m going to say the custom one is more accurate. At least in your example it matches my phone, google, and my pc. Maybe Magnitude uses a bit less significance for performance reasons.

2 Likes

they kinda do the same

the distance function returns sqrt((x1 - x2) ^ 2 + (y1 - y2) ^ 2 + (z1 - z2) ^ 2)

which is the same of (p1 - p2).Magnitude

and .Magnitude returns
(x ^ 2 + y ^ 2 + z ^ 2) ^ 0.5

1 Like

Its basically the same thing, x^.5 is equivilent to sqrt(x), I just do not get why they are off by a small amount when they return the exact same thing.

Well, it might be of help to see how .Magnitude works internally, but we probably don’t have the luxury to look at those things at the moment. So the best I could do is speculate a bit.

Feel free to skip the first 2 paragraphs, but read them if you want.

First off, “x^0.5” might not always be the same as “sqrt()”. Functionally they are the same, but their internal algorithms may be different resulting in different results. (yea that wording is bad, I didn’t realise it at first)
There is a good chance that “x^y” is more “appoximated” than “sqrt()” as well. But I say this from what I’ve learned about sqrt and pow in other languages.

Or in other words, square roots can be calculated in one way, while performing a “pow” may require a different approach. You could say “sqrt” is a specialization of “pow” for specific inputs, meaning it could be more accurate than “x^0.5”, if the implementation isn’t doing an if-check for 0.5
Either way, kinda off topic


Going along with what others have said, it appears that your custom function is more accuate than the built-in one. And that could be because somewhere, there is either an approximated version of a certain thing. Could be an internal sqrt maybe. Because I know that it is possible to make a sqrt function that is a bit “faster” (depending on hardware), but you loose some accuracy. So it could be that it uses a different algorithm which gives slightly different results.

Although, because of somthing else I noticed, it could be because the data type is being “down converted” internally. From what I’ve heard, lua values are always 64 bit floats. But maybe .Magnitude is casting the values down to 32 bit in some place. I suspect this because of the two values you showed:

Magnitude:  38.63935852050781
CustomFunc: 38.63935817272331 -- Custom Function is off by a small amount

The digit where they stop being the same digit is the 7th digit. And I know that 32 bit floats generally have a precision of about 7 digits, not always though.


Last remark I will say on this is that you might want to also try squaring your numbers by just doing a multiplication, and see what happens. Like:

local dx = x1-x0
dx *= dx
-- etc

I think it won’t make a difference, but that would only be because “x^y” and “math.pow” may have internal if-checks for if the exponent is an integer. But even then, you could gain some performance benefits by doing it this way anyway. Keep that in mind.

2 Likes

Not sure what to mark as Solution, but I’ll wait until there is someone else, otherwise ill mark this.

I don’t see any problem in your function, either I like it. I read this post (for Python but same question you asked).

It said that none of them are accurate, especially with floating numbers. So, they would make up different answers. But using sqrt() is good.

As @Andrew900460 said above, Luau is considered to be 32 bit, so their function would not be much efficient and x^.5 would be just giving different results and only precision till 7-digits.

It could be many things, One of my theories is that Roblox uses C++ and it is calculated to be more accurate, because math.sqrt() is not as accurate.

That’s not quite what he said. Luau numbers are all 64-bit.
CFrames, Vector3s, and most other types save space by using 32-bit floats.

local a = 0.1
local b = Vector3.new(0.1, 0, 0)
print(("64-bit precision: %.30f"):format(a))
print(("32-bit precision: %.30f"):format(b.X))

--> 64-bit precision: 0.100000000000000005551115123126
--> 32-bit precision: 0.100000001490116119384765625000
3 Likes

Here’s a demonstration where I show how the backend is getting 38.63935852050781 instead of 38.63935817272331: C++ Shell (click “Run”)

In that, DistanceUsingFloats uses the std::sqrt(float) overload while DistanceUsingDoubles uses the std::sqrt(double) overload. Their docs say what the difference is:

std::sqrt is required by the IEEE standard to be correctly rounded from the infinitely precise result. In particular, the exact result is produced if it can be represented in the floating-point type. …

The additional overloads are not required to be provided exactly as (A). They only need to be sufficient to ensure that for their argument num of integer type, std::sqrt(num) has the same effect as std::sqrt(static_cast<double>(num)).

So I guess you could make an argument that your custom function, which works with doubles, is more accurate in a way… but not really, it’s doing a “more accurate” calculation by creating precision that doesn’t actually exist.

It would be like if you measured “5.4 cm” on a ruler, then said you made it more precise by writing a bunch of zeros after the 4.

Magnitude is faster, equally as (in)accurate, and people will know what you’re doing when they read it, prefer it every time.

6 Likes

Sorry, I wrote everything in hurry (I had my class…). I wanted to convey what you just said.

1 Like

Yup, so it was a precision thing.

To be clear, the “x^.5” was me just speculating that I (we?) don’t know how lua’s “power operator” works behind the scenes.

Most languages don’t have a power operator, they just have a function called “pow(x,y)” in their standard library. Which means somone had to code the implementation, contrasted to multiplication and addition which have hardware implementations (the cpu already knows how to do them).
So from one language to another, and from one library to another, implementations can be different and give different results. And I made special mentions about “x^.5” because I don’t know how different it is from just doing a sqrt.
In the event you wanted to make your own magnitude function.

It’s generally good to go with the more specialized version, rather than the generalized one, if you know your use case.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.