开发者

Looking for .NET Math method that will zero a negative integer

Similar in concept to Math.Abs() - I'm looking for a funct开发者_如何学Cion that when given a positive integer will return the same integer. If given a negative, will return zero.

So:

f(3) = 3
f(0) = 0
f(-3) = 0

Yes, this is simple enough to write on my own but I'm wondering if the .NET Math class already has this built in or if the same can be achieved by cleverly chaining a few Math.* calls?


It's called Math.Max:

Math.Max(0, x)


This seems to be what you want, no?

Math.Max(0, num);


I think

Math.Max(0, x)

is what you want.


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

  1. Since & (bitwise-AND) has very low precedence in C#, you will usually have to surround these expressions with outer parentheses:

    ((~num >> 31) & num)
    
  2. 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
    
  3. For 64-bit values, shift by 63 bits instead of 31:

    /* signed */     long v;      (~v >> 63) & v
    
    /* unsigned */   ulong ul;    ((long)~ul >> 63) & ul
    
  4. 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 value 0x80000000 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)

  5. 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;
    
  6. 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.


It looks like Math.Max is the way to go, but this would work too... ;)

(num + Math.Abs(num)) / 2


Math.Max is best, but without Math and in VB

(num >= 0) * -num
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜