Compare round half up numbers - linux

Compare the round half up values of num1=172 and num2=172.8, where num2's value will be 173.
Print whether or not num1 and num2 are equal.
if (( $(echo "$num1 == $num2" | bc -l) )); then
echo "num1 and num2 are equal"
else
echo "number are not close to each other'
fi

Use https://en.wikipedia.org/wiki/Dynamic_programming . Your problem consist of:
rounding numbers to zero decimal digits
comparing the results
The first part can be found on stackoverflow, like Round a divided number in Bash , the second part can be done with just == even with string comparison.
round() {
printf "%.${2:-0}f" "$1"
}
num1=172
num2=172.8
if (( $(round "$num1") == $(round "$num2") )); then
echo "Equal"
else
echo "Not equal"
fi
The (( arithmetic expression is specific to Bash shell.
You can compare them in bc using the same method. First take a rounding function from
https://github.com/zg/bc/blob/master/code/funcs.bc and then compare the rounded numbers:
if (($(bc -l <<EOF
define int(x) { auto os;os=scale;scale=0;x/=1;scale=os;return(x) }
int($num1) == int($num2)
EOF
) )); then

No bash, no external utils, just pure (ugly) POSIX shell code in two functions:
rhup ()
{
[ "${1##*.[5-9]*}" ]
echo "$((${1%%.*}+$?))"
}
req ()
{
a=
[ "$(rhup "$1")" = "$(rhup "$2")" ] || a="not "
echo "When rounded half up $1 and $2 are ${a}equal."
}
Demo:
req 2 3 ; req 2 2.2 ; req 2.4 2.5
Output:
When rounded half up 2 and 3 are not equal.
When rounded half up 2 and 2.2 are equal.
When rounded half up 2.4 and 2.5 are not equal.
How it works:
Given a number rhup (short for round half up) uses shell parameter substitution to check if a half-up decimal suffix exists. Then it adds the resulting error code of 0 or 1 to the number's integer prefix and prints the sum.
req (short for rhup equal) runs rhup on two numbers, compares them, if they're not equal, sets $a to "not ", then prints the desired conditional English sentence.
Neither function does any error checking on the input values.

Related

How to find the location(s) of specific characters in a string

With the aid of this question, I can find out if a string holds a specific character. I want to be able to find out where the character actually is. For example for the string banana, how would I be able to determine the letter n is the 3rd and 5th letter, or for the letter a is the 2nd,4th and 6th letter. and b is the first letter.
Q: For a given string, how can I find the location of a given character in that string?
You can do it with a for loop.
char=a
string=banana
len=${#string}
for (( i=0; i < len; i++ )); do
if [[ $char == ${string:$i:1} ]]
then echo $i
fi
done
The positions printed are zero-based. You could echo $((i+1)) to get 1-based positions instead.
${string:$i:1} extracts the ith character of the string, using bash's substring operator, as explained in Shell Parameter Expansion:
${parameter:offset:length}
This is referred to as Substring Expansion. It expands to up to length characters of the value of parameter starting at the character specified by offset.
Here's a fancy way to do it:
#!/usr/bin/env bash
findChar(){
string="${1}"
char="${2}"
length=${#string}
offset=0
r=()
while true; do
string="${string#*${char}}"
length_new="${#string}"
if [[ "${length}" == "${length_new}" ]]; then
echo "${r[#]}"
return
fi
offset=($(( $offset + $length - $length_new )))
r+=("${offset}")
length="${length_new}"
done
}
findChar banana b
findChar banana a
Here's my take on this:
#!/usr/bin/env bash
[[ ${BASH_VERSINFO[0]} < 4 ]] && { echo "Requires bash 4."; exit 1; }
string="${1:-banana}"
declare -A result=()
for ((i=0; i<${#string}; i++)); do
result[${string:$i:1}]="${result[${string:$i:1}]} $i"
done
declare -p result
The idea is that we walk through the string, adding character positions to strings that are values in an array whose subscripts are the letters you're interested in. It's quick & easy, and gives you a result set you can manipulate afterwards, rather than just sending things to stdout.
My result with this is:
$ ./foo
declare -A result='([a]=" 1 3 5" [b]=" 0" [n]=" 2 4" )'
$ ./foo barber
declare -A result='([a]=" 1" [b]=" 0 3" [e]=" 4" [r]=" 2 5" )'
Results are zero-based (i.e. "b" is in position 0).
Note an interesting side-effect of this method is that every position is preceded by a space, so if you want to count the number of occurrences of a character, you can just count the spaces:
$ declare -A result
$ result[a]=" 1 3 5"
$ count="${result[a]//[0-9]/}"
$ echo "${#count}"
3
$
I don't know what you're planning to do with this data, but if you like, you could easily turn these string results into arrays of their own for easier handling within bash.
Note that associative arrays were introduced with bash version 4.

calling function in shell script

the user will enter 3 numbers , and I want to calculate these number's roman values. I copy paste to_roman() function but its take 3 arguments . I'm confused . then I call this function in case statement.How can I implement this function for 3 numbers ? any implementation advice ?
to_roman () # Must declare function before first call to it.
{
number=$1
factor=$2
rchar=$3
let "remainder = number - factor"
while [ "$remainder" -ge 0 ]
do
echo -n $rchar
#echo $remainder
let "number -= factor"
let "remainder = number - factor"
done
return $number
}
to_roman $num 10 x
num=$?
to_roman $num 9 ix
num=$?
to_roman $num 5 v
num=$?
to_roman $num 4 iv
num=$?
to_roman $num 1 i
You just read the params in function like $1 $2 $3 and so on.
$# being the total number of parameters
Depending on your implementation you may want to loop over the arguments for n argument support per #EitanReiner suggestion.
It is done with $#. For example:
for param in "$#"
do
echo "$param"
done
In your case it probably would be an overkill since you get 3 parameters only. Anyway, you should check if you got exactly 3 parameters.

Reversing a bash for loop

I have this:
for (( count= "$WP_RANGE_START"; count< "$WP_RANGE_STOP"+1; count=count+1 ));
Where WP_RANGE_STARTis a number like 1 and WP_RANGE_STOPis a number like 10.
Right now this will step though going 1,2,...10
How can I do so that it counts backwards?(10,9,...1)
I guess the mirror image of what you have would be
for (( count="$WP_RANGE_STOP"; count >= "$WP_RANGE_START"; count=count-1 ));
But a less cumbersome way to write it would be
for (( count=WP_RANGE_STOP; count >= WP_RANGE_START; count-- ));
The $ is unnecessary in arithmetic context.
When dealing with literals, bash has a range expansion feature using brace expansion:
for i in {0..10}; # or {10..0} or what have you
But it's cumbersome to use with variables, as the brace expansion happens before parameter expansion. It's usually easier to use arithmetic for loops in those cases.
Your incrementing code can be "simplified" as:
for count in $(eval echo {$WP_RANGE_START..$WP_RANGE_STOP});
So, to decrement you can just reverse the parameters"
for count in $(eval echo {$WP_RANGE_STOP..$WP_RANGE_START});
Assuming you've got a bash version of 3 or higher, you can specify an increment or decrement by appending it to the range, like so:
CHANGE=1
for count in $(eval echo {$WP_RANGE_STOP..$WP_RANGE_START..$CHANGE});
The for loop is your problem.
i=11 ; until [ $((i=i-1)) -lt 1 ] ; do echo $i ; done
OUTPUT
10
9
8
7
6
5
4
3
2
1
You don't need any bashisms at all.

What does an ampersand in arithmetic evaluation and numbers with x's mean in Bash?

I'm curious about what exactly the following comparison does, in as much detail as possible, especially relating to the 0x2 and the & characters and what exactly they do,
if [ $((${nValid} & 0x1)) -eq 1 ]; then
#...snip...
fi
if [ $((${nValid} & 0x2)) -eq 2 ]; then
#...snip...
fi
& is the bitwise AND operator. So you are asking to do a bitwise and between 0x1 and the value that ${nVAlid} is returning.
For more information on bitwise operations look here.
A shell script interprets a number as decimal (base 10), unless that number has a special prefix or notation. A number preceded by a 0 is octal (base 8). A number preceded by 0x is hexadecimal (base 16). A number with an embedded # evaluates as BASE#NUMBER (with range and notational restrictions).
So, in [ $((${nValid} & 0x1)) -eq 1 ], $nValid is anded with 0x1 and compared with decimal 1. Similarly the second comparison too.
Read this and this for detailed info.
It's testing nValid on a per-bit basis.
The bitwise AND operator (&) means that bit-by-bit, the operator will do an AND comparison. So, if nValid is a byte (8 bit) value, then look at the operation in binary:
nValue & 0b00000001
If nValue is 42, then the operation would look like this
(nValue = 0b00101010) & 0b00000001 => 0b00000000 // (does not have the last bit set)
(nValue & 0b00000001) == 0b00000001 // false
and for the 2nd (nValid & 0x2)
(nValue = 0b00101010) & 0b00000010 => 0b00000010 // (has the 2nd-to-last bit set)
(nValue & 0b00000010) == 0b00000010 // true
This is useful for testing flags within variables; usually you use the AND to check for flags by isolating bits and the OR to combine flags.
0b00001000 | 0b00000010 | 0b00000001 => 0b00001011
0x1 and 0x2 are the hexadecimal notations for 1 and 2. The & is the bitwise AND operator. What these lines do is test the value in nValid whether the least significant bit (0x1) and second least significant bit (0x2) are set.
The scheme goes like this (C notation):
if (val & (1 << bitNumber) == (1 << bitNumber)) {
// The bit at position bitNumber (from least to most significant digit) is set
}
The << is the left bitshift operator. 1 << 0 == 1, 1 << 1 == 2, 1 << 2 == 4, ...
So for better readability the lines should be more like:
if [ $((${nValid} & X)) -eq X ]; then
where X is a power of 2 (instead of mixing hexadecimal and decimal notation).
That could be rewritten as:
if (( nValid & 2#00000001 )); then
#...snip...
fi
if (( nValid & 2#00000010 )); then
#...snip...
fi
with the number of binary digits chosen to be most appropriate for the context. It's not necessary to test for equality if you're only checking one bit*. You could still use the hex representation if it makes more sense. The braces and dollar sign aren't necessary in this context.
You might want to use constants with meaningful names instead of hard-coded values:
declare -r FOO=$((2#00000001))
declare -r BAR=$((2#00000010))
if (( nValid & FOO )); then
#...snip...
fi
if (( nValid & BAR )); then
#...snip...
fi
* You will need to test for equality if you're testing multiple bits at the same time:
if (( (nValid & (FOO | BAR)) == (FOO | BAR) )); then
#...snip...
fi
You will need the extra parentheses since == has a higher precedence than the bitwise operators.
Clearing and setting bits in Bash:
(( var |= FOO )) # set the bits in FOO into var
(( var &= ~BAR )) # clear the bits in BAR from var

How to compare two floating-point values in shell script

I had to do a division in shell script and the best way was:
result1=`echo "scale=3; ($var1 / $total) * 100"| bc -l`
result2=`echo "scale=3; ($var2 / $total) * 100"| bc -l`
but I want to compare the values of $result1 and $result2
Using if test $result1 -lt $result2 or if [ $result1 -gt $result2 ] didn't work :(
Any idea how to do that?
You can compare floating-point numbers using expr(1):
: nr#yorkie 3724 ; expr 3.1 '<' 3.3
1
: nr#yorkie 3725 ; expr 3.1 '<' 3.09
0
You can also have bc do the comparisons as well as the calculations:
if [ "$(echo $result1 '<' $result2 | bc -l)" -eq 1 ];then ... fi
Finally, ksh93 can do arithmetic evaluation $(($result1 < $result2)) with floating-point numbers, although bash cannot.
note that you've gotta be a bit careful when dealing with floating point numbers and if you are testing for equality you really want to decide on some precision and then compare using that. Something like:
if (abs(x1-x2) < 0.0001) then equal # pseudo-code
the reason being that with computers we're dealing with limited-precision binary fractions not true mathematical reals. Limiting the precision in bc with the scale=3 will have this effect.
I'd also advise against trying to do this stuff in shell script. It's not that you can't do it but you'll have to fork off lots of little sub commands to do the tricky bits and that's slow to execute and generally a pain to write - you spend most of your time trying to get the shell to do what you want rather than writing the code you really want. Drop into a more sophisticated scripting language instead; my language of choice is perl but there are others. like this...
echo $var1 $var2 $total | perl -ne 'my ($var1, $var2, $tot) = split /\s+/; if ($var1/$tot == $var2/$tot) { print "equal\n"; }'
also note that you're dividing by the same value ($total in your question) so the whole comparison can be done against the numerators (var1 and var2) provided $total is positive
Posting a new answer since I cannot yet comment...
#Norman Ramsey's answer is not quite accurate:
expr will perform an integer or string comparison, not a floating-point comparison.
Here's what the man page says:
expr1 {=, >, >=, <, <=, !=} expr2
Return the results of integer comparison if both arguments are integers; otherwise, returns the results of string comparison using the locale-specific collation sequence.
(just try expr 8.9 '<' 10 and get 0 where it should be 1).
bcworks great, but isn't always installed.
So another alternative is using perl -e:
perl -e 'print expression' will print 1 if expression is true and nothing (empty string) otherwise.
e.g. perl -e 'print 8.9 < 10' - prints "1", while perl -e 'print 2>4' prints nothing.
And when used in if statement:
if [ $(perl -e "print $result1 < $result2") ];then ... fi

Resources