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