Permalink
Browse files

Use rational arithmetic instead of bc

Use rational arithmetic throughout to avoid shelling out to bc, which
is slow and apparently nonportable.  Instead of dealing with decimal
numbers like 4.35, we convert 4.35 to 435 / 100 and deal with the
integers 435 and 100.

It is ridiculous, but it is a lot faster.

All the tests pass now.
  • Loading branch information...
mjdominus committed Nov 15, 2011
1 parent f21acf2 commit 0cde7d0009eb0778d26c2c4e7bdbd2771e77115e
Showing with 47 additions and 26 deletions.
  1. +47 −26 spark
View
73 spark
@@ -25,7 +25,6 @@
# # => Prints the spark help text.
set -e
debug='false'
fudge='0.5' # for rounding, when debug mode is not enabled
# Prints the help text for spark.
#
@@ -47,7 +46,7 @@ EOF
# The actual fun characters we are generating in the sparkline.
ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇)
number_of_ticks=$(( ${#ticks[@]} - 1 ))
number_of_tiers=$(( ${#ticks[@]} - 1 ))
# The numbers the user gave us.
numbers=()
@@ -65,45 +64,67 @@ setup_array() {
# convert comma-separated string to array
IFS=,
sorted=($sorted)
sort_min=${sorted[0]}
sort_max=${sorted[${#sorted[@]} - 1]}
tier_size=$(tier_size)
to_rational ${sorted[0]}
min_n=$n
min_d=$d
to_rational ${sorted[${#sorted[@]} - 1]}
max_n=$n
max_d=$d
range_n=$(( max_n * min_d - min_n * max_d ))
range_d=$(( max_d * min_d ))
numbers=($1)
}
# Find the distance between tiers so we know which tick to assign to each input
tier_size()
{
number_of_ticks=${#ticks[@]}
# Mustn't use $((..)) here because we need floating-point arithmetic
distance=$( echo " ($sort_max - $sort_min) / ($number_of_ticks - 1)" | bc -l )
echo $distance
# given an input number which might be a decimal, convert it to
# a rational number; set n and d to its numerator and
# denominator. For example, 3.3 becomes n=33 and d=10;
# 17 becomes n=17 and d=1.
to_rational() {
# Crapulent bash can't handle decimal numbers, so we will convert
# the input number to a rational
if [[ $1 =~ (.*)\.(.*) ]] ; then
i_part=${BASH_REMATCH[1]}
f_part=${BASH_REMATCH[2]}
n="$i_part$f_part";
d=$(( 10 ** ${#f_part} ))
else
n=$1
d=1
fi
}
# Determines what tick we should print for this number and prints it.
#
# Returns nothing.
print_tick()
{
number=$1
# Don't use $((...)) here; we need floating-point arithmetic
tick_index=$( echo " ($number - $sort_min) / $tier_size + $fudge " | bc -l )
# + 0.5 in previous line causes 'truncate' to round off to the nearest integer
if $debug ; then
echo "$number $tick_index"
# The following rigamarole calculates
# ($number - $min) / $tier_size
# using rational arithmetic. This is necessary because $((...)) only
# does integer calculations and because shelling out to bc or dc is
# slow and nonportable
to_rational $1
tick_index_d=$(( range_n * d * min_d ))
tick_index_n=$(( ( n * min_d - min_n * d ) * number_of_tiers * range_d ))
# round to nearest integer: first add 1/2
tick_index_an=$(( tick_index_n * 2 + tick_index_d ))
tick_index_ad=$(( tick_index_d * 2 ))
# divide and truncate
tick_index=$(( tick_index_an / tick_index_ad ))
if $debug; then
echo "$number $tick_index_n / $tick_index_d = $tick_index"
else
tick_index=$(truncate $tick_index)
echo -n ${ticks[$tick_index]};
fi
}
# $((...)) and printf both produce syntax errors on decimal numbers, so
# we will use a stone knife instead
truncate()
{
echo $1 | sed -e 's/\..*$//'
}
# Iterate over all of our data and print them out.
#
# Returns nothing.

0 comments on commit 0cde7d0

Please sign in to comment.