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.

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.Â

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 the`Math.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#.