Suggestion: math.ule

101 views
Skip to first unread message

Sainan

unread,
Oct 29, 2025, 9:38:18 AM (9 days ago) Oct 29
to lu...@googlegroups.com
I'm working with unsigned 64-bit integers and I'm trying to prevent overflows, like so:

assert(v <= 0xffffffffffffffff - bias);
v += bias;

Porting this code from C to Lua proved to be rather bothersome:

local lim = 0xffffffffffffffff - bias
assert(math.ult(v, lim) or v == lim)
v = v + bias

If we had a math.ule function, this could be a bit more trivial.

I guess I could also just check that v + bias >= v, but I'm not sure I love the assumptions this makes.

-- Sainan

Francisco Olarte

unread,
Oct 29, 2025, 11:14:17 AM (9 days ago) Oct 29
to lu...@googlegroups.com
It's been ages since I've done this, but IIRC:
ule(a,b) = not gt(a,b)
gt(a,b) = lt(b,a)
so ule(a,b)=not ult(b,a)

assert(not math.ult(0xffffffffffffffff - bias, v) --??

Maybe 
assert(not math.ult(0xffffffffffffffff - v, bias) -- Should be the same ??

Also, if bias does not need unsigned ( these kind of things happen in my code, in power calculations, I use a uint32 total but add int16 squared (excluding -32768, for symmetric range ), which are uint30 and fit in both int32 and uint32 ) I think you could just use 0xfff..fff -v <= bias directly, but caffeine level is too low now to assert it ).

Francisco Olarte.

Sainan

unread,
Oct 29, 2025, 11:44:29 AM (9 days ago) Oct 29
to lu...@googlegroups.com
> ule(a,b) = not gt(a,b)
> gt(a,b) = lt(b,a)
> so ule(a,b)=not ult(b,a)

Derp, for some reason I completely forgot about 'not'. You are indeed correct, thanks for pointing that out.

-- Sainan

Sergey

unread,
Oct 31, 2025, 3:36:02 AM (7 days ago) Oct 31
to 'Sainan' via lua-l
29.10.2025 16:37, 'Sainan' via lua-l пишет:
Here is function that can handle carry and overflows:
-- lua_int.lua

local function bitlen(limit)
	limit=limit or 512
	local x,y,i=0,1,1
	while x<y and i<limit do x,y,i=y,y*2,i+1 end
	return i
end

local function adc(a,b,c)
	a=a|0 b=b|0 if c~=0 then c=1 else c=0 end
	local r=a+b+c
	if a<0 then if b<0 or r>=0 then c=1 else c=0 end
	elseif b<0 then if a<0 or r>=0 then c=1 else c=0 end
	else c=0 end
	return r,c
end

local function sbc(a,b,c)
	a=a|0 b=b|0 if c~=0 then c=1 else c=0 end
	local r=a-b-c
	if a>=0 then if b<0 or a-c<b then c=1 else c=0 end
	else if b<0 and a<b+c then c=1 else c=0 end end
	return r,c
end

local function adci(a,b,c)
	local sa,sb,r,rc=a<0,b<0,adc(a,b,c)
	return r,rc, sa==sb and sa~=(r<0)
end

local function sbci(a,b,c)
	local sa,sb,r,rc=a<0,b<0,sbc(a,b,c)
	return r,rc, sa~=sb and sa~=(r<0)
end

return {
	bitlen=bitlen,-- returns 64
	adc=adc,   -- returns a+b+c, unsiged_carry
	sbc=sbc,   -- returns a-b-c, unsiged_carry
	adci=adci, -- returns a+b+c, usigned_carry, integer_overflow
	sbci=sbci, -- returns a-b-c, unsiged_carry, integer_overflow
}


Sainan

unread,
Oct 31, 2025, 4:53:58 AM (7 days ago) Oct 31
to lu...@googlegroups.com
Afaik, there is no difference between signed/unsigned for addition, subtraction, and multiplication in a two's complement system.

-- Sainan

bil til

unread,
Oct 31, 2025, 5:31:29 AM (7 days ago) Oct 31
to lu...@googlegroups.com
> Afaik, there is no difference between signed/unsigned for addition, subtraction, and multiplication in a two's complement system.

:).

Theoretically yes, but the practice can be hard :)... .

(a prominent error case is Win98, which needed PC restart after 50
days, because they forgot this issue for usec system time counter
subtraction issue, which is described in the following...).

A typical application would be subtraction of two integer time
counters tStart, ,tEnd to get the difference time tdiff.

The time counters are "nastily" "running through" from -MAXINT to
+MAXINT, but the difference time typically is smaller time value
without overflow danger.

In this case in C a nice formula would be:

int tdiff = (int)tEnd < 0 ? (uint)tEnd - (uint)tStart : (int)tEnd - (int)tStart

C will then automtically use the SUBS assembler command for the first
selection, and SUBU for the second... (exact sub names depending on
controller family of course...) - so this line in C is VERY fast... .

Doing something like this in a script language like Lua, which of
course want to "keep the variable type world" easy, does not present
such tricks, so it gets quite a bit more heavy / time-consuming.

I think in Lua you should best write (assumed of course, that all 3
variables are ints, tEnd after tStart (so tDiff positive) and that the
time difference is below 64bit limit... this is also assumed for the C
code above of course):

if( tEnd < 0) then
tdiff= -(tEnd-tStart)
else
tdiff= tEnd-tStart
end

(or "tdiff= tEnd < 0 and -tEnd-tStart or tEnd-tStart" ... for
"one-line-programming fans" :) )

Sainan

unread,
Oct 31, 2025, 5:50:07 AM (7 days ago) Oct 31
to lu...@googlegroups.com
The fact there's SUBS and SUBU means we're probably not dealing with a two's complement ISA like x86.

-- Sainan

Andrey Dobrovolsky

unread,
Oct 31, 2025, 6:05:36 AM (7 days ago) Oct 31
to lu...@googlegroups.com
Hi,

If your calculations are executed according to the same operands
bitnesses, there is really no difference between addition, subtraction
and (surprisingly) multiplication. But if You want to use 8-bytes
along with 4-bytes for example, You have to be aware of the higher
nibble expansion with zeroes (unsigned) or spreading the sign bit
(signed).
In Lua we have the single integer type, so I'm sure we don't need any
comparisons for correct results.
The different assembler commands may treat OVERFLOW bit differently,
for signed and unsigned operands. I'm not absolutely sure, but it
seems to me that Lua doesn't take care of OVERFLOW bit for integers.

Regards,
Andrew

пт, 31 жовт. 2025 р. о 11:50 'Sainan' via lua-l <lu...@googlegroups.com> пише:
>
> The fact there's SUBS and SUBU means we're probably not dealing with a two's complement ISA like x86.
>
> -- Sainan
>
> --
> You received this message because you are subscribed to the Google Groups "lua-l" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to lua-l+un...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/lua-l/6YVUUMkpG6EhOoONPWJvGQYBSWZNpYXvDdv92dixBkRn73T3zx2_v0AIrQlWXKFSSCvJGM6K6IT1VDRmhVxVI8tPf_ZejiX2HYK0Gxu6Xc0%3D%40calamity.inc.

bil til

unread,
Oct 31, 2025, 7:12:14 AM (7 days ago) Oct 31
to lu...@googlegroups.com
> The fact there's SUBS and SUBU means we're probably not dealing with a two's complement ISA like x86.

ups, sorry, I was wrong... . The problem is NOT the subtraction, also
there in fact is NO different SUBS and SUBU command in assembler... .

So in Lua AND C perfectly fine just to write

tdiff= tEnd - tStart

to get the difference time - this will work always as long as the
timer is 64bit.

The problem is only the comparison operator, e. g. if you want to
check t2 is the later time (assuming that the expired time is in
"lower int range"). In this case you have to check differently,
depending whether there was sign change or not:

if (t1>0) == (t2>0) then
bo2islarger= t1 < t2
else
bo2islarger= math.ult( t1, t2)
end

... thanks, I am slightly surprised that I did not check this earlier :) ... .

bil til

unread,
Oct 31, 2025, 7:25:09 AM (7 days ago) Oct 31
to lu...@googlegroups.com
Sorry, I have to correct myself once again ... :(.

To check the larger value, the most easy way would be

bo2islarger = t2 - t1 > 0

Just the following will fail:

bo2islarger= t2>t1

Sainan

unread,
Oct 31, 2025, 7:34:04 AM (7 days ago) Oct 31
to lu...@googlegroups.com
Yes, comparison indeed needs to differentiate, so I am glad Lua does have math.ult:

print(0 < 0x8000000000000000) -- false
print(math.ult(0, 0x8000000000000000)) -- true

But of course even without it, bitwise operations could be used for unsigned comparisons anyway.

-- Sainan

Francisco Olarte

unread,
Oct 31, 2025, 10:32:08 AM (7 days ago) Oct 31
to lu...@googlegroups.com
On Fri, 31 Oct 2025 at 12:25, bil til <bilt...@gmail.com> wrote:
To check the larger value, the most easy way would be
bo2islarger = t2 - t1 > 0

This is no good if you are using signed 64 to keep unsigned, reducing to 16 bits for compactness you have 0xFFFF-0x0001=0xFFFE, which is <0 in signed ( but obviously >= in unsigned ).

Even in signed you can have overflow. If t2 is math.mininteger and t1 is 1, t2-t1 is math.maxinteger.

Francisco Olarte.

Francisco Olarte

unread,
Oct 31, 2025, 10:38:39 AM (7 days ago) Oct 31
to lu...@googlegroups.com
On Fri, 31 Oct 2025 at 12:34, 'Sainan' via lua-l <lu...@googlegroups.com> wrote:
But of course even without it, bitwise operations could be used for unsigned comparisons anyway.

You normally do not need to resort to them, unless you are trying to squeeze something out.
something like:
function untested_kludgy_twos_complement_only_ult(a,b) 
   if (a<0) then
     if (b<0) then
        return b<a
     else
        return false
     end
   else
     if (b<0) then 
       return true
     else
       return a<b
     end
   end
end
-- Can use elseif and others, just wanted to make it clear.
-- Equals will fall to 1st/4th branch and properly return false.

Francisco Olarte.

bil til

unread,
Oct 31, 2025, 1:28:11 PM (7 days ago) Oct 31
to lu...@googlegroups.com
Thanks for pointing this out.

This "t2-t1 > 0" will work only for my timer application. (such
difference time counting would make no sense, if the difference time
exceeds or even aproaches the signed int value - it must always keep
far away from this (in typical applications this will be valid without
problems for 64 bit, even for 32bit)).

also in my example bo2larger should be better called bo2later, to
clarify this timer application (which anyway is quite important, you
meet such timer applications quite often typically...).

(and I showed this only to clarify the problems... of course using
math.ult would be the more correct way...).

Sean Conner

unread,
Oct 31, 2025, 3:29:21 PM (7 days ago) Oct 31
to 'Sainan' via lua-l
It was thus said that the Great 'Sainan' via lua-l once stated:
> The fact there's SUBS and SUBU means we're probably not dealing with a
> two's complement ISA like x86.

Not so fast. MIPS is a two's complement architecture that has a SUB and a
SUBU instruction. SUB will trap on overflow, while SUBU will not. That is
the only difference between the two instructions.

-spc

Sainan

unread,
Oct 31, 2025, 3:33:11 PM (7 days ago) Oct 31
to lu...@googlegroups.com
Huh, that's interesting and also... weird? Wouldn't 0xffff -> 0x0000 be just as much an overflow as 0x7fff -> 0x8000?

-- Sainan

Sean Conner

unread,
Oct 31, 2025, 3:54:20 PM (7 days ago) Oct 31
to 'Sainan' via lua-l
It was thus said that the Great 'Sainan' via lua-l once stated:
> Huh, that's interesting and also... weird? Wouldn't 0xffff -> 0x0000 be
> just as much an overflow as 0x7fff -> 0x8000?

As a signed value, 0xFFFF is -1. Adding one to that is 0. No signed
overflow, which is what SUB tests for (and I should have mentioned).

The x86 architecture (as well as others that are two's complement and have
an explicit condition or flag regsiter) make the distinction between signed
overflow (with a O or V condition/flag) and unsigned overflow (C
condition/flag).

-spc
Reply all
Reply to author
Forward
0 new messages