LinuxQuestions.org Member Success StoriesJust spent four hours configuring your favorite program? Just figured out a Linux problem that has been stumping you for months?
Post your Linux Success Stories here.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
The method is "Round-to-even", aka unbiased rounding, convergent rounding, statistician's rounding, Dutch rounding, Gaussian rounding, or bankers' rounding.
I was very strict in following the logic given in the Wikipedia article. This means that I intentionally avoided certain programming structures/styles/optimizations. It also seems to make the code easier to read, adapt or change.
I added in additional error-checking, and I decided to do a lot more work to deal with some technically-valid but unusual cases.
I also ended up doing another little test function. I do believe there are some bash tools which can do this sort of thing, but I have no intention of continuing with bash much further. This was a fun but really strange experience.
As an aside, this code also happens to append additional zeros if it needs to.
replace_character() {
unset searchstring_success
until [ "sky" = "falling" ]; do
# 2 parameters, no blanks, first parameter must be one character.
if [ ! "$#" -eq 3 ] || [ "$1" = "" ] || [ "$2" = "" ] || [ "$3" = "" ] || [ `expr length $1` -gt 1 ]; then echo "Needs three parameters: a character, a string and a position"; break ; fi
expr $3 + 1 &> /dev/null ; result=$?
if [ $result -ne 0 ]; then echo $3 is not a number. ; break ; fi
character="$1"
string="$2"
position="$3"
length=${#string}
i=0
unset newstring
until [ $i -eq $length ]; do
if [ $i -eq $position ]; then
newstring=$newstring$character
else
newstring=$newstring${string:$i:1}
fi
((i++))
done
echo $newstring
break
done
}
# TODO: deal with "+" or "-" characters. Should be easy.
round() {
until [ "sky" = "falling" ]; do
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [ "$1" = "" ]; then echo "Needs one or two parameters: a number and an optional position"; break ; fi
# Check $1
# If I've only been given a number, then what the heck am I being called upon to do? Bail out.
if [[ "$1" =~ '^([0-9]+)$' ]]; then echo $1 ; break ; fi
if [[ "$1" =~ '^([0-9]+\.)$' ]]; then
left=${1%.*}
# FIXME: This coding cannot deal with a bad $2 ($rounding_digit_location) since that checking is done later on!!
number=`for i in {1..${#2}}; do printf 0; done`
# ".123" => "0.123"
elif [[ "$1" =~ '^(\.[0-9]+)$' ]]; then
left=0
number=${1#*.}
# If it's a number in the form of nnn.nnn
elif [[ "$1" =~ '^([0-9]+\.[0-9]+)$' ]]; then
left=${1%.*}
number=${1#*.}
else
echo $1 is not a number. ; break
fi
# Check $2
if [ "$2" = "" ]; then
# By default round to this many digits
rounding_digit_location=0
else
if [[ ! "$2" =~ '^([0-9]+)$' ]]; then echo $2 is not a number. ; break ; fi
rounding_digit_location=$2
# If the rounding digit is longer than what's available to work with, make it the maximum length.
if [ "$rounding_digit_location" -gt ${#number} ]; then rounding_digit_location=${#number} ; fi
fi
# If I've been given a boring number, don't even bother to do rounding
if [ $number = 0 ]; then
# there a much better way to do this with some kind of {} thing, but I can't remember where I saw that note.
until [ $rounding_digit_location -eq -1 ]; do
final=$final"0"
((rounding_digit_location--))
done
echo $left"."$final
break
fi
# Above is my error and hedge-case programming.
# Now we begin the actual code.
# It was translated as literally as possible from the Wikipedia English explanation.
# Their original notes are included next to each ##
## Decide which is the last digit to keep.
# Convert it to a count-from-zero
((rounding_digit_location--))
item=${number:$rounding_digit_location:1}
## Increase it by 1 if the next digit is 6 or more, or a 5 followed by one or more non-zero digits.
## ... the next digit
item_after=${number:$(( $rounding_digit_location + 1 )):1}
if [ "$item_after" = "" ]; then item_after=0 ; fi
## ... is 6 or more
if [ "$item_after" -ge 6 ]; then
result=$(( $item + 1 ))
fi
## ... or a 5 followed by one or more non-zero digits.
if [ $item_after -eq 5 ]; then
## ... followed by one or more non-zero digits
string_after=${number:$(( $rounding_digit_location + 2))}
# and if I run past the edge of the string, it's 0
if [ "$string_after" = "" ]; then string_after=0 ; fi
if [ $string_after -gt 0 ]; then
result=$(( $item + 1 ))
fi
fi
## Leave it the same if the next digit is 4 or less
if [ $item_after -le 4 ]; then result=$item ; fi
## Otherwise, if all that follows the last digit is a 5 and possibly trailing zeroes; then increase the rounded digit if it is currently odd; else, if it is already even, leave it alone.
if [ $item_after -eq 5 ]; then
## ... followed by one or more zero digits
string_after=${number:$(( $rounding_digit_location + 2))}
# and if I run past the edge of the string, it's 0
if [ "$string_after" = "" ]; then string_after=0 ; fi
if [ $string_after -eq 0 ]; then
if [ $(( $item % 2 )) -ne 0 ]; then
# ... then increase the rounded digit if it is currently odd
result=$(( $item + 1 ))
else
# ... else, if it is already even, leave it alone.
result=$item
fi
fi
fi
# take the rounded digit and slap it overtop of the original:
final=`replace_character $result $number $rounding_digit_location`
# Truncate everything past the rounded digit:
truncate=${final:$(( $rounding_digit_location + 1 ))}
final=${final%$truncate*}
if [ "$left" = "" ]; then
echo $final
else
if [ "$final" = "" ]; then
# If I was given nnn.nnn but rounded to 0 digits:
echo $left
else
# If I was given nnn.nnn:
echo $left"."$final
fi
fi
break
done
}
# --------------
# Testing
# --------------
round_test() {
result=`round $1 $2`
expected=$3
if [ "$result" = "$expected" ]; then printf pass ; else ((fail_count++)) ; printf fail - got $result ; fi
echo " - $1 ($2) => $3"
}
# Cases taken from http://en.wikipedia.org/wiki/Rounding#Round-to-even_method
round_test_cases() {
fail_count=0
echo " Wikipedia cases"
# 3.016 rounded to hundredths is 3.02 (because the next digit (6) is 6 or more)
round_test 3.016 2 3.02
# 3.013 rounded to hundredths is 3.01 (because the next digit (3) is 4 or less)
round_test 3.013 2 3.01
# 3.015 rounded to hundredths is 3.02 (because the next digit is 5, and the hundredths digit (1) is odd)
round_test 3.015 2 3.02
# 3.045 rounded to hundredths is 3.04 (because the next digit is 5, and the hundredths digit (4) is even)
round_test 3.045 2 3.04
# 3.04501 rounded to hundredths is 3.05 (because the next digit is 5, but it is followed by non-zero digits)
round_test 3.04501 2 3.05
echo " My cases"
round_test 1 "" 1
round_test 2 0 2
round_test 3.00 "" 3
round_test .44 4 0.44 # careful: this should not become 0.4400
round_test 5.0000 1 5.0
round_test 6.0000 2 6.00
round_test 7. 2 7.00
if [ $fail_count = 1 ]; then
echo $fail_count failure
else
echo $fail_count failures
fi
}
Last edited by anonguy9; 03-29-2009 at 01:38 AM.
Reason: minor update
for i in 3.016 3.013 3.015 3.045 3.04501
> do
> printf "pass - %f (%1d) => %4.2f\n" $i 2 $i
> done
Moreover, please take in mind that a decimal digit of 5 has to be rounded to the upper integer: 3.045 rounds up to 3.05, not down to 3.04 as in the Wikipedia example.
I do notice that printf is part of awk/gawk. I wanted to make a bash-only solution. I didn't realise printf was an external command. I guess I should stick with echo -n from now on.
Quote:
Originally Posted by colucix
Moreover, please take in mind that a decimal digit of 5 has to be rounded to the upper integer: 3.045 rounds up to 3.05, not down to 3.04 as in the Wikipedia example.
This is how I learned things too, but Apparently if the digit to the right of the target digit is a five there are special rules. The target digit only increments if it would become even. I even implemented it the way I knew from memory, but I scrapped all of that to do it the way I read. It was also an exercise in translating an English procedure into pseudocode and then real code.
This was more of a learning experience anyway.. I'm not going to put the code to significant (if any) use. I wouldn't want to be pedantic and implement all the variations, even though that would be a simple side project.
I do notice that printf is part of awk/gawk. I wanted to make a bash-only solution. I didn't realise printf was an external command. I guess I should stick with echo -n from now on.
You're right. However printf (different from the awk counterpart) is either a shell built-in and an external program. For pure bash programming you have to stick with the shell built-ins. On most system you will find the complete list in
Code:
man bashbuiltins
or something similar, depending on the linux distro. If you're in doubt and don't know if your shell is currently using the built-in or the external command, use type, e.g.
Code:
$ type echo
echo is a shell builtin
$ type printf
printf is a shell builtin
$ type type
type is a shell builtin
$ type cut
cut is /usr/bin/cut
in the list above, only the last command is an external one. Anyway you can get a different result on your system for the echo and printf commands.
No joy on man bashbuiltins, but man builtins does give me the list.
You're right, printf is a builtin for me!
Well that's hilarious. I really did hack together some legacy crap that's been replaced by a oneliner. =)
But I guess that's just the way it is. The included man page "documentation" (used loosely) as well as the official documentation as well as the best tutorials and blog postings didn't mention rounding.
Just like my grandpa could say "back in my day we had to read the source code, and all the comments were misleading too", one day I could tell my grandkids "back in my day the documentation was written by the *programmer*, and all their examples sucked".
Community documentation ftw. I'm so glad wikis are getting popular for online program documentation, and even more glad that MediaWiki gets chosen more ThanThoseOtherCrappyWikis so I can actually help.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.