From 8de8daca601dc85e2b73687856f0a321016b4463 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Sep 2024 12:31:24 -0400 Subject: Introduce llless(), and use it for llmin(), llmax(). Add tests to verify that llless() correctly handles signed <=> unsigned comparison, which native "<" does not. --- indra/llcommon/lldefs.h | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/lldefs.h') diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h index 2fbb26dc1a..ed075e8d96 100644 --- a/indra/llcommon/lldefs.h +++ b/indra/llcommon/lldefs.h @@ -28,6 +28,7 @@ #define LL_LLDEFS_H #include "stdtypes.h" +#include #include // Often used array indices @@ -169,6 +170,31 @@ constexpr U32 MAXADDRSTR = 17; // 123.567.901.345 = 15 chars + \0 + // llclampb(a) // clamps a to [0 .. 255] // +// llless(d0, d1) safely compares d0 < d1 even if one is signed and the other +// is unsigned. A simple (d0 < d1) expression converts the signed operand to +// unsigned before comparing. If the signed operand is negative, that flips +// the negative value to a huge positive value, producing the wrong answer! +// llless() specifically addresses that case. +template +inline bool llless(T0 d0, T1 d1) +{ + if constexpr (std::is_signed_v && ! std::is_signed_v) + { + // T0 signed, T1 unsigned: negative d0 is less than any unsigned d1 + if (d0 < 0) + return true; + } + else if constexpr (! std::is_signed_v && std::is_signed_v) + { + // T0 unsigned, T1 signed: any unsigned d0 is greater than negative d1 + if (d1 < 0) + return false; + } + // both T0 and T1 are signed, or both are unsigned, or both non-negative: + // straightforward comparison works + return d0 < d1; +} + // recursion tail template inline auto llmax(T data) @@ -180,7 +206,7 @@ template inline auto llmax(T0 d0, T1 d1, Ts... rest) { auto maxrest = llmax(d1, rest...); - return (d0 > maxrest)? d0 : maxrest; + return llless(maxrest, d0)? d0 : maxrest; } // recursion tail @@ -194,12 +220,28 @@ template inline auto llmin(T0 d0, T1 d1, Ts... rest) { auto minrest = llmin(d1, rest...); - return (d0 < minrest) ? d0 : minrest; + return llless(d0, minrest) ? d0 : minrest; } template inline A llclamp(A a, MIN minval, MAX maxval) { + // The only troublesome case is if A is unsigned and either minval or + // maxval is both signed and negative. Casting a negative number to + // unsigned flips it to a huge positive number, making this llclamp() call + // ineffective. + if constexpr (! std::is_signed_v) + { + if constexpr (std::is_signed_v) + { + assert(minval >= 0); + } + if constexpr (std::is_signed_v) + { + assert(maxval >= 0); + } + } + A aminval{ static_cast(minval) }, amaxval{ static_cast(maxval) }; if ( a < aminval ) { -- cgit v1.2.3 From 0c42147fabaef31a2c577fc009dec354447c2e7f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Sep 2024 16:57:12 -0400 Subject: Avoid VC fatal warning when trying to fix un/signed comparison. --- indra/llcommon/lldefs.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'indra/llcommon/lldefs.h') diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h index ed075e8d96..d4b063f88c 100644 --- a/indra/llcommon/lldefs.h +++ b/indra/llcommon/lldefs.h @@ -183,16 +183,23 @@ inline bool llless(T0 d0, T1 d1) // T0 signed, T1 unsigned: negative d0 is less than any unsigned d1 if (d0 < 0) return true; + // both are non-negative: explicitly cast to avoid C4018 + return std::make_unsigned_t(d0) < d1; } else if constexpr (! std::is_signed_v && std::is_signed_v) { // T0 unsigned, T1 signed: any unsigned d0 is greater than negative d1 if (d1 < 0) return false; + // both are non-negative: explicitly cast to avoid C4018 + return d0 < std::make_unsigned_t(d1); + } + else + { + // both T0 and T1 are signed, or both are unsigned: + // straightforward comparison works + return d0 < d1; } - // both T0 and T1 are signed, or both are unsigned, or both non-negative: - // straightforward comparison works - return d0 < d1; } // recursion tail -- cgit v1.2.3