Looking for .NET Math method that will zero a negative integer
This seems to be what you want, no?
Math.Max(0, num);
It's called Math.Max
:
Math.Max(0, x)
Given 32-bit signed integer num
, the following expression returns zero if it is negative, or the original unchanged value otherwise:
(~num >> 31) & num
This operation is sometimes called clamping; values less than zero are clamped to zero. To effect clamping on num
itself, use the following statement:
num &= ~num >> 31;
Explanation
Only positive integers (and zero) have 0
for their sign bit, which is the leftmost, or "most-significant bit" (a.ka., "MSB"). Let's consider the 32-bit case. Since bit positions are numbered from left-to-right starting with 0, the sign bit is "bit 31". By flipping this bit and then propagating it to each of the 31 other bit positions, you get a result where either:
- for positive values and zero, all bits are set (
0xFFFFFFFF
,-1
), or - for negative values, all bits are cleared (
0x00000000
,0
).
By masking the original value with this result, you have zeroed out the value, but only if it was negative originally.
Remarks
Since
&
(bitwise-AND
) has very low precedence in C#, you will usually have to surround these expressions with outer parentheses:((~num >> 31) & num)
If
num
is unsigned (e.g.,uint ui
), you must use a cast to make sure the shift is signed. This is called a right-arithmetic shift, and it ensures that the MSB is duplicated into each rightwards shifted position:((int)~ui >> 31) & ui
For 64-bit values, shift by 63 bits instead of 31:
/* signed */ long v; (~v >> 63) & v /* unsigned */ ulong ul; ((long)~ul >> 63) & ul
As shown, you must use the
~
(bitwise-NOT
) operator to flip the sign bit. If you attempt to use "unary minus"-
instead, you will get the wrong answer for value0x80000000
because this is one of exactly two integer values that is not affected by applying the minus sign to it. Bitwise-NOT
, on the other hand, is guaranteed to flip every bit of any/every value. (The other value that can't be negated is zero, which for this particular problem happens to work out correctly either way)If you're in a rush, here are some tested extension methods, ready to copy/paste.
public static int Clamp0(this int v) => v & ~v >> 31; public static long Clamp0(this long v) => v & ~v >> 63;
One hazard with using the methods shown in (5.) is that there's no error or warning if the caller forgets to assign the return value to anything. In C#7 you can define by-reference extension methods which allow for in-situ mutation of value-types. Such methods help avoid the aforementioned problem since they can (and accordingly always should) be declared as returning
void
:public static void RefClamp0(this ref int v) => v &= ~v >> 31; public static void RefClamp0(this ref long v) => v &= ~v >> 63; // 'void' ──^ 'ref' ──^ ^── result assigned by callee
Call-site examples for the preceding by-ref extension method on
int
:int x = -999; x.RefClamp0(); // CORRECT, clamps the value of 'x' in-situ; now x == 0 // x = x.RefClamp0(); // NO -- 'void' return enforces correct by-ref usage // CS0029: Cannot implicitly convert type 'void' to 'int' // -999.RefClamp0(); // NO -- compiler errors: // CS1510: A ref or out value must be an assignable variable // CS0201: Only [expressions] can be used as a statement
Learn more about non-branching code!
This code example provided above is one of the simplest bit-twiddling examples which demonstrates branchless code. If you're not familiar with it, the term generally refers to a wide variety of micro-optimization techniques which try to minimize conditional branches in user code, in order to reduce misprediction stalls in the CPU pipeline.
I think
Math.Max(0, x)
is what you want.