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. # # => Prints the spark help text.
set -e set -e
debug='false' debug='false'
fudge='0.5' # for rounding, when debug mode is not enabled
# Prints the help text for spark. # Prints the help text for spark.
# #
@@ -47,7 +46,7 @@ EOF
# The actual fun characters we are generating in the sparkline. # The actual fun characters we are generating in the sparkline.
ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇) ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇)
number_of_ticks=$(( ${#ticks[@]} - 1 )) number_of_tiers=$(( ${#ticks[@]} - 1 ))
# The numbers the user gave us. # The numbers the user gave us.
numbers=() numbers=()
@@ -65,45 +64,67 @@ setup_array() {
# convert comma-separated string to array # convert comma-separated string to array
IFS=, IFS=,
sorted=($sorted) sorted=($sorted)
sort_min=${sorted[0]}
sort_max=${sorted[${#sorted[@]} - 1]} to_rational ${sorted[0]}
tier_size=$(tier_size) 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) numbers=($1)
} }
# Find the distance between tiers so we know which tick to assign to each input # given an input number which might be a decimal, convert it to
tier_size() # a rational number; set n and d to its numerator and
{ # denominator. For example, 3.3 becomes n=33 and d=10;
number_of_ticks=${#ticks[@]} # 17 becomes n=17 and d=1.
# Mustn't use $((..)) here because we need floating-point arithmetic to_rational() {
distance=$( echo " ($sort_max - $sort_min) / ($number_of_ticks - 1)" | bc -l ) # Crapulent bash can't handle decimal numbers, so we will convert
echo $distance # 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. # Determines what tick we should print for this number and prints it.
# #
# Returns nothing. # Returns nothing.
print_tick() print_tick()
{ {
number=$1 # The following rigamarole calculates
# Don't use $((...)) here; we need floating-point arithmetic # ($number - $min) / $tier_size
tick_index=$( echo " ($number - $sort_min) / $tier_size + $fudge " | bc -l ) # using rational arithmetic. This is necessary because $((...)) only
# + 0.5 in previous line causes 'truncate' to round off to the nearest integer # does integer calculations and because shelling out to bc or dc is
if $debug ; then # slow and nonportable
echo "$number $tick_index" 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 else
tick_index=$(truncate $tick_index)
echo -n ${ticks[$tick_index]}; echo -n ${ticks[$tick_index]};
fi 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. # Iterate over all of our data and print them out.
# #
# Returns nothing. # Returns nothing.

0 comments on commit 0cde7d0

Please sign in to comment.