Newbie question: modulo operator with negative operand, bug or feature?

Hi,

I'm new to the Ash shell so my apologies if this is well known. In normal maths and other shells and languages I've used, the modulo operator always returns a positive remainder. For example see this discussion (first post so I can't hyperlink it):

The Ash shell (and I am told Bash too) return a negative result. This code run on BusyBox illustrates the problem:

#!/bin/sh
echo "The result of A % B should be an integer in the range 0 to B-1"
echo "(9-7) % 5 = $(((9-7)%5)) (should be 2)."
echo "(7-9) % 5 = $(((7-9)%5)) (should be 3)."
exit 0

Is this accepted as just a 'feature' of the shells or is it a bug?

1 Like
echo "(9-7) % 5 = $(( $(( 9 - 7 )) % 5 )) (should be 2)."
# and
echo "(9-7) % 5 = $(( ( 9 - 7 ) % 5 )) (should be 2)."

Note the spaces.

Both work in bash. /bin/sh does not have to always equal bash, it should a modern POSIX shell. I do not know how /bin/sh evaluates on busybox.

I tried every shell I could find and the answer was:

$ dash modtest
The result of A % B should be an integer in the range 0 to B-1
(9-7) % 5 = 2 (should be 2).
(7-9) % 5 = -2 (should be 3).

across all POSIX shells, dash (Debian Almquist Shell), bash, ksh88, ksh93, zsh...

aia@localhost fleefoot]$ echo "(7-9) % 5 = $(((7-9)%5)) (should be 3)."
(7-9) % 5 = -2 (should be 3).

-2%5 is -2 according how the sign is handled in languages that satisfy it as a = ((a / b) * b) + (a % b)

An example to show the difference using Perl

[aia@localhost fleefoot]$ perl -le 'print -2%5'
3
[aia@localhost fleefoot]$ perl -le 'print (-2)%5'
-2

Hi.

-- Modulo - Wikipedia

Best wishes ... cheers, drl

2 Likes

@Scrutinizer - what did you try, the original code? The '$((' '))' construct requires spaces around it I believe.

NO! it does not need spaces, just leading spaces

I'm wrong - the $(( does not require spaces it is part of command substitution (from opnegroup.org):

But since that is true then why do we get wacky (divergent) results with POSIX-compliant shells? my ksh did not like it. My bash (3.4.3) did after I added some spaces. It specifically complained about %5. Hmm.

Aia, could you elaborate on why that should render a different outcome ?

In awk:

awk 'BEGIN {print (-2)%5, -2%5}'
-2 -2

Hi Jim, yes, the original code...

What ksh and bash are those on what OS? Any ksh I tried and any bash on any OS did not seem to have a problem with it,

(-2)%5 behaves in what it is called in the Perl documentation as "native C arithmetic" integer arithmetic (as provided by your C compiler) (Almost at the end of the page)
Some more about C affair with modulus.

-2%5 uses the Perl's implementation of how modulus should behave when dealing with negative numbers in Arithmetic.

A relevant discussion about it in one Perl forum

1 Like

Hi.

Somewhat extensively for the perl situation:

perlop - perldoc.perl.org

As pointed to from the table of how signs are propagated to results of integer modulo expressions in many programming languages: https://en.wikipedia.org/wiki/Modulo\_operation\#Remainder\_calculation\_for\_the\_modulo_operation

Best wishes ... cheers, drl

2 Likes

Thanks Jim, I tried that and it made no difference:

echo "The result of A % B should be an integer in the range 0 to B-1, let's test it ..."
echo "(9-7) % 5 = $(( $(( 9 - 7 )) % 5 )) (should be 2)."
echo "(7-9) % 5 = $(( $(( 7 - 9 )) % 5 )) (should be 3)."

Output:

The result of A % B should be an integer in the range 0 to B-1, let's test it ...
(9-7) % 5 = 2 (should be 2).
(7-9) % 5 = -2 (should be 3).

No spaces are required in an arithmetic evaluation. Spaces are sometimes required when you are doing command substitution when the command being executed by the command substitution starts with a subshell because the $(( could be the start of an arithmetic evaluation or could be the start of a command substitution. If it is intended to be a command substitution starting with a subshell, you may need to have a space between the two opening parentheses to avoid an ambiguity in the shell's grammar.

The following:

echo "(9-7) % 5 = $(($((9-7))%5)) (should be 2)."

should always work, but:

echo "$((echo a;echo b)|(wc -l;echo done))" 

might or might not work. To make it work reliably, you need a space as shown below:

var=$( (echo a;echo b)|(wc -l;echo done))

According to the standards, shell script writers should ALWAYS put a space between the parentheses when it is intended to be a command substitution starting with a subshell.

1 Like

Thanks drl,

The Perl document says:

which covers this case and the result should be positive or "matching the divisor". As you point out, the Wikipedia table shows BASH gives the same sign as the dividend and ASH seems to be the same.

My application was for a circular counter: given a value m, in the range 0..(size-1), the next entry is next=$(( ($m+1) % $size)) while the previous should be prev=$(( ($m-1) % $size) but that gives -1 for $m=0. The workaround is to use prev=$(( ($m+$size-1) % $size) which should work on any shell.

Thanks for all the detailed replies, I've picked up quite a bit on the syntax from this thread as well as resolving the question.

1 Like