In programming, it is very crucial to figure out whether a number is positive or negative. This article will walk through different ways to do that in C#. We will discuss in detail some of the very common ways to evaluate if a number is positive or negative, accompanied by code examples.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Conditional Operator to Check Positive or Negative Number

The easiest way to check a number’s sign is by using a conditional operator ?:. The conditional operator is also known as the ternary conditional operator. With ?: conditional operator, it can be decided if a number is greater than, less than, or equal to 0. 

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Let’s look at this with a simple example:

public static int IsPositiveOrNegativeUsingConditionalMethod<T>(T number) 
    where T : ISignedNumber<T>, IComparisonOperators<T, T, bool>
{
    if (number == T.Zero)
        return 0;

    return number > T.Zero ? 1 : -1;
}

Here, we define a generic method with a type parameter T. We expect a type that implements both the ISignedNumber<T> and IComparisonOperators<T, T, bool> interfaces. The ISignedNumber<T> interface includes methods for checking whether the number is positive or negative. Whereas the interface IComparisonOperators<T, T, bool> includes comparison operators such as < and >.

First, we check whether the number is greater than 0, then set the result as 1, if the number is less than 0, set the result as -1. In all, the result will be 1 if the number is positive, if the number is negative the result will be -1, the result will be 0 if the number is zero.

Bitwise Operators to Check Positive or Negative Number

Another concise method involves validating the positivity or negativity of a number by using bitwise operators. 

Left Shift Operator

The left shift operator (<<) is a bitwise operator that shifts the bits of a binary number representation to the left by a specified number of positions. The most significant bit (MSB) of a signed integer tells us its sign. Here, 0 is for positive and 1 is for negative.

Let’s see the general syntax of the left shift operator:

result = value << count

In short, value is the number whose bits are to be shifted, and count is the number of positions to shift the bits to the left.

Let’s take a closer look at the left shift operator:

public static unsafe int IsPositiveOrNegativeUsingLeftShiftMethod<T>(T number) 
    where T : unmanaged, IBinaryInteger<T>,ISignedNumber<T>
{
    if (number == T.Zero)
        return 0;

    int bits = sizeof(T) * 8 - 1;

    return ((T.One << bits) & number) == T.Zero ? 1 : -1;
}

For that purpose, we create a generic method with a type parameter T and return an int. We constrain to types that are unmanaged and implement both the IBinaryInteger<T> and ISignedNumber<T> interfaces.

(sizeOf<T>() * 8) - 1 calculates the number of bits in the binary representation of the type T. It uses the sizeOf<T>() method to get the size of the type in bytes and then multiply it by 8 to convert it to bits. This - 1 is used to determine the position of the most significant bit (MSB).

(T.One << bits) performs a bitwise left shift operation on the value 1 by bits positions. This effectively sets the MSB of T.One to 1 and the remaining bits to 0. Finally, the bitwise AND  with the original value number will return the MSB of the number. 

If the result is equal to, T.Zero it means the MSB of the number is 0, indicating a positive number. In this case, the method returns 1. If the result is not equal to, T.Zero it means the MSB of the number is 1, indicating a negative number. In this case, the method returns -1.

Right Shift Operator

Likewise, we have the option to use the right shift operator as well. The right shift operator (>>) is a bitwise operator that shifts the bits of a binary number to the right by a specified number of positions.

Let’s take a look at the general syntax for the right shift operator:

result = value >> count

Here, value is the number whose bits are to be shifted, and count is the number of positions to shift the bits to the right.

Let’s explore the right shift operator:

public static unsafe int IsPositiveOrNegativeUsingRightShiftMethod<T>(T number) 
    where T : unmanaged, IBinaryInteger<T>, ISignedNumber<T>
{
    if (number == T.Zero)
        return 0;

    int bits = sizeof(T) * 8 - 1;

    return number >> bits == T.Zero ? 1 : -1;
}

Here, we define another generic method which returns a int value, again constraining it to unmanaged types. We implement the IBinaryInteger<T> and ISignedNumber<T> interfaces. In the method, we calculate the number of bits in the type T using sizeOf<T>() * 8 - 1 (assuming it’s a binary integer type) and performs a bitwise right shift >> operation on the original number accordingly.

Inside the conditional operation, we check if the result of the bitwise right shift operation is equal to zero. This means the least significant bit (LSB) is 0, indicating a positive number. In this case, we return 1. If the result is not equal to zero, it means the LSB is 1, indicating a negative number. In this case, we return -1.

Math.Abs to Check Positive or Negative Number

The Math.Abs() is a basic math method that returns the same number by removing its sign. Consequently, this simplifies us to determine positive and negative numbers. 

Let’s look at Math.Abs():

public static int IsPositiveOrNegativeUsingMathAbsMethod<T>(T number) 
    where T : ISignedNumber<T>
{
    if (number == T.Zero)
        return 0;

    return T.Abs(number) == number ? 1 : -1;
}

We define a generic method that expects a type that implements the ISignedNumber<T> interface. This interface includes methods such as Zero and Abs for handling zero and absolute value operations, respectively. Here, we use theMath.Abs() method to calculate the absolute value. When we have a match, this implies positivity, otherwise, it indicates negativity.

Math.Sign to Check Positive or Negative Number

Furthermore, to the checking sign of a number, the Math.Sign() method provides a concise and readable built-in function, returning an integer that indicates the sign of a number. 

Let’s see how we can use Math.Sign():

public static int IsPositiveOrNegativeUsingMathSignMethod<T>(T number) 
    where T : INumber<T>
{
    return T.Sign(number);
}

Here, we use an interface INumber<T> that includes a Sign() method, which returns 1 for positive, -1 for negative, and 0 for zero. With the generic type parameter T constrained to INumber<T>, we accommodate diverse numeric types, like integers, floats, or custom numeric types.

Int32.IsPositive and Int32.IsNegative to Check Positive or Negative Number

.NET 7 introduced two new methods to validate whether a number is positive or negative. These methods are Int32.IsPositive() and Int32.IsNegative(). Similarly, .NET provides IsPositive() and IsNegative() method for other numeric data types.

Let’s take a look at how these .NET inbuilt methods works:

public static int IsPositiveOrNegativeUsingBuiltInMethod<T>(T number) 
    where T : ISignedNumber<T>
{
    if (number == T.Zero)
        return 0;

    return T.IsPositive(number) ? 1 : -1;
}

Now, we define a generic method that expects a type that implements the ISignedNumber<T> interface. This interface includes a method IsPositive() for checking whether the number is positive or negative. The T.IsPositive() method returns true for positive values and false for negative values. 

Our generic implementation also provides T.IsNegative() which will return true if the number is negative, otherwise it will return false for a positive number.

Performance Comparison

We evaluate the results of a benchmark running our code for different methods discussed in this article. We are using BenchmarkDotNet for benchmarking and comparison. Here, we took a sample of 1000 positive and negative numbers for benchmarking.

Now, let’s look at the results:

| Method            | Mean     | Error    | StdDev    | Median   | Rank |
|------------------ |---------:|---------:|----------:|---------:|-----:|
| MathSignMethod    | 132.9 ns |  2.11 ns |   1.97 ns | 132.9 ns |    1 |
| BuiltInMethod     | 137.5 ns |  1.97 ns |   1.54 ns | 137.5 ns |    2 |
| ConditionalMethod | 139.0 ns |  2.45 ns |   3.19 ns | 137.5 ns |    2 |
| LeftShiftMethod   | 194.8 ns |  9.47 ns |  27.92 ns | 209.2 ns |    3 |
| RightShiftMethod  | 208.1 ns |  4.14 ns |   7.46 ns | 209.5 ns |    3 |
| MathAbsMethod     | 535.4 ns | 86.07 ns | 253.79 ns | 327.7 ns |    4 |

From the result, we can see that Math.Abs() is the slowest, it is approximately 4 times slower than the other methods. The performance of all other methods of comparatively the same.

Ease of Usability

Besides performance, another important factor is the ease of usability for developers. In the below section, we have highlighted some of the important factors from the usability perspective for each method discussed in this article. 

Conditional operator ?:, presents a straightforward approach to sign evaluation. The clarity of the code makes it easy to understand, especially for developers who prioritize readability. However, the explicit nature of these statements may impact performance, especially in scenarios where computational efficiency is crucial.

Bitwise operators, specifically the left-shift (<<) and right-shift (>>) operators offer a more direct manipulation of binary representations. While these operations can be perceived as cryptic for some developers, they often provide performance benefits in scenarios where computational speed is paramount. 

The Math.Abs() method simplifies sign evaluation by returning the absolute value of a number. While this approach is clear and concise, it may involve additional computational overhead, particularly when compared to the direct manipulation offered by bitwise operators. 

The Math.Sign() method provides a concise built-in function, returning an integer that signifies the sign of a number. It balances readability and performance, making it an attractive choice for many developers. 

The latest additions to the C# arsenal, Int32.IsPositive() and Int32.IsNegative(), offer convenient inbuilt solutions for sign evaluation. These methods aim to enhance efficiency, but their performance characteristics need a thorough examination. 

Conclusion

We have seen various methods, such as conditional operators, bitwise operators, Math.Abs(), Math.Sign(), and .NET inbuilt functions, to check the sign of a number. This article aims to present these methodologies in a coherent and informative manner, facilitating a comprehensive understanding of sign evaluation in C#. Benchmarking insights empower developers to choose the most suitable method for programming, balancing readability and performance in sign evaluation in C#.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!