bc truncate floating point number - linux

How do I truncate a floating point number using bc
e.g if I do
echo '4.2-1.3' | bc
which outputs 2.9 how I get it to truncate/use floor to get 2

Use / operator.
echo '(4.2-1.3) / 1' | bc

Dividing by 1 works ok if scale is 0 (eg, if you start bc with bc and don't change scale) but fails if scale is positive (eg, if you start bc with bc -l or increase scale). (See transcript below.) For a general solution, use a trunc function like the following:
define trunc(x) { auto s; s=scale; scale=0; x=x/1; scale=s; return x }
Transcript that illustrates how divide by 1 by itself fails in the bc -l case, but how trunc function works ok at truncating toward zero:
> bc -l
bc 1.06.95
[etc...]
for (x=-4; x<4; x+=l(2)) { print x,"\t",x/1,"\n"}
-4 -4.00000000000000000000
-3.30685281944005469059 -3.30685281944005469059
-2.61370563888010938118 -2.61370563888010938118
-1.92055845832016407177 -1.92055845832016407177
-1.22741127776021876236 -1.22741127776021876236
-.53426409720027345295 -.53426409720027345295
.15888308335967185646 .15888308335967185646
.85203026391961716587 .85203026391961716587
1.54517744447956247528 1.54517744447956247528
2.23832462503950778469 2.23832462503950778469
2.93147180559945309410 2.93147180559945309410
3.62461898615939840351 3.62461898615939840351
define trunc(x) { auto s; s=scale; scale=0; x=x/1; scale=s; return x }
for (x=-4; x<4; x+=l(2)) { print x,"\t",trunc(x),"\n"}
-4 -4
-3.30685281944005469059 -3
-2.61370563888010938118 -2
-1.92055845832016407177 -1
-1.22741127776021876236 -1
-.53426409720027345295 0
.15888308335967185646 0
.85203026391961716587 0
1.54517744447956247528 1
2.23832462503950778469 2
2.93147180559945309410 2
3.62461898615939840351 3

Try the following solution. It will truncate anything after the decimal point without a problem:
echo 'x = 4.2 - 1.3; scale = 0; x / 1' | bc -l
echo 'x = l(101) / l(10); scale = 0; x / 1' | bc -l
You can make the code a tad shorter by performing calculations directly on the numbers:
echo 'scale = 0; (4.2 - 1.3) / 1' | bc -l
echo 'scale = 0; (l(101) / l(10)) / 1' | bc -l
In general, you can use this function to get only the integer part of a number:
define int(x) {
auto s;
s = scale;
scale = 0;
x /= 1; /* This will have the effect of truncating x to its integer value */
scale = s;
return (x);
}
Save that code into a file (let's call it int.bc) and run the following command:
echo 'int(4.2 - 1.3);' | bc -l int.bc

The variable governing the amount of decimals on division is scale.
So, if scale is 0 (the default), dividing by 1 would truncate to 0 decimals:
$ echo '(4.2-1.3) / 1 ' | bc
2
In other operations, the number of decimals is calculated from the scale (number of decimals) of each operand. In add, subtract and multiplication, for example, the resulting scale is the biggest of both:
$ echo ' 4.2 - 1.33333333 ' | bc
2.86666667
$ echo ' 4.2 - 1.333333333333333333 ' | bc
2.866666666666666667
$ echo ' 4.2000 * 1.33 ' | bc
5.5860
Instead, in division, the number of decimals is strictly equal to th evalue of the variable scale:
$ echo 'scale=0;4/3;scale=3;4/3;scale=10;4/3' | bc
1
1.333
1.3333333333
As the value of scale has to be restored, it is better to define a function (GNU syntax):
$ echo ' define int(x){ os=scale;scale=0;x=x/1;scale=os;return(x) }
int( 4.2-1.3 )' | bc
2
Or in older POSIX language:
$ echo ' define i(x){
o=scale;scale=0;x=x/1;scale=o;return(x)
}
i( 4.2-1.3 )' | bc
2

You say:
truncate/use floor
And those are not the same thing in all cases. The other answers so far only show you how to truncate (i.e. "truncate towards zero" i.e. "discard the part after the decimal").
For negative numbers, the behavior is different.
To wit:
truncate(-2.5) = -2
floor(-2.5) = -3
So, here is a floor function for bc:
# Note: trunc(x) is defined as noted elsewhere in the other answers
define floor(x) {
auto t
t=trunc(x)
if (t>x) {
return t-1
} else {
return t
}
}
Aside:
You can put this, and other helper functions, in a file. For instance, I have this alias in my shell:
alias bc='bc -l ~/.bcinit'
And so whenever I run bc, I get all my utility functions from ~/.bcinit available by default.
Also, there is a good list of bc functions here: http://phodd.net/gnu-bc/code/funcs.bc

You may do something like this:
$ printf "%.2f\n" $(echo "(4530 / 4116 - 1) * 100" | bc -l)
10.06
Here I am trying to find the % change. Not purely bc though.

Related

Time difference in shell (hour)

I'm trying to calculate time difference stored inside of two variables inside of a shell script, I'm observing the following pattern:
hhmm -> 0950
so:
time1=1333
time2=0950
Now I need to calculate the difference in time between time1 and time2, as for now I have tried:
deltaTime=$(($time1-$time2))
but I'm facing the following error message
1333-0950: value too great for base (error token is "0950")
I'm expecting as a result: $deltaTime=0343
Unfortunately, I am strictly bound to use this time pattern. I have already researched for a solution online, some of them propose to use date -d... but I couldn't get it to work :(
Your approach has two issues.
First issue: bash recognizes numbers with leading zeroes as octal. You can force base10 by adding 10# prefix.
Second issue: it is incorrect to consider strings in hhmm format as numbers and substract them. e.g. 1333-950=383 but difference between 09:50 and 13:33 is 3 hours and 43 minutes. You should convert string values to common units, e.g. to minutes, substract them and convert back to hhmm format.
time1=1333
time2=0950
str2min()
{
printf "%u" $((10#${1%??} * 60 + 10#${1#??}))
}
min2str()
{
printf "%02u%02u" $(($1 / 60)) $(($1 % 60))
}
time1m=$(str2min $time1)
time2m=$(str2min $time2)
timediff=$(($time1m - $time2m))
deltaTime=$(min2str $timediff)
You could use this implementation maybe?
#!/usr/bin/env bash
diff_hhmm() {
local -r from=$1
local -i from_hh=10#${from:0:2} # skip 0 chars, read 2 chars (`${from:0:2}`) using base 10 (`10#`)
local -ri from_mm=10#${from:2:2} # skip 2 chars, read 2 chars (`${from:0:2}`) using base 10 (`10#`)
local -r upto=$2
local -ri upto_hh=10#${upto:0:2}
local -ri upto_mm=10#${upto:2:2}
local -i diff_hh
local -i diff_mm
# Compute difference in minutes
(( diff_mm = from_mm - upto_mm ))
# If it's negative, we've "breached" into the previous hour, so adjust
# the `diff_mm` value to be modulo 60 and compensate the `from_hh` var
# to reflect that we've already subtracted some of the minutes there.
if (( diff_mm < 0 )); then
(( diff_mm += 60 ))
(( from_hh -= 1 ))
fi
# Compute difference in hours
(( diff_hh = from_hh - upto_hh ))
# Ensure the result is modulo 24, the number of hours in a day.
if (( diff_hh < 0 )); then
(( diff_hh += 24 ))
fi
# Print the values with 0-padding if necessary.
printf '%02d%02d\n' "$diff_hh" "$diff_mm"
}
$ diff_hhmm 1333 0950
0343
$ diff_hhmm 0733 0950
2143
$ diff_hhmm 0733 0930
2203
Or an even shorter implementation using a big arithmetic compound command ((( ... )) ) and inlining some variables:
diff_hhmm_terse() {
local -i diff_hh diff_mm
((
diff_mm = 10#${1:2:2} - 10#${2:2:2},
diff_hh = 10#${1:0:2} - 10#${2:0:2},
diff_hh -= diff_mm < 0 ? 1 : 0,
diff_mm += diff_mm < 0 ? 60 : 0,
diff_hh += diff_hh < 0 ? 24 : 0
))
printf '%02d%02d\n' "$diff_hh" "$diff_mm"
}
Do you have the possibility to drop the leading zero?
As you can see from my prompt:
Prompt> echo $((1333-0950))
-bash: 1333-0950: value too great for base (error token is "0950")
Prompt> echo $((1333-950))
383
Other proposal:
date '+%s'
Let me give you some examples:
date '+%s'
1662357975
... (after some time)
date '+%s'
1662458180
=>
echo $((1662458180-1662357975))
100205 (amount of seconds)
=>
echo $(((1662458180-1662357975)/3600))
27 (amount of hours)
This bash one-liner may be used if time difference is not negative (that is, time1 >= time2):
printf '%04d\n' $(( 10#$time1 - 10#$time2 - (10#${time1: -2} < 10#${time2: -2} ? 40 : 0) ))

Compare round half up numbers

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.

Do math on CSH or SH only with string variables

I have string variables MIN and SEC (minute and seconds).
MIN = "1"
SEC = "34"
I want to do calculations on these.
TOTSEC = MIN*60 + SEC
I tried:
expr $SEC + $MIN * 60
Result:
expr: non-numeric argument
Let it be known I am running busybox on a custom microcomputer and so have no access to bash,bc, and that other solution provides.
In sh, by which I'll assume you mean a POSIX shell, your best option is to use Arithmetic Expansion:
$ MIN=1
$ SEC=34
$ TOTSEC=$(( MIN * 60 + SEC ))
$ printf '%d\n' "$TOTSEC"
94
In csh however, the built-in math works quite differently:
% set MIN = 1
% set SEC = 34
% # TOTSEC = ( $MIN * 60 + $SEC )
% printf '%d\n' "$TOTSEC"
94
According to the man page, the # command permits numeric calculations to be performed and the result assigned to a variable.
Note that the expr command is external to the shell, so it should be usable from either one.
In sh:
$ TOTSEC=$(expr "$MIN" \* 60 + "$SEC")
And in csh:
% set TOTSEC = `expr "$MIN" \* 60 + "$SEC"`
Note: your sh may not be POSIX compliant. Most likely, it's ash, which is the ancestor of dash and FreeBSD's /bin/sh. You'll need to test in your environment.

Decimal values in bash script becoming all zeros

This is my first bash script and have ran into a little problem with setting the decimal precision. I have been tasked to create a bash script that calculates the area and circumference of a circle given the diameter of 20. This is what I currently have
#!/bin/bash
clear
diameter=$1 # storing first argument
radius=$(echo "scale=5;$diameter / 2" | bc) # setting radius
# echo "$radius"
# calculate area of a circle
area=$(echo "scale=5;3.14 * ($radius * $radius)" | bc -l) # A = pi(r^2)
# calculate circumference of a circle
circum=$(echo "scale=5;2 * 3.14 * ($radius)" | bc -l) # C = 2(pi)(r)
echo "Circumference: $circum"
echo "Area: $area"
When I run the script it prints out
Circumference: 62.80000
Area: 314.00000
It should be printing out
Circumference: 62.83185
Area: 314.15926
I am not understand why it is not displaying the correct decimal values. I have given the scale=5 to display five decimal places which it is doing. I am confused why the zeros are showing up and not the true decimal values. Any help or suggestions would be greatly appreciated.
If pi were equal to 3.14, then your code is giving exact results.
To get the exact value of pi in bc code, use 4*a(1):
# calculate area of a circle
area=$(echo "scale=5;4*a(1) * ($radius * $radius)" | bc -l) # A = pi(r^2)
# calculate circumference of a circle
circum=$(echo "scale=5;2 * 4*a(1) * ($radius)" | bc -l) # C = 2(pi)(r)
(This works because a(1) is the arctangent of 1 which is pi/4.)
With those changes, a sample run looks like:
$ bash circ.sh 20
Circumference: 62.83120
Area: 314.15600
The limited precision now is due to the choice of scale=5.
Still better precision
The code below keeps the best precision around until it is time to print and then prints in your desired precision:
#!/bin/bash
diameter=$1 # storing first argument
radius=$(echo "$diameter/2" | bc -l) # setting radius
# calculate area of a circle
area=$(echo "4*a(1)*$radius^2" | bc -l) # A = pi(r^2)
# calculate circumference of a circle
circum=$(echo "2 * 4*a(1) * $radius" | bc -l) # C = 2(pi)(r)
printf "Circumference: %.5f\n" "$circum"
printf "Area: %.5f\n" "$area"
Example:
$ bash circ.sh 20
Circumference: 62.83185
Area: 314.15927

make an ascii move in linux shell

How can i make a train ascii to look like it's moving in the linux shell from right to left?
_-====-__-____-============-__
_( _)
OO( )_
0 (_ _)
o0 (_ _)
o `=-___-===-_____-========-__)
.o _________
. ______ ______________ | | _____
_()_||__|| ________ | | |_________| __||___||__
( | | | | | |Y_____00_| |_ _|
/-OO----OO**=*OO--OO*=*OO--------OO*=*OO-------OO*=*OO-------OO*=P
You could install the linux command 'sl' if you want trains running over your screen.
http://www.cyberciti.biz/tips/displays-animations-when-accidentally-you-type-sl-instead-of-ls.html
You should take a look at the ANSI Escape Codes, especially at moving the cursor and clearing the screen. For a quick start you can just clear \e[2J the entire screen and redraw everything.
Example:
#include <iostream>
using namespace std;
/* ASCII control character for ESCAPE (ESC) is "\e"
* Alternatives: Oct 033, Dez 27, Hex 1B
*
* The '\e' escape sequence is not part of ISO C and many other language
* specifications. However, it is understood by several compilers.
* The Escape character can also be entered by pressing the "Escape" or
"Esc"
* key on some systems.
*/
int main() {
cout << "\e[35m" << "Purple\n" << "\e[m";
// cout << "\e[2J\e[H" << "\e[35mPlease enter\e[m: \n";
cout << "foo" << '\x2B' << "\n";
return 0;
}
Or you use the library ncurses from GNU.
EDIT:
You can use pr -tro width, original solution with expand beneath :
Put your ascii image in a file (train.txt), but remove the first spaces (that all lines have in common).
i=0
while [ $i -lt 20 ]; do
clear
cat train.txt | pr -tro $i
sleep 1
(( i = i + 1 ))
done
Solution with expand:
Put your ascii image in a file (train.txt), but replace the first spaces (that all lines have in common) by 1 tab.
i=0
while [ $i -lt 20 ]; do
clear
cat train.txt | expand -$i
sleep 1
(( i = i + 1 ))
done
Alternative (oneliner image): use \r
Alternative for expand: use printf with a width in the formatstring.
Moving to the left: start with i=20 and use -gt 0 and (( i = i - 1 ))

Resources