1. Overview

Reversing a number can be useful in many scenarios, such as when solving mathematical problems or manipulating data structures.

In this tutorial, we’ll explore how to print a given number in the reverse order in Bash while preserving the negative or positive sign symbol that may appear at its beginning.

2. Task Description

Our goal is to reverse any given number provided as input, such as 1234, -1234, and 1.234. The value can either be an integer or a floating-point number. Moreover, the number may have a or + sign preceding it, indicating whether it’s negative or positive.

In such cases, the sign symbol’s position as the first character should remain unchanged. Therefore, reversing -1234 should give -4321. Likewise, reversing +1234 should result in +4321.

To solve this task, we encounter two distinct problems:

  1. reversing a number not preceded by any sign
  2. detecting the presence of a sign before a number and ensuring its preservation in the output

Next, we’ll address these two problems one by one.

3. Reversing an Unsigned Number

In Bash, we can reverse any sequence of characters using the rev command. Considering that digits and decimal points are also characters, we can use the rev command to reverse the order of digits in a number:

$ echo '1.234' | rev
432.1

Here, we echo a number and pass the output as input to rev using a pipe. Alternatively, we can provide the number directly to the rev command using a here-string approach:

$ rev <<< '1.234'
432.1

For our needs, we’ll create a function named reverse_characters() that uses the rev command to reverse its input:

$ cat reverse_characters.sh
reverse_characters() {
    local input="$@"
    rev <<< "$input"
}

Within the function, we define a local variable named input and assign it all of the function’s arguments ($@). Then, we reverse input using rev and display it on the standard output (stdout).

To use our function, we source the script that contains it:

$ . ./reverse_characters.sh
$ reverse_characters 1.234
432.1

By using the function, we can reverse the number (1.234) provided as an argument, resulting in an output of 432.1. Later on, we’ll incorporate this function as a component in our final solution to reverse a number, which may be preceded by a or + sign.

4. Reversing a Signed Number

To reverse a number that may be accompanied by a sign at the beginning, we first test for the existence of a sign as the first character. Then, we can use our previous solution to reverse the unsigned number, and finally, prepend the sign if it’s present.

Additionally, it’s usually a good practice to verify right at the start that the provided input is a valid number, potentially accompanied by a sign, before proceeding with the reversal process.

4.1. Input Validation

The initial step in our procedure is to verify the input is in the expected format. To accomplish this, we create a function named is_valid_number() to test whether the input is a number that may include a sign:

$ cat is_valid_number.sh
is_valid_number() {
    local input="$@"
    if [[ "$input" =~ ^[-+]?(\.[[:digit:]]*|[[:digit:]]+\.?[[:digit:]]*)$ ]]; then
        return 0
    else
        return 1
    fi
}

The function uses regex within a [[]] construct to validate whether the provided input argument is in the required format:

  • begins with an optional or + character, which is indicated within a character class followed by the ? modifier
  • continues with either a decimal point followed by zero or more digits (\.[[:digit:]]*), or one or more digits, optionally followed by a decimal point and zero or more digits ([[:digit:]]+\.?[[:digit:]]*)

The caret (^) and dollar ($) symbols in the regex represent the start and end of the input, respectively, ensuring that no additional characters are present beyond those specified. The regex allows for floating point numbers, including cases with a missing zero where the number starts or ends with a decimal point, like .1234 or 1234., or even a single dot.

Importantly, the regular expression used doesn’t allow whitespace before, after, or within the number. So, if the input contains leading or trailing whitespace characters, the function won’t consider it a valid number.

The function returns 0 for a successful match and 1 for a failed match. This exit status proves useful for directing the execution flow within the final procedure.

To evaluate the functionality of the is_valid_number() function, we can again source the script and test using varying input:

$ . ./is_valid_number.sh
$ is_valid_number -1234 && echo true || echo false
true
$ is_valid_number +1234 && echo true || echo false
true
$ is_valid_number 1.234 && echo true || echo false
true
$ is_valid_number A1234 && echo true || echo false
false

For the inputs -1234, +1234, and 1.234, which match the regex format, the function returns a value of 0. This causes the logical operator constructs using && and || to echo true. Conversely, in the case of A1234, which doesn’t meet the required format, the function returns a value of 1, leading to the echoing of false.

4.2. Sign Validation

Once we’ve validated that the input is in the required format, we need to also check whether a sign is present at the beginning. If a sign is absent, we can simply proceed to reverse the number using our reverse_characters() function. However, if a sign is present, we need to extract it and preserve its existence in the output at the correct position.

To verify whether a number begins with a or + character, we again use regex:

$ cat is_signed_number.sh
is_signed_number() {
    local input="$@"
    if [[ "$input" =~ ^[-+](\.[[:digit:]]*|[[:digit:]]+\.?[[:digit:]]*)$ ]]; then
        return 0
    else
        return 1
    fi
}

This regular expression closely resembles the one employed in the is_valid_number() function. However, there’s a distinction: the absence of the ? modifier after the [-+] character class. This means that the character class is no longer optional. In other words, we’re specifically testing for the presence of a or + symbol at the beginning of the number.

Next, we source the script and test the is_signed_number() function using varying input:

$ . ./is_signed_number.sh
$ is_signed_number -1234 && echo true || echo false
true
$ is_signed_number +1234 && echo true || echo false
true
$ is_signed_number 1234 && echo true || echo false
false

We notice that the first two examples result in the detection of a sign, whereas the last example contains no sign symbol.

4.3. Reversing the Input

Now, we’re finally ready to make use of the previously defined functions to reverse a number that may potentially include a sign:

$ cat reverse.sh
is_valid_number() {
    local input="$@"
    if [[ "$input" =~ ^[-+]?(\.[[:digit:]]*|[[:digit:]]+\.?[[:digit:]]*)$ ]]; then
        return 0
    else
        return 1
    fi
}

is_signed_number() {
    local input="$@"
    if [[ "$input" =~ ^[-+](\.[[:digit:]]*|[[:digit:]]+\.?[[:digit:]]*)$ ]]; then
        return 0
    else
        return 1
    fi
}

reverse_characters() {
    local input="$@"
    rev <<< "$input"
}

reverse() {
    local input="$@"
    if ! is_valid_number "$input"; then
        echo "Invalid input: $input"
        return 1
    fi
    if is_signed_number "$input"; then
        local sign="${input:0:1}"
        local number="${input:1}"
        local reversed_number="$(reverse_characters "$number")"
        echo "${sign}${reversed_number}"
    else
        reverse_characters "$input"
    fi
}

The reverse() function implements the main logic of the program. Initially, it uses the is_valid_number() function to verify whether the input is in a valid format. If the input fails this validation, the function echoes a message indicating an invalid input and exits with an exit value of 1.

Otherwise, if the input is valid, the function continues with several steps:

  1. check whether the input includes a sign at the beginning using the is_signed_number() function
  2. if a sign is present, perform several operations:
    1. extract the sign using ${input:0:1} indexing and store it in a separate variable
    2. store the remaining digits and decimal point, if any, in the number variable
    3. reverse number via reverse_characters()
    4. prepend stored sign to the reversed number
  3. if the input doesn’t contain a sign at the beginning, it’s directly reversed with the reverse_characters() function

Let’s test the reverse() function after sourcing:

$ . ./reverse.sh
$ reverse 1234
4321
$ reverse +1234
+4321
$ reverse -1.234
-432.1
$ reverse A1234
Invalid input: A1234

Notably, the function succeeds at handling the different types of input in the expected manner.

5. Conclusion

In this article, we’ve explored the process of reversing a number while ensuring the preservation of its sign, if any. Initially, we verify the required input format and check for a sign symbol at the beginning. By extracting and preserving the sign symbol separately, we can accurately reverse any number while maintaining the original sign position.