Could we have math.round()

We have both a floor and a ceil, so why not have a round? It’s used fairly often for user input/ display purposes, and I could see people using floor a lot less where it shouldn’t be used if they didn’t have to make a round function each time.

I know that round() is basically just floor X+0.5, but ceil is also just floor(X+1)

As for it being a Lua thing, Roblox has modified core libraries before, hasn’t it?

12 Likes

ceil is not floor(X+1) - does not work for whole numbers.
(also there’s some complexity with fp specials but let’s omit that)

However, I do think round is a pretty fundamental function and should be in math.

12 Likes

I have two things I’d want a general purpose round function to do:

  • Round to specific places (e.g. round 1.02423052 to 1.02 or 1234 to 1200)
  • Round 1.499999 to 1.5 and 1.2499999 to 1.25 without changing parameters (aside from the number to be rounded obviously)
2 Likes

So the widely used round(number,accuracy) with accuracy defaulting to 1?
(even if you didn’t mean that, this seems the best implementation)

4 Likes

The reason there isn’t a math.round already is because there’s half a dozen ways to round a number in the way you’re intending:

  • Round half up
  • Round half down
  • Round half towards zero
  • Round half away from zero
  • Round half to even
  • Round half to odd

By using “round”, you could easily be referring to four more types of rounding (two of which floor and ceil already provide):

  • Round towards minus infinity (math.floor)
  • Round towards plus infinity (math.ceil)
  • Round towards zero
  • Round away from zero

Also note that you might just want to display a number truncated to a number of places, in which case you should use string.format instead.

2 Likes

The reason there isn’t a math.round already is because there’s half a dozen2 ways to round a number in the way you’re intending:

This is unimportant. You specify a rounding mode (it’s actually easy to specify - you just pick the same rounding mode fp unit uses for rounding extra mantissa bit) and roll with it. C has a round function, GLSL has a round function, HLSL has a round function.

1 Like

Rounding to specific places is tricky - the result may not be representative as a floating point number (for example, 1.02 isn’t). You want a string conversion for this (as @Anaminus mentions, string.format already can do that)

I’m not sure what specifically your second example intends to achieve (what should happen to 1.149999999? 1.15 is not representable as a float).

1 Like

I have a different idea of what math.round() would do:

This will take three arguments:

math.round(number, roundToNearestMultiple, decimalPlaces)
-- e.g.
math.round(6.2538, 2.511111, 3) -- prints 5.022

Let me demonstrate what it would do:

-- it would essentially combine these two functions:

function RoundToNearest(x, nearestSnap)
     return nearestSnap * math.floor(x / nearestSnap + 0.5)
end
function Round(x, places)
     local decimal = math.pow(10, places or 0)
     return math.floor(x * decimal + 0.5) / decimal
end

print(RoundToNearest(0, 10)) -- prints 0
print(RoundToNearest(7.535, 13.5234)) -- prints 13.5234

print(Round(10.5234, 2)) -- prints 10.52
print(Round(RoundToNearest(12, 13.498787), 4)) -- prints 13.4988

-- Here's the combined version, exactly how it would look if implemented

function RoundToNearestWithDecimalAmount(x, nearestSnap, decimalPlaces)
	assert(x ~= nil, " argument 1 missing or nil")
	assert(nearestSnap ~= nil or decimalPlaces ~= nil, " math.round requires at least 2 arguments")
	local thisSnap;
	if nearestSnap then
		thisSnap = nearestSnap * math.floor(x / nearestSnap + 0.5)
		if not decimalPlaces then
			return thisSnap
		end
	end
	assert(decimalPlaces >= 0, " decimal places can only be positive")
	assert(decimalPlaces % 1 == 0, " decimal places has to be an integer")
	
	local thisDecimal = math.min(math.pow(10, decimalPlaces or 0), 1e9) -- so it doesn't crash and print "nan"
	local roundedSnap = math.floor((thisSnap or x) * thisDecimal + 0.5) / thisDecimal
	return roundedSnap
end

print(RoundToNearestWithDecimalAmount(100, 90)) -- prints 90
print(RoundToNearestWithDecimalAmount(8, 2, nil)) -- prints 8
print(RoundToNearestWithDecimalAmount(12.123123, 3.1, 2)) -- prints 12.4
print(RoundToNearestWithDecimalAmount(12.1234, nil, 3)) -- prints 12.123

Would this be a viable function?

5 Likes