Does it cost less to send 1.123 than 1.123456 via remote events?

I have been wondering if event:FireServer(1.123) takes up less bandwidth than event:FireServer(1.123456), or are they both the exact same, as in they are both double precision floats, so they must take up the same exact bandwidth?

5 Likes

I don’t believe that just because the shortest precise decimal expansion of the double of the former has less digits than the letter, it’ll mean it’ll take up less bandwidth.
I believe they take up the same bandwidth so no, I don’t believe that it costs less to send 1.123 than 1.123456 via remote events.

5 Likes

Yeah that does seem like the most logical answer, but here is another one, is doing event:FireServer(1) cheaper than event:FireServer(1.123), if no (which I think is the right answer as all numbers in lua are double precision), then is there a way to explicitely send integers to save bandwidth? Same way you have Vector3int16 which is composed of 16bit signed integers.

I doubt it is, they would need some sort of integer type indicator along with it. There is no integer type, and they don’t have to add a hidden one just for remote events.

A somewhat hacky way to do this is to use a Vector2int16, left shift one of the axes, and use bitwise OR. Before sending we would use bitwise AND and right shift it. The client would do this:

local function cast_to_vector2int16(n)
	return Vector2int16.new(bit32.band(n, 0x11110000), bit32.band(n, 0x00001111))
end
event:FireServer(cast_to_vector2int16(1))

and the server would call this when recieving:

local function cast_back_to_number(v2i16)
	return bit32.bor(bit32.lshift(v2i16.X, 16), v2i16.Y)
end
1 Like

That’s actually pretty neat, as long as it saves bandwidth I dont see anything wrong with it.

Would there be a similar way to send single (binary32) or half (binary16) floats to save bandwidth if i need the decimal point?

I would put a decimal point after the last (or first, in bit positions) 3 bits when doing the same thing with a 32 bit number, so it would be a 29 bit number + x/8. If you won’t mess with large numbers I could say this is OK but otherwise you could actually make it, but I don’t think either of these are worth it. You would be saving only 4 bytes.

You could always use string.pack with f for single then unpack it as two
short (int16) value hh.

Vector2int16.new(string.unpack("hh", string.pack("f", value)))

(or simply send it as strings but I’m not sure how much bandwidth would this take)

Not sure about binary16.

1 Like

I am not sure what that does, it’s not really returning the number i inputed? I may be using it wrong but it gave me the idea of using Vector2int16.new() and use X as the integer and Y as the decimal portion of the float. So something like this:

local function numberToVec2int(number: number)
	return Vector2int16.new(math.clamp(math.floor(number), -32768, 32767), math.round(math.fmod(number, 1) * (10 ^ 4)))
end

local function vec2intToNumber(vec2: Vector2int16)
    return tonumber(vec2.X .. "." .. vec2.Y)
end

The problem with that is your number will cant be larger than 32767 or smaller than -32768, but I dont think my map will exceed 32k studs either directions.

Correct me if I am wrong, but this should save 32bits?

Yes, a double is usually 64 bits. I can’t really understand what you’ve done there, To move the decimal point, you could have first multiplied the number by 8 on the client, and then divided the number by 8 on the server.

No. All lua numbers are sent as signed float64s with a one-byte overhead for their type, meaning they take 9 bytes in total to pass through a remote.
An int16, in comparison, is only 2 bytes, 3 including a byte overhead.

I made a plugin specifically to observe this kind of behavior, which you can use:

2 Likes

What I have done is rather simple:

local function numberToVec2int(number: number): Vector2int16
	return Vector2int16.new(math.clamp(math.floor(number), -32768, 32767), math.round(math.fmod(number, 1) * (10 ^ 4)))
end

local function vec2intToNumber(vec2: Vector2int16): number
	return tonumber(vec2.X .. "." .. vec2.Y)
end

-- On server
event:FireAllClients(numberToVec2int(12345.123456789)) -- This sends 32bits worth of data

-- On client
event.OnClientEvent:Connect(function(compressedNumber)
    local num: number = vec2intToNumber(compressedNumber)
    print(num) -- 12345.1235
end)

So instead of sending 64bits worth of data, i take the number and “compress” it using vector2int16 and I send it to the client which results in me sending only 32bits, then client receives it and switches it back to a number.