Comprehensive Reference Guide — Integer math, floating point with bc/awk, precision control, and number formatting
Bash provides powerful built-in capabilities for arithmetic and floating point operations. This guide covers the essential techniques every Linux system administrator needs to master, from basic calculations to precise floating point math with proper rounding and formatting.
Integer Arithmetic: Bash supports integer arithmetic natively through $(( )), let, and expr. Fast and built-in, but truncates decimals.
Floating Point: For decimal math, use bc (arbitrary precision calculator) or awk (pattern processing with math). Both handle decimals properly.
| Method | Syntax | Pros | Cons |
|---|---|---|---|
| $(( )) | result=$((5 + 3)) |
POSIX, fast, readable | Integer only |
| (( )) | ((result = 5 + 3)) |
Good for conditionals | Bash-specific |
| let | let result=5+3 |
Multiple assignments | No spaces allowed |
| expr | result=$(expr 5 + 3) |
POSIX, portable | Slow, quirky syntax |
| declare -i | declare -i n; n=5+3 |
Auto-evaluates | Variable-specific |
| Category | Operator | Description | Example |
|---|---|---|---|
| Basic | + | Addition | $((5 + 3)) → 8 |
- | Subtraction | $((10 - 4)) → 6 | |
* | Multiplication | $((6 * 7)) → 42 | |
/ | Division (truncates) | $((17 / 5)) → 3 | |
% | Modulo | $((17 % 5)) → 2 | |
| Power | ** | Exponentiation | $((2 ** 10)) → 1024 |
- | Unary minus | $((-5)) → -5 | |
| Increment | ++var | Pre-increment | Increment, then return |
var++ | Post-increment | Return, then increment | |
--var | Pre-decrement | Decrement, then return | |
var-- | Post-decrement | Return, then decrement | |
| Assignment | += | Add assign | ((x += 5)) |
-= | Subtract assign | ((x -= 3)) | |
*= | Multiply assign | ((x *= 2)) | |
/= | Divide assign | ((x /= 4)) | |
%= | Modulo assign | ((x %= 3)) | |
**= | Power assign | ((x **= 2)) | |
| Bitwise | & | AND | $((12 & 10)) → 8 |
| | OR | $((12 | 10)) → 14 | |
^ | XOR | $((12 ^ 10)) → 6 | |
~ | NOT | $((~5)) → -6 | |
<< | Left shift | $((1 << 4)) → 16 | |
>> | Right shift | $((16 >> 2)) → 4 | |
| Comparison | < | Less than | Returns 1 or 0 |
> | Greater than | Returns 1 or 0 | |
<= | Less or equal | Returns 1 or 0 | |
>= | Greater or equal | Returns 1 or 0 | |
== | Equal | Returns 1 or 0 | |
!= | Not equal | Returns 1 or 0 | |
| Logical | && | AND | $((a && b)) |
|| | OR | $((a || b)) | |
! | NOT | $((!a)) | |
| Ternary | ?: | Conditional | $((a > b ? a : b)) |
| Grouping | ( ) | Precedence | $(((a + b) * c)) |
bc is an arbitrary precision calculator. The -l flag loads the math library (trig, log, etc.) and sets default scale to 20.
# Simple calculation
echo "5 / 3" | bc # Returns 1 (integer)
echo "scale=2; 5 / 3" | bc # Returns 1.66
# With math library
echo "scale=4; sqrt(2)" | bc -l # Returns 1.4142
# Multiple lines / here-doc
bc <<EOF
scale=4
a = 3.14159
b = 2.71828
a * b
EOF
scale sets the number of decimal places for division and some functions:
| Scale | Example | Result |
|---|---|---|
| 0 (default) | 10 / 3 | 3 |
| 2 | scale=2; 10 / 3 | 3.33 |
| 6 | scale=6; 10 / 3 | 3.333333 |
| 10 | scale=10; 10 / 3 | 3.3333333333 |
| Function | Description | Example |
|---|---|---|
sqrt(x) | Square root | sqrt(16) → 4 |
s(x) | Sine (radians) | s(0) → 0 |
c(x) | Cosine (radians) | c(0) → 1 |
a(x) | Arctangent | a(1) → 0.785... |
l(x) | Natural log | l(2.718) → ~1 |
e(x) | e^x | e(1) → 2.718... |
j(n,x) | Bessel function | j(0,1) |
awk handles floating point natively and is excellent for processing data with calculations.
# Simple calculation
awk 'BEGIN { print 5 / 3 }' # 1.66667
# With variables
awk -v a=3.14 -v b=2.71 'BEGIN { print a * b }'
# Formatted output
awk 'BEGIN { printf "%.2f\n", 5/3 }' # 1.67
# From shell variables
x=10; y=3
awk -v x="$x" -v y="$y" 'BEGIN { print x / y }'
| Function | Description | Example |
|---|---|---|
sin(x) | Sine (radians) | sin(3.14159/2) |
cos(x) | Cosine | cos(0) |
atan2(y,x) | Arctangent | atan2(1,1) |
exp(x) | e^x | exp(1) |
log(x) | Natural log | log(2.718) |
sqrt(x) | Square root | sqrt(2) |
int(x) | Truncate to int | int(3.9) → 3 |
rand() | Random 0-1 | rand() |
srand() | Seed random | srand() |
| Feature | bc | awk |
|---|---|---|
| Precision | Arbitrary (set scale) | Double precision (~15 digits) |
| Speed | Slower for simple ops | Faster startup |
| Data processing | Calculator only | Full text processing |
| Syntax | Calculator-style | C-like |
| Best for | High precision, financial | Data files, quick calcs |
Core integer operations using $(( )) syntax—the recommended method for all integer math.
#!/bin/bash
# Integer arithmetic fundamentals
a=25
b=7
echo "=== Basic Operations ==="
echo "a = $a, b = $b"
echo "Addition: $a + $b = $((a + b))"
echo "Subtraction: $a - $b = $((a - b))"
echo "Multiplication: $a * $b = $((a * b))"
echo "Division: $a / $b = $((a / b))"
echo "Modulo: $a % $b = $((a % b))"
echo "Exponent: 2 ** 10 = $((2 ** 10))"
echo ""
echo "=== Operator Precedence ==="
echo "5 + 3 * 2 = $((5 + 3 * 2))" # 11 (mult first)
echo "(5 + 3) * 2 = $(((5 + 3) * 2))" # 16 (parens first)
echo "20 / 4 / 2 = $((20 / 4 / 2))" # 2 (left to right)
echo "2 ** 3 ** 2 = $((2 ** 3 ** 2))" # 512 (right to left)
echo ""
echo "=== Compound Expressions ==="
# Quadratic formula component: b² - 4ac
a_coef=1; b_coef=5; c_coef=6
discriminant=$(( b_coef**2 - 4*a_coef*c_coef ))
echo "Discriminant (b²-4ac): $discriminant"
# Average of three numbers (integer)
x=10; y=20; z=35
avg=$(( (x + y + z) / 3 ))
echo "Average of $x, $y, $z: $avg"
echo ""
echo "=== Negative Numbers ==="
echo "-5 + 3 = $((-5 + 3))"
echo "10 / -3 = $((10 / -3))"
echo "-10 / -3 = $((-10 / -3))"
echo "-17 % 5 = $((-17 % 5))" # Sign follows dividend
echo ""
echo "=== Variables Without \$ Inside (( )) ==="
count=10
((count = count + 5)) # No $ needed inside (( ))
echo "count + 5 = $count"
((count += 10))
echo "count += 10 = $count"
25 / 7 = 3 not 3.57. The decimal is discarded, not rounded. For proper division, use bc or awk.
Proper decimal division with precision control using bc.
#!/bin/bash
# Floating point division with bc
echo "=== Basic Division ==="
echo "Integer (bash): 10 / 3 = $((10 / 3))"
echo "Float (bc): 10 / 3 = $(echo "scale=4; 10 / 3" | bc)"
echo ""
echo "=== Scale Examples ==="
for s in 0 2 4 6 10; do
result=$(echo "scale=$s; 22 / 7" | bc)
printf "scale=%2d: 22/7 = %s\n" $s "$result"
done
echo ""
echo "=== Precision in Chained Operations ==="
# Higher internal scale, then round final result
result=$(echo "scale=10; x = 1/3; y = x * 3; scale=2; y/1" | bc)
echo "1/3 * 3 (scale=10, then 2): $result"
echo ""
echo "=== Financial Calculations ==="
# Money should use scale=2
price=19.99
tax_rate=0.08
tax=$(echo "scale=2; $price * $tax_rate" | bc)
total=$(echo "scale=2; $price + $tax" | bc)
echo "Price: \$$price"
echo "Tax (8%): \$$tax"
echo "Total: \$$total"
echo ""
echo "=== Percentage Calculations ==="
total=847
part=523
percent=$(echo "scale=2; ($part / $total) * 100" | bc)
echo "$part of $total = ${percent}%"
# With proper rounding
percent_rounded=$(echo "scale=4; p=($part / $total) * 100; scale=1; p/1" | bc)
echo "Rounded to 1 decimal: ${percent_rounded}%"
echo ""
echo "=== Division Edge Cases ==="
# Very small numbers
echo "1 / 1000000 = $(echo "scale=10; 1/1000000" | bc)"
# Division by near-zero (bc handles gracefully)
echo "1 / 0.0001 = $(echo "scale=2; 1/0.0001" | bc)"
# Leading zeros
echo "0.5 / 2 = $(echo "scale=4; 0.5/2" | bc)"
echo "Note: bc may omit leading zero"
echo ""
echo "=== Comparison: bash vs bc ==="
numerator=100
denominator=7
bash_result=$((numerator / denominator))
bc_result=$(echo "scale=6; $numerator / $denominator" | bc)
echo "Bash: $numerator / $denominator = $bash_result"
echo "bc: $numerator / $denominator = $bc_result"
Different rounding techniques: truncate, round half-up, round half-even (banker's), ceiling, and floor.
#!/bin/bash
# Rounding methods
echo "=== Truncation (Remove Decimals) ==="
# Just set scale=0 after calculation
for val in 3.1 3.5 3.9 -3.1 -3.5 -3.9; do
truncated=$(echo "scale=0; $val / 1" | bc)
printf "%5s truncated = %s\n" $val "$truncated"
done
echo ""
echo "=== Round Half-Up (Standard Rounding) ==="
# Add 0.5 (or 0.05 for 1 decimal) then truncate
round_half_up() {
local value=$1
local decimals=$2
local factor=$(echo "10^$decimals" | bc)
local adjust=$(echo "scale=10; 0.5 / $factor" | bc)
# Handle negative numbers
if (( $(echo "$value < 0" | bc -l) )); then
adjust="-$adjust"
fi
echo "scale=$decimals; ($value + $adjust) / 1" | bc
}
echo "Rounding to 0 decimals:"
for val in 3.4 3.5 3.6 -3.4 -3.5 -3.6; do
rounded=$(round_half_up $val 0)
printf "%5s rounds to %s\n" $val "$rounded"
done
echo ""
echo "Rounding to 2 decimals:"
for val in 3.144 3.145 3.146; do
rounded=$(round_half_up $val 2)
printf "%s rounds to %s\n" $val "$rounded"
done
echo ""
echo "=== Ceiling (Round Up) ==="
ceiling() {
local val=$1
local int_part=$(echo "$val / 1" | bc)
local dec_part=$(echo "$val - $int_part" | bc)
if (( $(echo "$dec_part > 0" | bc -l) )); then
echo "$int_part + 1" | bc
else
echo "$int_part"
fi
}
echo "Ceiling examples:"
for val in 3.0 3.1 3.9 4.0; do
result=$(ceiling $val)
printf "ceil(%s) = %s\n" $val "$result"
done
echo ""
echo "=== Floor (Round Down) ==="
floor() {
echo "$1 / 1" | bc
}
echo "Floor examples:"
for val in 3.0 3.1 3.9 4.0; do
result=$(floor $val)
printf "floor(%s) = %s\n" $val "$result"
done
echo ""
echo "=== Practical: Round Currency ==="
amounts=(19.994 19.995 19.996 20.001 20.009)
for amt in "${amounts[@]}"; do
rounded=$(round_half_up $amt 2)
printf "\$%s → \$%s\n" $amt "$rounded"
done
Working with very large and very small numbers, scientific notation input/output.
#!/bin/bash
# Scientific notation and large numbers
echo "=== bc with Large Numbers ==="
# bc handles arbitrary precision
echo "2^100 = $(echo "2^100" | bc)"
echo ""
echo "Factorial 50:"
echo "scale=0; f=1; for(i=1;i<=50;i++) f*=i; f" | bc
echo ""
echo "=== awk Scientific Notation ==="
# awk handles scientific notation naturally
awk 'BEGIN {
print "1e6 =", 1e6
print "1.5e-3 =", 1.5e-3
print "6.022e23 =", 6.022e23
}'
echo ""
echo "=== Convert to Scientific Notation ==="
to_scientific() {
awk -v n="$1" 'BEGIN { printf "%.3e\n", n }'
}
echo "Large numbers:"
to_scientific 1234567890
to_scientific 0.000000123
echo ""
echo "=== Parse Scientific Notation ==="
# bc doesn't understand 1e6, but awk does
sci_value="3.5e-4"
decimal=$(awk -v x="$sci_value" 'BEGIN { printf "%.10f\n", x }')
echo "$sci_value = $decimal"
echo ""
echo "=== Very Small Numbers ==="
echo "scale=20; 1/10^15" | bc
echo ""
echo "=== Overflow Prevention ==="
# Bash integers overflow around 2^63
echo "Bash max (approx): $((2**62))"
echo "This overflows: $((2**63))"
# bc doesn't overflow
echo "bc 2^100:"
echo "2^100" | bc
echo ""
echo "=== Practical: Byte Calculations ==="
# TB to bytes
tb=5
bytes=$(echo "$tb * 1024^4" | bc)
echo "$tb TB = $bytes bytes"
# Format large number
format_bytes() {
awk -v b="$1" 'BEGIN {
if (b >= 1024^4) printf "%.2f TB\n", b/1024^4
else if (b >= 1024^3) printf "%.2f GB\n", b/1024^3
else if (b >= 1024^2) printf "%.2f MB\n", b/1024^2
else if (b >= 1024) printf "%.2f KB\n", b/1024
else printf "%d B\n", b
}'
}
echo "Formatted: $(format_bytes $bytes)"
echo ""
echo "=== Precision Comparison ==="
# IEEE 754 double vs bc arbitrary
echo "1/3 in awk (double): $(awk 'BEGIN { printf "%.20f\n", 1/3 }')"
echo "1/3 in bc (scale=20): $(echo "scale=20; 1/3" | bc)"
Formatting numbers with leading zeros, decimal alignment, thousands separators, and padding.
#!/bin/bash
# Number formatting and output
echo "=== printf with Integers ==="
num=42
printf "Default: %d\n" $num
printf "Width 8: %8d\n" $num
printf "Left align: %-8d|\n" $num
printf "Zero pad: %08d\n" $num
printf "With sign: %+d\n" $num
echo ""
echo "=== printf with Floats ==="
pi=3.14159265
printf "Default: %f\n" $pi
printf "2 decimals: %.2f\n" $pi
printf "Width 10.2: %10.2f\n" $pi
printf "Zero pad: %010.2f\n" $pi
printf "Scientific: %e\n" $pi
printf "Shorter: %g\n" $pi
echo ""
echo "=== Leading Zeros ==="
for i in 1 10 100; do
printf "ID: %05d\n" $i
done
echo ""
echo "=== Decimal Alignment ==="
printf "%-15s %10s\n" "Item" "Price"
printf "%-15s %10.2f\n" "Widget" 9.99
printf "%-15s %10.2f\n" "Gadget" 149.50
printf "%-15s %10.2f\n" "Thingamajig" 1299.00
echo ""
echo "=== Thousands Separator ==="
# Using printf with apostrophe (locale-dependent)
export LC_NUMERIC="en_US.UTF-8"
printf "With commas: %'d\n" 1234567890
# Manual thousands separator
add_commas() {
echo "$1" | sed ':a;s/\B[0-9]\{3\}\>$/,&/;ta'
}
echo "Manual: $(add_commas 1234567890)"
echo ""
echo "=== Currency Formatting ==="
format_currency() {
printf "\$%'.2f" "$1"
}
echo "$(format_currency 1234.5)"
echo "$(format_currency 1234567.89)"
echo ""
echo "=== Percentage Formatting ==="
format_percent() {
local value=$1
local decimals=${2:-1}
awk -v v="$value" -v d="$decimals" 'BEGIN { printf "%." d "f%%\n", v }'
}
format_percent 75.5
format_percent 99.999 2
format_percent 0.123456 3
echo ""
echo "=== Right-Align Numbers in Report ==="
printf "%-20s %12s %12s %10s\n" "Server" "Requests" "Errors" "Rate"
printf "%-20s %12s %12s %10s\n" "------" "--------" "------" "----"
printf "%-20s %'12d %'12d %9.2f%%\n" "web-prod-01" 1234567 1234 0.10
printf "%-20s %'12d %'12d %9.2f%%\n" "web-prod-02" 987654 567 0.06
printf "%-20s %'12d %'12d %9.2f%%\n" "api-server" 5432100 8765 0.16
Comparing floating point numbers and validating numeric input.
#!/bin/bash
# Floating point comparison and validation
echo "=== Floating Point Comparison ==="
# bc returns 1 for true, 0 for false
compare_float() {
local a=$1 op=$2 b=$3
local result=$(echo "$a $op $b" | bc -l)
if ((result)); then
echo "$a $op $b: TRUE"
else
echo "$a $op $b: FALSE"
fi
}
compare_float 3.14 ">" 3.1
compare_float 2.5 "==" 2.5
compare_float 0.1 "<" 0.2
compare_float 1.0 "!=" 1.00 # Note: bc compares values
echo ""
echo "=== Equality with Tolerance ==="
# Floating point equality should use tolerance
float_equal() {
local a=$1 b=$2 tolerance=${3:-0.0001}
local diff=$(echo "scale=10; d=$a-$b; if(d<0) -d else d" | bc)
local result=$(echo "$diff < $tolerance" | bc -l)
((result))
}
if float_equal 0.1 0.1000001; then
echo "0.1 ≈ 0.1000001 (within tolerance)"
fi
if ! float_equal 0.1 0.2; then
echo "0.1 ≠ 0.2"
fi
echo ""
echo "=== Range Checking ==="
in_range() {
local value=$1 min=$2 max=$3
local result=$(echo "$value >= $min && $value <= $max" | bc -l)
((result))
}
for val in 0 50 100 150; do
if in_range $val 1 100; then
echo "$val is in range [1, 100]"
else
echo "$val is OUT of range [1, 100]"
fi
done
echo ""
echo "=== Input Validation ==="
is_number() {
local input=$1
# Match integer or decimal, optional negative
[[ $input =~ ^-?[0-9]+\.?[0-9]*$ ]]
}
is_integer() {
[[ $1 =~ ^-?[0-9]+$ ]]
}
is_positive() {
local result=$(echo "$1 > 0" | bc -l 2>/dev/null)
[[ $result == "1" ]]
}
test_values=("42" "3.14" "-5" "abc" "12.34.56" "" "1e5")
printf "%-12s %-10s %-10s %-10s\n" "Value" "Number?" "Integer?" "Positive?"
for val in "${test_values[@]}"; do
is_num=$(is_number "$val" && echo "Yes" || echo "No")
is_int=$(is_integer "$val" && echo "Yes" || echo "No")
is_pos=$(is_positive "$val" && echo "Yes" || echo "No")
printf "%-12s %-10s %-10s %-10s\n" "'$val'" "$is_num" "$is_int" "$is_pos"
done
echo ""
echo "=== Practical: Threshold Check ==="
check_threshold() {
local value=$1 warn=$2 crit=$3
if (( $(echo "$value >= $crit" | bc -l) )); then
echo "CRITICAL: $value >= $crit"
return 2
elif (( $(echo "$value >= $warn" | bc -l) )); then
echo "WARNING: $value >= $warn"
return 1
else
echo "OK: $value"
return 0
fi
}
check_threshold 45.5 50 90
check_threshold 75.2 50 90
check_threshold 95.8 50 90
Computing sum, mean, min, max, variance, and standard deviation.
#!/bin/bash
# Statistical calculations
# Sample data
data=(23 45 67 89 12 34 56 78 90 11 44 55 66 77 88)
echo "Data: ${data[*]}"
echo "Count: ${#data[@]}"
echo ""
echo "=== Using awk for Statistics ==="
echo "${data[*]}" | tr ' ' '\n' | awk '
{
sum += $1
sumsq += $1^2
count++
values[NR] = $1
if (NR == 1 || $1 < min) min = $1
if (NR == 1 || $1 > max) max = $1
}
END {
mean = sum / count
variance = (sumsq - sum^2/count) / count
stddev = sqrt(variance)
# Median (sort values first)
n = asort(values)
if (n % 2) {
median = values[(n+1)/2]
} else {
median = (values[n/2] + values[n/2+1]) / 2
}
print "Sum: ", sum
print "Mean: ", mean
print "Min: ", min
print "Max: ", max
print "Range: ", max - min
print "Median: ", median
print "Variance: ", variance
printf "Std Dev: %.4f\n", stddev
}'
echo ""
echo "=== Percentiles ==="
echo "${data[*]}" | tr ' ' '\n' | sort -n | awk '
{
values[NR] = $1
count = NR
}
END {
# Percentile function
function percentile(p) {
idx = p/100 * count
lower = int(idx)
if (lower < 1) lower = 1
if (lower >= count) return values[count]
upper = lower + 1
fraction = idx - lower
return values[lower] + fraction * (values[upper] - values[lower])
}
printf "P25 (Q1): %.2f\n", percentile(25)
printf "P50 (Med): %.2f\n", percentile(50)
printf "P75 (Q3): %.2f\n", percentile(75)
printf "P90: %.2f\n", percentile(90)
printf "P99: %.2f\n", percentile(99)
}'
echo ""
echo "=== Using bc for High Precision ==="
# Sum with bc
sum=0
for val in "${data[@]}"; do
sum=$(echo "$sum + $val" | bc)
done
echo "Sum: $sum"
# Mean with bc
mean=$(echo "scale=4; $sum / ${#data[@]}" | bc)
echo "Mean: $mean"
# Variance with bc
sumsq=0
for val in "${data[@]}"; do
diff=$(echo "scale=10; $val - $mean" | bc)
sq=$(echo "scale=10; $diff * $diff" | bc)
sumsq=$(echo "scale=10; $sumsq + $sq" | bc)
done
variance=$(echo "scale=4; $sumsq / ${#data[@]}" | bc)
stddev=$(echo "scale=4; sqrt($variance)" | bc)
echo "Variance: $variance"
echo "Std Dev: $stddev"
Converting between units: bytes, temperature, distance, and more.
#!/bin/bash
# Unit conversions
echo "=== Byte Conversions ==="
bytes_to_human() {
local bytes=$1
awk -v b="$bytes" 'BEGIN {
units[0]="B"; units[1]="KB"; units[2]="MB"
units[3]="GB"; units[4]="TB"; units[5]="PB"
for (i=5; i>=0; i--) {
limit = 1024^i
if (b >= limit) {
printf "%.2f %s\n", b/limit, units[i]
exit
}
}
printf "%d B\n", b
}'
}
human_to_bytes() {
local input=$1
echo "$input" | awk '{
val = $1
unit = toupper($2)
if (unit == "KB" || unit == "K") mult = 1024
else if (unit == "MB" || unit == "M") mult = 1024^2
else if (unit == "GB" || unit == "G") mult = 1024^3
else if (unit == "TB" || unit == "T") mult = 1024^4
else mult = 1
printf "%.0f\n", val * mult
}'
}
echo "1536 bytes = $(bytes_to_human 1536)"
echo "1048576 bytes = $(bytes_to_human 1048576)"
echo "5368709120 bytes = $(bytes_to_human 5368709120)"
echo ""
echo "1.5 GB = $(human_to_bytes '1.5 GB') bytes"
echo "500 MB = $(human_to_bytes '500 MB') bytes"
echo ""
echo "=== Temperature Conversions ==="
c_to_f() {
echo "scale=1; ($1 * 9/5) + 32" | bc
}
f_to_c() {
echo "scale=1; ($1 - 32) * 5/9" | bc
}
echo "0°C = $(c_to_f 0)°F"
echo "100°C = $(c_to_f 100)°F"
echo "37°C = $(c_to_f 37)°F (body temp)"
echo ""
echo "32°F = $(f_to_c 32)°C"
echo "212°F = $(f_to_c 212)°C"
echo "98.6°F = $(f_to_c 98.6)°C"
echo ""
echo "=== Distance Conversions ==="
km_to_miles() {
echo "scale=2; $1 * 0.621371" | bc
}
miles_to_km() {
echo "scale=2; $1 * 1.60934" | bc
}
echo "100 km = $(km_to_miles 100) miles"
echo "60 miles = $(miles_to_km 60) km"
echo "Marathon (42.195 km) = $(km_to_miles 42.195) miles"
echo ""
echo "=== Time Conversions ==="
seconds_to_hms() {
local secs=$1
local h=$((secs / 3600))
local m=$(((secs % 3600) / 60))
local s=$((secs % 60))
printf "%02d:%02d:%02d" $h $m $s
}
hms_to_seconds() {
IFS=':' read -r h m s <<< "$1"
echo "$((h * 3600 + m * 60 + s))"
}
echo "3661 seconds = $(seconds_to_hms 3661)"
echo "1:30:45 = $(hms_to_seconds "1:30:45") seconds"
echo ""
echo "=== Network Speed ==="
# Mbps to MB/s (divide by 8)
mbps_to_mbs() {
echo "scale=2; $1 / 8" | bc
}
echo "100 Mbps = $(mbps_to_mbs 100) MB/s"
echo "1000 Mbps (Gigabit) = $(mbps_to_mbs 1000) MB/s"
# Transfer time
calc_transfer_time() {
local size_mb=$1
local speed_mbps=$2
local speed_mbs=$(echo "scale=4; $speed_mbps / 8" | bc)
local seconds=$(echo "scale=0; $size_mb / $speed_mbs" | bc)
echo "$(seconds_to_hms $seconds)"
}
echo "1 GB at 100 Mbps: $(calc_transfer_time 1024 100)"
echo "1 GB at 1 Gbps: $(calc_transfer_time 1024 1000)"
Handling division by zero, invalid input, overflow, and other edge cases.
#!/bin/bash
# Error handling and edge cases
echo "=== Division by Zero ==="
# Bash: causes error
set +e # Don't exit on error
result=$((10 / 0)) 2>/dev/null
if [[ $? -ne 0 ]]; then
echo "Bash: Division by zero error (caught)"
fi
# bc: prints error but continues
result=$(echo "10 / 0" | bc 2>&1)
echo "bc: $result"
# Safe division function
safe_divide() {
local num=$1 denom=$2 scale=${3:-2}
if [[ $(echo "$denom == 0" | bc) -eq 1 ]]; then
echo "ERROR: Division by zero"
return 1
fi
echo "scale=$scale; $num / $denom" | bc
}
echo "Safe divide 10/2: $(safe_divide 10 2)"
echo "Safe divide 10/0: $(safe_divide 10 0)"
echo ""
echo "=== Handling Invalid Input ==="
calc() {
local expr=$1
# Validate input (basic check)
if ! [[ $expr =~ ^[0-9+\-*/\.\(\)\ ]+$ ]]; then
echo "ERROR: Invalid characters in expression"
return 1
fi
# Try calculation
result=$(echo "scale=4; $expr" | bc 2>&1)
if [[ $result =~ "error" ]] || [[ -z $result ]]; then
echo "ERROR: Invalid expression"
return 1
fi
echo "$result"
}
echo "calc '2 + 3': $(calc '2 + 3')"
echo "calc '10 / 3': $(calc '10 / 3')"
echo "calc 'abc': $(calc 'abc')"
echo ""
echo "=== Integer Overflow ==="
# Bash uses 64-bit signed integers
max_int=$((2**63 - 1))
echo "Max 64-bit signed: $max_int"
# This overflows
overflow=$((max_int + 1))
echo "Overflow result: $overflow"
# Check before operation
safe_add() {
local a=$1 b=$2
local max=$((2**63 - 1))
if ((a > 0 && b > max - a)); then
echo "ERROR: Would overflow"
return 1
fi
echo $((a + b))
}
echo "Safe add (small): $(safe_add 100 200)"
echo "Safe add (overflow): $(safe_add $max_int 1)"
echo ""
echo "=== Floating Point Precision ==="
# Classic floating point issue
result=$(awk 'BEGIN { print 0.1 + 0.2 }')
echo "0.1 + 0.2 = $result (not exactly 0.3)"
# Compare with tolerance
if (( $(echo "($result - 0.3) < 0.0001 && ($result - 0.3) > -0.0001" | bc -l) )); then
echo "But close enough with tolerance"
fi
# bc is exact for decimal fractions
echo "bc: $(echo "scale=1; 0.1 + 0.2" | bc)"
echo ""
echo "=== Empty/Unset Variables ==="
safe_calc() {
local val=${1:-0} # Default to 0 if empty
echo $((val + 10))
}
echo "With value: $(safe_calc 5)"
echo "Empty: $(safe_calc '')"
echo "Unset: $(safe_calc)"
echo ""
echo "=== Negative Number Edge Cases ==="
# Modulo with negatives
echo "Bash modulo behavior:"
echo " 17 % 5 = $((17 % 5))"
echo " -17 % 5 = $((-17 % 5))"
echo " 17 % -5 = $((17 % -5))"
echo " -17 % -5 = $((-17 % -5))"
# Absolute value
abs() {
local val=$1
if ((val < 0)); then
echo $((-val))
else
echo $val
fi
}
echo "abs(-42) = $(abs -42)"
A reusable math library with integer and floating point functions.
#!/bin/bash
# Complete calculator library
#######################################
# Integer Operations
#######################################
# Safe integer division
int_div() {
local a=$1 b=$2
((b == 0)) && { echo "ERROR"; return 1; }
echo $((a / b))
}
# Greatest common divisor
gcd() {
local a=$1 b=$2
while ((b != 0)); do
local t=$b
b=$((a % b))
a=$t
done
echo $a
}
# Least common multiple
lcm() {
local a=$1 b=$2
echo $(( (a * b) / $(gcd $a $b) ))
}
# Factorial
factorial() {
local n=$1
if ((n <= 1)); then
echo 1
else
echo $((n * $(factorial $((n-1)))))
fi
}
# Is prime
is_prime() {
local n=$1
((n < 2)) && return 1
((n == 2)) && return 0
((n % 2 == 0)) && return 1
local i=3
while ((i * i <= n)); do
((n % i == 0)) && return 1
((i += 2))
done
return 0
}
#######################################
# Floating Point Operations
#######################################
# Add with precision
float_add() {
echo "scale=${3:-6}; $1 + $2" | bc
}
# Subtract with precision
float_sub() {
echo "scale=${3:-6}; $1 - $2" | bc
}
# Multiply with precision
float_mul() {
echo "scale=${3:-6}; $1 * $2" | bc
}
# Divide with precision
float_div() {
local a=$1 b=$2 scale=${3:-6}
if [[ $(echo "$b == 0" | bc) -eq 1 ]]; then
echo "ERROR"
return 1
fi
echo "scale=$scale; $a / $b" | bc
}
# Power
float_pow() {
echo "scale=${3:-6}; e($2 * l($1))" | bc -l
}
# Square root
float_sqrt() {
echo "scale=${2:-6}; sqrt($1)" | bc
}
# Round to N decimals
float_round() {
local val=$1 decimals=${2:-2}
printf "%.${decimals}f" "$val"
}
# Absolute value
float_abs() {
echo "scale=10; if ($1 < 0) -($1) else $1" | bc
}
# Min of two numbers
float_min() {
echo "scale=10; if ($1 < $2) $1 else $2" | bc
}
# Max of two numbers
float_max() {
echo "scale=10; if ($1 > $2) $1 else $2" | bc
}
#######################################
# Demo
#######################################
echo "========================================"
echo " Calculator Library Demo"
echo "========================================"
echo ""
echo "=== Integer Functions ==="
echo "gcd(48, 18) = $(gcd 48 18)"
echo "lcm(4, 6) = $(lcm 4 6)"
echo "factorial(6) = $(factorial 6)"
echo -n "is_prime(17) = "; is_prime 17 && echo "Yes" || echo "No"
echo -n "is_prime(18) = "; is_prime 18 && echo "Yes" || echo "No"
echo ""
echo "=== Floating Point Functions ==="
echo "float_add(3.14, 2.86) = $(float_add 3.14 2.86)"
echo "float_div(22, 7, 10) = $(float_div 22 7 10)"
echo "float_pow(2, 0.5) = $(float_pow 2 0.5)"
echo "float_sqrt(2) = $(float_sqrt 2)"
echo "float_round(3.14159, 2) = $(float_round 3.14159 2)"
echo "float_abs(-42.5) = $(float_abs -42.5)"
echo "float_min(3.14, 2.71) = $(float_min 3.14 2.71)"
echo "float_max(3.14, 2.71) = $(float_max 3.14 2.71)"
echo ""
echo "=== Practical: Loan Payment ==="
# Monthly payment = P * (r(1+r)^n) / ((1+r)^n - 1)
calc_loan_payment() {
local principal=$1
local annual_rate=$2
local months=$3
# Monthly rate
local r=$(echo "scale=10; $annual_rate / 100 / 12" | bc)
# (1+r)^n
local factor=$(echo "scale=10; e($months * l(1 + $r))" | bc -l)
# Payment formula
local payment=$(echo "scale=2; $principal * ($r * $factor) / ($factor - 1)" | bc)
echo $payment
}
principal=200000
rate=6.5
term=360 # 30 years
payment=$(calc_loan_payment $principal $rate $term)
total=$(echo "scale=2; $payment * $term" | bc)
interest=$(echo "scale=2; $total - $principal" | bc)
echo "Loan: \$$(printf "%'d" $principal)"
echo "Rate: ${rate}% for $(($term/12)) years"
echo "Monthly payment: \$$payment"
echo "Total paid: \$$total"
echo "Total interest: \$$interest"
echo ""
echo "========================================"
# Basic operations
result=$((a + b))
result=$((a - b))
result=$((a * b))
result=$((a / b)) # Truncates
result=$((a % b)) # Modulo
result=$((a ** b)) # Power
# Increment/decrement
((count++))
((count--))
((count += 5))
# Conditionals
if ((a > b)); then ...
max=$((a > b ? a : b))
# bc basics
echo "scale=2; 10 / 3" | bc # 3.33
echo "scale=6; sqrt(2)" | bc -l # 1.414213
# awk basics
awk 'BEGIN { print 10 / 3 }' # 3.33333
awk 'BEGIN { printf "%.2f\n", 10/3 }' # 3.33
# Shell variable in bc
result=$(echo "scale=2; $a / $b" | bc)
# Shell variable in awk
result=$(awk -v a="$a" -v b="$b" 'BEGIN { print a/b }')
| Task | Code |
|---|---|
| Percentage | echo "scale=2; ($part/$total)*100" | bc |
| Round to 2 decimals | printf "%.2f" "$value" |
| Check if float > value | (( $(echo "$a > $b" | bc -l) )) |
| Absolute value | echo "if ($x<0) -$x else $x" | bc |
| Min of two | $((a < b ? a : b)) |
| Max of two | $((a > b ? a : b)) |
$(( )) for integer math—fast and readablebc for precision-critical floating pointawk for data processing with mathscale in bc for division