Get ceiling integer from number in linux (BASH) - linux

How would I do something like:
ceiling(N/500)
N representing a number.
But in a linux Bash script

Why use external script languages? You get floor by default. To get ceil, do
$ divide=8; by=3; (( result=(divide+by-1)/by )); echo $result
3
$ divide=9; by=3; (( result=(divide+by-1)/by )); echo $result
3
$ divide=10; by=3; (( result=(divide+by-1)/by )); echo $result
4
$ divide=11; by=3; (( result=(divide+by-1)/by )); echo $result
4
$ divide=12; by=3; (( result=(divide+by-1)/by )); echo $result
4
$ divide=13; by=3; (( result=(divide+by-1)/by )); echo $result
5
....
To take negative numbers into account you can beef it up a bit. Probably cleaner ways out there but for starters
$ divide=-10; by=10; neg=; if [ $divide -lt 0 ]; then (( divide=-divide )); neg=1; fi; (( result=(divide+by-1)/by )); if [ $neg ]; then (( result=-result )); fi; echo $result
-1
$ divide=10; by=10; neg=; if [ $divide -lt 0 ]; then (( divide=-divide )); neg=1; fi; (( result=(divide+by-1)/by )); if [ $neg ]; then (( result=-result )); fi; echo $result
1
(Edited to switch let ... to (( ... )).)

Call out to a scripting language with a ceil function. Given $NUMBER:
python -c "from math import ceil; print ceil($NUMBER/500.0)"
or
perl -w -e "use POSIX; print ceil($NUMBER/500.0), qq{\n}"

Here's a solution using bc (which should be installed just about everywhere):
ceiling_divide() {
ceiling_result=`echo "($1 + $2 - 1)/$2" | bc`
}
Here's another purely in bash:
# Call it with two numbers.
# It has no error checking.
# It places the result in a global since return() will sometimes truncate at 255.
# Short form from comments (thanks: Jonathan Leffler)
ceiling_divide() {
ceiling_result=$((($1+$2-1)/$2))
}
# Long drawn out form.
ceiling_divide() {
# Normal integer divide.
ceiling_result=$(($1/$2))
# If there is any remainder...
if [ $(($1%$2)) -gt 0 ]; then
# rount up to the next integer
ceiling_result=$((ceiling_result + 1))
fi
# debugging
# echo $ceiling_result
}

You can use awk
#!/bin/bash
number="$1"
divisor="$2"
ceiling() {
awk -vnumber="$number" -vdiv="$divisor" '
function ceiling(x){return x%1 ? int(x)+1 : x}
BEGIN{ print ceiling(number/div) }'
}
ceiling
output
$ ./shell.sh 1.234 500
1
Or if there's a choice, you can use a better shell that
does floating point, eg Zsh
integer ceiling_result
ceiling_divide() {
ceiling_result=$(($1/$2))
echo $((ceiling_result+1))
}
ceiling_divide 1.234 500

Expanding a bit on Kalle's great answer, here's the algorithm nicely packed in a function:
ceildiv() {
local num=$1
local div=$2
echo $(( (num + div - 1) / div ))
}
or as a one-liner:
ceildiv(){ echo $((($1+$2-1)/$2)); }
If you want to get fancy, you could use a more robust version validates input to check if they're numerical, also handles negative numbers:
ceildiv() {
local num=${1:-0}
local div=${2:-1}
if ! ((div)); then
return 1
fi
if ((num >= 0)); then
echo $(( (num + div - 1) / div ))
else
echo $(( -(-num + div - 1) / div ))
fi
}
This uses a "fake" ceil for negative numbers, to the highest absolute integer, ie, -10 / 3 = -4 and not -3 as it should, as -3 > -4. If you want a "true" ceil, use $(( num / div )) instead after the else
And then use it like:
$ ceildiv 10 3
4
$ ceildiv 501 500
2
$ ceildiv 0 3
0
$ ceildiv -10 1
-10
$ ceildiv -10 3
-4

Mathematically, the function of ceiling can be define with floor, ceiling(x) = -floor(-x). And, floor is the default when converting a positive float to integer.
if [ $N -gt 0 ]; then expr 1 - $(expr $(expr 1 - $N) / 500); else expr $N / 500; fi
Ref. https://en.wikipedia.org/wiki/Floor_and_ceiling_functions

You can use jq if you have it installed. It's "sed for JSON", but I find it surprisingly handy for simple tasks like this too.
Examples:
$ echo 10.001 | jq '.|ceil'
11
$ jq -n '-10.001 | ceil'
-10

Floor () {
DIVIDEND=${1}
DIVISOR=${2}
RESULT=$(( ( ${DIVIDEND} - ( ${DIVIDEND} % ${DIVISOR}) )/${DIVISOR} ))
echo ${RESULT}
}
R=$( Floor 8 3 )
echo ${R}
Ceiling () {
DIVIDEND=${1}
DIVISOR=${2}
$(( ( ( ${DIVIDEND} - ( ${DIVIDEND} % ${DIVISOR}) )/${DIVISOR} ) + 1 ))
echo ${RESULT}
}
R=$( Ceiling 8 3 )
echo ${R}

If you have a string representation of a decimal number, bash does support ceiling using printf function like this:
$ printf %.4f 0.12345
0.1235
But if you need to do some math using decimals, you can use bc -l that by default scales to 20 decimals, then use the result with printf to round it.
printf %.3f $(echo '(5+50*3/20 + (19*2)/7 )' | bc -l)
17.929

This is a simple solution using Awk:
If you want the ceil of ($a/$b) use
echo "$a $b" | awk '{print int( ($1/$2) + 1 )}'
and the floor use
echo "$a $b" | awk '{print int($1/$2)}'
Note that I just echo the dividend '$a' as the first field of the line to awk and the divisor '$b' as the second.

Some more concise Awk logic
awk '
function ceil(ip) {
print ip%1 ? int(ip)+1 : ip
}
BEGIN {
ceil(1000/500)
ceil(1001/500)
}
'
Result
2
3

This function wont't add 1, if the division returns a non-floating number.
function ceiling {
DIVIDEND=${1}
DIVISOR=${2}
if [ $(( DIVIDEND % DIVISOR )) -gt 0 ]; then
RESULT=$(( ( ( $DIVIDEND - ( $DIVIDEND % $DIVISOR ) ) / $DIVISOR ) + 1 ))
else
RESULT=$(( $DIVIDEND / $DIVISOR ))
fi
echo $RESULT
}
Use it like this:
echo $( ceiling 100 33 )
> 4

Some more concise Awk logic
awk '
function ceil(ip) {
print ip%1 ? int(ip)+1 : ip
}
BEGIN {
ceil(1000/500)
ceil(1001/500)
}
'
Result
2
3

Using the gorgeous 'printf' 1 will round up to the next integer
printf %.0f $float
or
printf %.0f `your calculation formula`
or
printf %.0f $(your calculation formula)
ref: how to remove decimal from a variable?

Without specifying any function, we can use the following awk script:
echo x y | awk '{ r=$1 % $2; q=$1/y; if (r != 0) q=int(q+1); print q}'
Not sure this one get any logical error or not. Please correct.

If you are already familiar with the Python library, then rather than learn bc, you might want to define this bash function:
pc () { pyexpr="from math import *; print($#)"; python -c "$pyexpr"; }
Then:
pc "ceil(3/4)"
1
but also any valid python expression works:
pc pi / 4
0.7853981633974483
pc "'\n'.join(['Pythagoras said that %3.2f^2 + %3.2f^2 is always %3.2f'
% (sin(ai), cos(ai), sin(ai)**2 + cos(ai)**2)
for ai in [pi / 4 * k for k in range(8)]])"
Pythagoras said that 0.00^2 + 1.00^2 is always 1.00
Pythagoras said that 0.71^2 + 0.71^2 is always 1.00
Pythagoras said that 1.00^2 + 0.00^2 is always 1.00
Pythagoras said that 0.71^2 + -0.71^2 is always 1.00
Pythagoras said that 0.00^2 + -1.00^2 is always 1.00
Pythagoras said that -0.71^2 + -0.71^2 is always 1.00
Pythagoras said that -1.00^2 + -0.00^2 is always 1.00
Pythagoras said that -0.71^2 + 0.71^2 is always 1.00

Related

How to cut a number into digits and add them to each other in linux

For example 2211 needs to display 6 but I can't find a command that helps me with it.
I have already tried with cut but that can only cut one at a time.
Works in Debian. Try this:
#!/bin/bash
number="2211"
result=0
for (( i=0; i<${#number}; i++ )); do
echo "number[${i}]=${number:$i:1}"
result=$(( result + ${number:$i:1} ))
done
echo "result = ${result}"
Using a while + read loop.
#!/usr/bin/env bash
str=2211
while IFS= read -rd '' -n1 addend; do
((sum+=addend))
done < <(printf '%s' "$str")
declare -p sum
If your bash is new enough, instead of declare
echo "${sum#A}"
If you have bash version 5.2 (the most recent version to this date):
shopt -s patsub_replacement
n=2211
echo $((${n//?/+&}))
Math variant probably there are more elegant variants
sum=0
num=2211
n=${#num}
for ((i=n-1; i>=0; i--)); {
a=$((10**i))
b=$((num/a))
sum=$((sum+b))
num=$((num-(b*a)))
echo $i $a $b $num $sum
}
3 1000 2 211 2
2 100 2 11 4
1 10 1 1 5
0 1 1 0 6
$ echo $sum
6
Another (Shellcheck-clean) way to do it with arithmetic instead of string manipulation:
#! /bin/bash -p
declare -i num=2211 sum=0
while (( num > 0 )); do
sum+=num%10
num=num/10
done
echo "$sum"

how to find sum of even no till 100 in linux

what should be condition for while before adding while loops it prints even no upto 100 but I need to print sum of even numbers
#!/bin/bash
sum=0
for((n=2;n<=100;n=n+2))
do
echo $n
while [[$n 0]] # what should be condition for while loop
do
sum= `expr sum + $n`
done
echo "sum is $sum "
done
Does this count ^_* :
kent$ seq -s + 2 2 100|bc
2550
k=0;
for i in {1..100}; do
if [[ $(( i % 2 )) == 0 ]]; then
let k=k+i
fi ; done
echo $k
Prints
2550
or this:
k=0; for i in {1..100}; do
if (( i % 2 == 0 )); then
(( k=k+i )) ; fi ; done ; echo $k
You shouldn't have the while loop at all. You're already iterating with the for loop.
#!/bin/bash
sum=0
for((n=2;n<=100;n=n+2))
do
echo $n
((sum+=n))
done
echo "sum is $sum "

How to write script for arithmetic mean of even numbers

I have to write a script in bash where I have to provide 10 numbers to the table. Then script have to write the content out and arithmetic mean of even numbers. I did like 90% of the script, but I can't find out how to extract information about quantity of even numbers that is needed for arithmetic mean.
Here is my code:
echo "Provide data:"
i=0
for (( i = 0 ; i < 10; i++ ))
do
echo "Provide $[$i+1] number:"
read x
if [ "$x" = "" ]
then
break
else
table[$i]=$x
fi
done
echo "Provided data: ${table[*]}"
result=0
for (( i = 0 ; i < 10; i++))
do
res=$[${table[i]}%2]
if [ $res -eq 0 ]
then
echo "Number ${table[i]} is even"
result=$[$result+${table[$i]}]
fi
done
echo "SUM:$[$result]"
ignoring data input adding only odd inputs can look like :
$ cat c.sh
#!/bin/bash
declare -A xDarray
sum=0
xDarray[0 1]=1
xDarray[0 2]=3
xDarray[1 0]=2
xDarray[2 0]=4
for var in ${xDarray[#]}
do
if [ $(( $var & 1 )) == 0 ] ; then
echo $var is even
i+=1
tab[$i]=$var
sum=$(( $sum + $var))
fi
done
var=$(echo ${tab[#]} | sed 's/ / + /g' )
echo $var = $sum
result in
$ ./c.sh
2 is even
4 is even
2 + 4 = 6
$
whatever the number of data is used it would work
I let you work around your data input
Here are some suggested modifications for your script, syntax is simple.
#!/bin/bash
arr=()
for (( i=1;i<=10;i++ )); do
number=''
while [[ ! $number =~ ^[0-9]+$ ]]; do
printf "Please enter number $i:\n"
read number
done
arr+=($number)
done
printf "\nProvided numbers:"
printf " %d" "${arr[#]}"
printf "\nEven numbers:"
s=0
n=0
for x in "${arr[#]}"; do
if ! (( x % 2 )); then
printf " %d" "$x"
s=$(( s + x ))
(( n++ ))
fi
done
m=$(( s / n ))
printf "\nMean of the %d even numbers: %d / %d = %d\n" "$n" "$s" "$n" "$m"
Use an array arr to hold the input numbers, declare with arr=(), append numbers with arr+=($x), we refer only ${arr[#]} for all the items and we avoid any other complex array references, indices etc.
Every input number is tested against regular expression ^[0-9]+$ which means one or more digits (and only digits) with the =~ operator, and if this is not true, we prompt again for the same i-th input number.
Also we prefer printf for printing.
The last loop is the standard array loop, where we use again the arithmetic expansion syntax to find the even numbers, to add them to the sum and get the mean of them (result is an integer).
If you want to print a decimal result, e.g. with 2 floating points, you could use bc and printf "%f" like this:
m=$( bc <<< "scale=2; $s/$n" )
printf "%.2f" "$m"

Bash script: max,min,sum - many sources as parameter

Is it possible to write a script that reads the file containing numbers (one per line) and writes their maximum, minimum and sum. If the file is empty, it will print an appropriate message. The name of the file is to be given as the parameter of the script. I mange to create below script, but there are 2 errors:
./4.3: line 20: syntax error near unexpected token `done'
./4.3: line 20: `done echo "Max: $max" '
Is it possible to add multiple files as parameter?
lines=`cat "$1" | wc -l`
if [ $lines -eq 0 ];
then echo "File $1 is empty!"
exit fi min=`cat "$1" | head -n 1`
max=$min sum=0
while [ $lines -gt 0 ];
do num=`cat "$1" |
tail -n $lines`
if [ $num -gt $max ];
then max=$num
elif [ $num -lt $min ];
then min=$num fiS
sum=$[ $sum + $num] lines=$[ $lines - 1 ]
done echo "Max: $max"
echo "Min: number $min"
echo "Sum: $sum"
Pretty compelling use of GNU datamash here:
read sum min max < <( datamash sum 1 min 1 max 1 < "$1" )
[[ -z $sum ]] && echo "file is empty"
echo "sum=$sum; min=$min; max=$max"
Or, sort and awk:
sort -n "$1" | awk '
NR == 1 { min = $1 }
{ sum += $1 }
END {
if (NR == 0) {
print "file is empty"
} else {
print "min=" min
print "max=" $1
print "sum=" sum
}
}
'
Here's how I'd fix your original attempt, preserving as much of the intent as possible:
#!/usr/bin/env bash
lines=$(wc -l "$1")
if [ "$lines" -eq 0 ]; then
echo "File $1 is empty!"
exit
fi
min=$(head -n 1 "$1")
max=$min
sum=0
while [ "$lines" -gt 0 ]; do
num=$(tail -n "$lines" "$1")
if [ "$num" -gt "$max" ]; then
max=$num
elif [ "$num" -lt "$min" ]; then
min=$num
fi
sum=$(( sum + num ))
lines=$(( lines - 1 ))
done
echo "Max: $max"
echo "Min: number $min"
echo "Sum: $sum"
The dealbreakers were missing linebreaks (can't use exit fi on a single line without ;); other changes are good practice (quoting expansions, useless use of cat), but wouldn't have prevented your script from working; and others are cosmetic (indentation, no backticks).
The overall approach is a massive antipattern, though: you read the whole file for each line being processed.
Here's how I would do it instead:
#!/usr/bin/env bash
for fname in "$#"; do
[[ -s $fname ]] || { echo "file $fname is empty" >&2; continue; }
IFS= read -r min < "$fname"
max=$min
sum=0
while IFS= read -r num; do
(( sum += num ))
(( max = num > max ? num : max ))
(( min = num < min ? num : min ))
done < "$fname"
printf '%s\n' "$fname:" " min: $min" " max: $max" " sum: $sum"
done
This uses the proper way to loop over an input file and utilizes the ternary operator in the arithmetic context.
The outermost for loop loops over all arguments.
You can do the whole thing in one while loop inside a shell script. Here's the bash version:
s=0
while read x; do
if [ ! $mi ]; then
mi=$x
elif [ $mi -gt $x ]; then
mi=$x
fi
if [ ! $ma ]; then
ma=$x
elif [ $ma -lt $x ]; then
ma=$x
fi
s=$((s+x))
done
if [ ! $ma ]; then
echo "File is empty."
else
echo "s=$s, mi=$mi, ma=$ma"
fi
Save that script into a file, and then you can use pipes to send as many input files into it as you wish, like so (assuming the script is called "mysum"):
cat file1 file2 file3 | mysum
or for a single file
mysum < file1
(Make sure, the script is executable and on the $PATH, otherwise use "./mysum" for the script in the current directory or indeed "bash mysum" if it isn't executable.)
The script assumes that the numbers are one per line and that there's nothing else on the line. It gives a message if the input is empty.
How does it work? The "read x" will take input from stdin line-by-line. If the file is empty, the while loop will never be run, and thus variables mi and ma won't be set. So we use this at the end to trigger the appropriate message. Otherwise the loop checks first if the mi and ma variables exist. If they don't, they are initialised with the first x. Otherwise it is checked if the next x requires updating the mi and ma found thus far.
Note that this trick ensures that you can feed-in any sequence of numbers. Otherwise you have to initialise mi with something that's definitely too large and ma with something that's definitely too small - which works until you encounter a strange number list.
Note further, that this works for integers only. If you need to work with floats, then you need to use some other tool than the shell, e.g. awk.
Just for fun, here's the awk version, a one-liner, use as-is or in a script, and it will work with floats, too:
cat file1 file2 file3 | awk 'BEGIN{s=0}; {s+=$1; if(length(mi)==0)mi=$1; if(length(ma)==0)ma=$1; if(mi>$1)mi=$1; if(ma<$1)ma=$1} END{print s, mi, ma}'
or for one file:
awk 'BEGIN{s=0}; {s+=$1; if(length(mi)==0)mi=$1; if(length(ma)==0)ma=$1; if(mi>$1)mi=$1; if(ma<$1)ma=$1} END{print s, mi, ma}' < file1
Downside: if doesn't give a decent error message for an empty file.
a script that reads the file containing numbers (one per line) and writes their maximum, minimum and sum
Bash solution using sort:
<file sort -n | {
read -r sum
echo "Min is $sum"
while read -r num; do
sum=$((sum+num));
done
echo "Max is $num"
echo "Sum is $sum"
}
Let's speed up by using some smart parsing using tee, tr and calculating with bc and if we don't mind using stderr for output. But we could do a little fifo and synchronize tee output. Anyway:
{
<file sort -n |
tee >(echo "Min is $(head -n1)" >&2) >(echo "Max is $(tail -n1)" >&2) |
tr '\n' '+';
echo 0;
} | bc | sed 's/^/Sum is /'
And there is always datamash. The following willl output 3 numbers, being sum, min and max:
<file datamash sum 1 min 1 max 1
You can try with a shell loop and dc
while [ $# -gt 0 ] ; do
dc -f - -e '
['"$1"' is empty]sa
[la p q ]sZ
z 0 =Z
# if file is empty
dd sb sc
# populate max and min with the first value
[d sb]sY
[d lb <Y ]sM
# if max keep it
[d sc]sX
[d lc >X ]sN
# if min keep it
[lM x lN x ld + sd z 0 <B]sB
lB x
# on each line look for max, min and keep the sum
[max for '"$1"' = ] n lb p
[min for '"$1"' = ] n lc p
[sum for '"$1"' = ] n ld p
# print summary at end of each file
' <"$1"
shift
done

Linux bash. for loop and function, for adding numbers

I'm learning bash script's in Linux and I wanted to solve one problem that I thought it would be easy but I just cant figure it out.
I want to insert number as a parameter for example:
sh script.sh 5
And I want to get result 15 if I insert 5 (1+2+3+4+5)=15
I want to solve it with function.
n=$1
result=0
j=0
ADD(){
result=`expr $result + $j`
}
#for (( i=1; i<=$n; i++ ))
for i in {0..$n..1}
do
ADD
j=`expr $j + $1`
done
echo $result
Every time when I want to add number I want to call function for adding.
I have no idea if I even imagined right.
And I don't know how to use for loop's. I've tried two different for loop's and I think they are not working correctly.
Try this:
n=$1
sum=0
for i in `seq 1 $n` ; do
## redefine variable 'sum' after each iteration of for-loop
sum=`expr $sum + $i`
done
echo $sum
With a while loop and similar to your code:
#!/bin/bash
n=$(expr $1 + 1)
result=0
j=0
add(){
result=$(expr $result + $j)
}
while test $j -ne $n
do
add
j=$(expr $j + 1)
done
echo $result
The $(..whatever..) is similar to `..whatever..`, it executes your command and returns the value. The test command is very usefull, check out the man. In this case simulates a for loop comparing the condition $j -ne $n (j not equal n) and adding 1 to the j var in each turn of the loop.
You could try below:
#!/usr/bin/env bash
sumit() {
local n=$1
local sum=0
for (( i=0;i<=n;i++ )) ; do
(( sum = sum + i ))
done
echo "$sum"
}
sum=$(sumit $1)
echo "sum is ($sum)"
A typical way :
function summation()
{
sum=0
for((i=m; i<=n; i++))
{
sum=$((sum+i))
}
echo $sum
}
read m n;
summation $m $n
Here's a tricky version:
sum_to () (
set -- $(seq $1)
IFS=+
echo "$*" | bc
)
sum=$(sum_to 5)
echo $sum # => 15
That's very slow for large numbers though:
$ time sum_to 1000000
500000500000
real 0m2.545s
user 0m2.513s
sys 0m0.189s
More efficient
$ sum_to_2 () { { seq $1 | tr '\n' '+'; echo 0; } | bc; }
$ time sum_to_2 1000000
500000500000
real 0m0.727s
user 0m0.981s
sys 0m0.037s
Better:
$ sum_to_3 () { perl -le '$n=$ARGV[0]; $sum += $n-- while $n; print $sum' $1; }
$ time sum_to_3 1000000
500000500000
real 0m0.075s
user 0m0.071s
sys 0m0.002s
Worser: invoking an "external" program for each number
$ sum_to_slow () {
sum=0
for i in $(seq $1); do
sum=$(expr $sum + $i)
done
echo $sum
}
$ date; time sum_to_slow 1000000; date
Mon Mar 17 14:00:53 EDT 2014
^C
$ date
Mon Mar 17 14:07:01 EDT 2014
I hit Ctrl-C after a few minutes with no answer in sight. At least do the arithmetic in bash
$ sum_to_slow_improved () { sum=0; for i in $(seq $1); do (( sum += i )); done; echo $sum; }
$ time sum_to_slow_improved 1000000
500000500000
real 0m4.937s
user 0m4.869s
sys 0m0.124s
Still 2 orders of magnitude slower than the Perl version

Resources