1. Overview

When working with numbers, we often encounter scenarios where we must check whether a given number is positive or negative.

In this tutorial, we’ll explore how to effectively check if a number is positive or negative in Kotlin. Further, we’ll build step by step an idiomatic function that works for all Number instances.

2. Introduction to the Problem

Determining whether an integer is positive or negative isn’t a challenge. However, Kotlin has various Number types, such as Int, Long, Float, Double, etc.

We aim to create an idiomatic Kotlin number-checking function that reports whether a given number of any Number type is positive, negative, or zero.

To make the verification easier, let’s create an enum class NumberCategory:

enum class NumberCategory {
    Positive, Negative, Zero
}

So next, let’s start by checking Int numbers and achieving our goal step by step.

3. Using the if-else Expression

The most straightforward solution to determine whether a number is positive or negative would be comparing the number to 0. We can use Kotlin’s if-else expression to implement it:

fun categoryOfInt1(n: Int): NumberCategory {
    return if (n > 0) {
        Positive
    } else if (n < 0) {
        Negative
    } else {
        Zero
    }
}

As we can see, the code above is pretty straightforward. Next, let’s test if it can give the expected result if we pass different integers to the function:

assertEquals(Positive, categoryOfInt1(42))
assertEquals(Negative, categoryOfInt1(-42))
assertEquals(Zero, categoryOfInt1(0))

The test passes if we run it. Next, let’s make the code easier to read.

4. Using the when Expression

Kotlin’s when expression can often make code easier to read than if-else or switch-case expressions. So, let’s implement the categoryOfInt function in the “when” style:

fun categoryOfInt2(n: Int): NumberCategory {
    return when {
        n > 0 -> Positive
        n < 0 -> Negative
        else -> Zero
    }
}

As the code above shows, the when expression makes the logic pretty clear. Next, let’s test with different inputs:

assertEquals(Positive, categoryOfInt2(42))
assertEquals(Negative, categoryOfInt2(-42))
assertEquals(Zero, categoryOfInt2(0))

The test passes if we give it a run. Therefore, the “when” style function solves the problem too.

5. Making the Function Available to All Numbers

Although the “when” style solution solves the problem elegantly, the function only supports Int objects. If we want to do the same check on other types, following the path, we’ll create a set of functions such as categoryOfLong(), categoryOfFloat(), etc.

Next, let’s create one single function to support all kinds of Number instances.

The Number class is an abstract class. It defines a series of functions to convert between concrete Number types, such as toDouble(), toLong(), toInt(), etc. In other words, when we receive a Number instance, no matter if it’s Int, Long, or Byte, we can convert it to a particular Number type.

As Double has the largest value range among all Number types, we can change the function argument type to Number, and convert the input to Double, then compare its value to 0 to get the result:

fun categoryOfNum(n: Number): NumberCategory {
    val d = n.toDouble()
    return when {
        d > 0.0 -> Positive
        d < 0.0 -> Negative
        else -> Zero
    }
}

We can verify the function using the following test:

// Int
assertEquals(Positive, categoryOfNum(42))
assertEquals(Negative, categoryOfNum(-42))
assertEquals(Zero, categoryOfNum(0))
 
// Long
assertEquals(Positive, categoryOfNum(42L))
assertEquals(Negative, categoryOfNum(-42L))
assertEquals(Zero, categoryOfNum(0L))
 
// Double
assertEquals(Positive, categoryOfNum(42.42))
assertEquals(Negative, categoryOfNum(-42.42))
assertEquals(Zero, categoryOfNum(0.00))
 
// Float
assertEquals(Positive, categoryOfNum(42.42f))
assertEquals(Negative, categoryOfNum(-42.42f))
assertEquals(Zero, categoryOfNum(0.00f))

6. Further Improvement: Using the Extension Function

We’ve created categoryOfNum() to support all Number types. But, when we call it, we must first write the function name and then the input. Next, let’s make coding more fluent and natural by changing it into an extension function:

fun Number.category(): NumberCategory {
    val d = toDouble()
    return when {
        d > 0.0 -> Positive
        d < 0.0 -> Negative
        else -> Zero
    }
}

This extension allows us to invoke the function as 42L.category(), (-42.42f).category(), etc.:

// Int
assertEquals(Positive, 42.category())
assertEquals(Negative, (-42).category())
assertEquals(Zero, 0.category())
...   
// Float
assertEquals(Positive, 42.42f.category())
assertEquals(Negative, (-42.42f).category())
assertEquals(Zero, 0.00f.category()

7. Conclusion

In this article, we’ve learned how to create an idiomatic solution to determine if a Number instance is positive, negative, or zero.

As always, the complete source code for the examples is available over on GitHub.