2

I'm shell scripting and I want to round a given integer to the nearest power of two. We can use any standard tools available from the linux command line. You can assume bash. So arithmetic expansion as well as bc would be available.

Rounding to the nearest power of two on a log scale (not as a shell script):

r = 2^(round(log2(x)));

Imagine input to a function and output like this:

# power2 11
8
# power2 12
16
# power2 13
16
# power2 16
16

I'm not sure we have log available to us from a bash shell script. Do we have round? Not sure.

But I know you are super clever and can pull out an elegant and impressive solution.

Wes Modes
  • 2,024
  • 2
  • 22
  • 40
  • Please add sample input and your desired output for that sample input to your question. – Cyrus Nov 11 '15 at 20:28
  • Bash doesn't have support for non-integer arithmetic, much less transcendental functions, so you can't use logarithms. You could maybe convert to binary, use the bit just after the most significant 1 to decide whether to round up or down, and then turn the rest of the bits to 0.. – Mark Reed Nov 11 '15 at 20:38

4 Answers4

5

Use this function:

power2() { echo "x=l($1)/l(2); scale=0; 2^((x+0.5)/1)" | bc -l; }

Examples

$ power2 11
8
$ power2 12
16
$ power2 13
16
$ power2 16
16
$ power2 63
64

How it works

The echo statement creates a string that bc will interpret as commands. The commands consist of the following:

  • x=l($1)/l(2)

    This sets x to the value of the natural log of the first argument, l($1), divided the natural log of 2, l(2).

  • scale=0

    By setting scale to 0, future divisions will truncate to integer.

  • 2^((x+0.5)/1)

    The expression (x+0.5)/1 rounds x to the nearest integer. We then raise the result of this to the power 2.

John1024
  • 109,961
  • 14
  • 137
  • 171
  • Is scale=0 just doing a floor or is it rounding? – Wes Modes Nov 11 '15 at 20:44
  • @WesModes My first version didn't round. The current one (see updated answer) does. – John1024 Nov 11 '15 at 20:44
  • Nicely done. Good work correcting the rounding problem. And kudos for getting it in a 1 to 3 lines. – Wes Modes Nov 11 '15 at 20:47
  • @WesModes I just added an explanation to the answer. Yes, `scale=0` causes future divisions to floor, not round. As you likely observed, I corrected that by adding `0.5` where needed. – John1024 Nov 11 '15 at 20:49
0

How about this? This method relies on bit shifting until you've reached the last 1 bit, which is in position (or 1-off) of the position that the original number is closest to. No knowledge of bc required, just simple shifting. So bitwise, 1001000 would be closest to 1000000 or 10000000, you merely have to find the closest.

#!/bin/sh
ORIG=$1
A=$1
C=1
while [ $A -ne 1 ]; do
  A=$((A>>1))
  C=$((C<<1))
done
NEXT=$((C<<1))
DIFF1=$((ORIG-C))
DIFF2=$((NEXT-ORIG))
if [  "$DIFF1" -ge "$DIFF2" ]; then
  echo "$NEXT"
else
  echo "$C"
fi
ergonaut
  • 6,929
  • 1
  • 17
  • 47
0

You'll want to use bc. To calculate log base 2 of 17,

X=17

logresult=$( echo "l($x)/l(2)" | bc -l )

[answer=4.08746284125]

Rounding,

roundresult=$( echo "($logresult+0.5)/1" | bc )

[answer=4]

Exponentiation,

echo "2^$roundresult" | bc -l

[answer=16]

Putting those in a bash script,

#!/bin/bash x=$1 logresult=$( echo "l($x)/l(2)" | bc -l ) roundresult=$( echo "($logresult+0.5)/1" | bc ) echo "2^$roundresult" | bc -l

Running this,

./script.sh 17

16

Erik Bryer
  • 96
  • 6
0

Just to add yet another possibility:

power2() {
    local x=${1#-} n=1
    while ((n<x)); do ((n*=2)); done
    x=$((3*n>4*x?n/2:n))
    echo $(($1<0?-x:x))
}

Works with 0 and negative numbers too. Results are unspecified if first parameter is unset or not a number or a number beyond the integer limits (there might be infinite loops). If you use this, make sure you really control the input. Apart from that it's pure Bash. The idea is to find the power of 2 (say n) such that the input (say x) satisfies n/2<x≤n and then find which of n or n/2 is closest to x.

gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104