GNU BC: "modulo" % with scale other than 0

The command manual says this about how BC calculates the modulo:

The result of the expression is the "remainder" and it is computed in the following way. To compute a%b, first a/b is computed to scale digits. That result is used to compute a - ( a/b ) * b to the scale of the maximum of scale+scale(b) and scale(a). If scale is set to zero and both expressions are integers this expression is the integer remainder function.


EDIT: I looked at the source code for GNU BC and found that the mod operator extends the division operator. In other words, the modulo is calculated as a by-product of the division. It relies on integer division to calculate the modulo. When scale is set, however integer division does not take place.

Try this in BC:

bc
scale = 0
print 5/2

scale = 5
print 5/2

you should get:

2        << Integer Division
2.50000  << NOT integer division!

Now let's plug in these figures the way BC does. The manual says it uses a-(a/b)*b to calculate. Let's plug in our two results, the one resulting from integer division and the one with a scale other than 0.

a - ( a/b ) * b
5 - ( 2   ) * 2  = 1  << CORRECT!
5 - ( 2.5 ) * 2  = 0  << VERY WRONG!

Without integer division:

a - ( a/b ) * b == a - (  a  ) == 0

This is why scale must be set to 0 for the modulo to work properly.
The issue seems to arise out of the design of BC and how it handles numbers with a 'scale'. In order for the modulo to work correctly we need integer division.

There are other much more advanced tools that are free and open source for this purpose, and I recommend you use them.


I solved it this way:

integer

define int(x) { oldscale=scale; scale=0; x=x/1; scale=oldscale; return( x ); }

modulo

define mod(x,y) { oldscale=scale; scale=1000; x = x - y * int(x/y); scale=oldscale; return( x ); }

HTH


user272970's answer is great. Here's a tweak to it:

define int(x) { auto oldscale; oldscale=scale; scale=0; x=x/1; scale=oldscale; return( x ); }
define fmod(x,y) { auto oldscale; oldscale=scale; scale=1000; x = x - y * int(x/y); scale=oldscale; return( x ); }

This (using auto oldscale) makes oldscale local to the function. Without that, setting oldscale in int() from fmod() will overwrite the oldscale that is trying to be saved in fmod(), leaving scale set to 1000 instead of whatever you had before calling fmod().

I added these functions to ~/.bcrc and set the BC_ENV_ARGS environment variable to ~/.bcrc. That will load these functions every time you run bc. So now I can just run fmod(x,y) any time I'm in bc without having to manually define those functions every time.

p.s. scale of 1000 might be overkill in most cases

Tags:

Bc