简体   繁体   中英

Rounding to nearest power of two in bash

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.

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.

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM