Simple human front-end for 'bc(1)', the unlimited precision calculator
A simple human front-end for 'bc(1)' the unlimited precision calculator.
Some weeks ago I got tired of dealing with inhumanely formatted big numbers like those presented by fdisk(1) of a 320 GB hard disk
Code:
Disk: wd1 geometry: 38913/255/63 [625142448 Sectors]
Offset: 0 Signature: 0xAA55
Starting Ending LBA Info:
#: id C H S - C H S [ start: size ]
-------------------------------------------------------------------------------
0: A5 0 1 1 - 5220 254 63 [ 63: 83875302 ] FreeBSD
*1: A6 5221 0 1 - 10441 254 63 [ 83875365: 83875365 ] OpenBSD
2: 00 0 0 0 - 0 0 0 [ 0: 0 ] unused
3: 00 0 0 0 - 0 0 0 [ 0: 0 ] unused
Most people will find 625,142,448 or 625.142.448 easier to read then 625142448.
To solve this issue I wrote a simple shell script with pre-processes input for 'bc(1)', and post-processes it's output.
According to the man page, 'bc' is an arbitrary-precision arithmetic language and calculator. The 'bc' language supports variables, loops and functions. My goal was to only support simple single line calculations with
Code:
- + : unary minus and plus
+ - * / % : addition, subtraction, multiplication, division and modulus
Input conversion
The number format expected by 'bc' does not allow the usage of comma's or periods to group the digits in groups of three. Only a period '.' is allowed to specify a decimal fraction.
The shell script 'calc' supports two number formats:
12,345,678.99, as used in the Anglo-Saxon world, and 12.345.678,99 as used in other parts of the world.
The 12,345,678.99 I will call 'commified' and 12.345.678,99 'periodified'.
Conversion of commified input for digestion by 'bc' is simple, just remove all comma's.
A 'periodified' number needs two steps: first remove all periods '.', then change the lonely ',' into a '.'
Output conversion
Conversion of the result spit out by 'bc' is not that easy.
The 'Perl cookbook' by Christiansen and presents a 'commify' perl script.
My commented version of this script
Code:
#!/usr/bin/perl -w
# $Id: commify,v 1.1 2009/02/22 02:31:30 j65nko Exp $
use strict ;
# Input style nr without ",'s : -155566.1234
# Output style : -1,555,666.1234
# taken from 'Perl Cookbook'
# (\d\d\d)(?=/d) : group of 3 digits followed by another digit
# (?!\d*\.) : the digits should not be followed by a decimal "."
# -155566.1234 reversed : 4321.6665551-
# ^^^^ group of 3 digits followed by another digit is candidate for
# insertion of ','. Because it is followed by a decimal point, it is a decimal fraction,
# and decimal fraction shouldn't be grouped. So the '(?!\d*\.)' makes sure this does not
# happen
# The first group of 3 digits is 666, which are followed by a '5'.
# The regex engine just looks ahead for the 5, but will not consume it.
# In the next iteration, it will start at this '5' and thus find '555' followed by the '1'.
# So a ',' will be inserted.
# The next iteration starting at the '1' finds a '-' , no other 2 digits for
#
sub commify {
my $nr = reverse $_[0] ;
$nr =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g ;
return scalar reverse $nr ;
}
while (<>) {
print commify $_ ;
}
The script reverses the number to insert the grouping character, and then reverses the formatted number again.
This script also formed the inspiration of the 'periodify' script:
Code:
#!/usr/bin/perl -w
#$Id: periodify,v 1.1 2009/02/22 02:32:51 j65nko Exp $
use strict ;
# inspired by 'commify' routine from 'Perl Cookbook'
# first eliminate all ',' : 1,444,444.99 -> 1444444.99
# replace decimal "." by decimal ',' : 1444444.99 -> 1444444,99
# adds periods to group digits : 1.444.444,99
sub periodify {
my $nr = reverse $_[0] ;
$nr =~ s/,//g ;
$nr =~ s/\./,/ ;
$nr =~ s/(\d\d\d)(?=\d)(?!\d*,)/$1./g ;
return scalar reverse $nr ;
}
while (<>) {
print periodify $_ ;
}
The 'calc' sh script is simple:
Code:
#!/bin/sh
# $Id: calc,v 1.2 2009/02/22 02:38:06 j65nko Exp $
case "$1" in
#
-p ) # clean 1.000.000,99 -> 1000000.99 as required by 'bc'
CLEAN="sed -e s!\.!!g -e s!,!\.!g"
FORMAT='periodify' # answer produced by 'bc'
# replace decimal point by ',' and reinsert '.' for grouping
;;
-c ) # clean 1,000,000.99 -> 1000000.99 as required by 'bc'
CLEAN="sed -e s!,!!g"
FORMAT='commify' # answer produced by 'bc'
;;
*) echo "Usage: $0 -p (group digits with periods: '.')"
echo " or"
echo " $0 -c (group digits with commas : ',')"
exit 1
esac
while true ; do
printf "Expression : " >&2
read EXPR
if [ -z "${EXPR}" ] ; then break
fi
[ -t 0 ] || echo "${EXPR}"
EXPR=$( echo "${EXPR}" | $CLEAN )
#printf "Normalized : $EXPR\n"
ANSWER=$(echo "scale=4 ; ${EXPR}" | bc )
HUMAN=$(echo "${ANSWER}" | ${FORMAT} )
printf "%s ( %s )\n" ${ANSWER} ${HUMAN}
done
Some examples - The biggest numbers expressable in 32 and 64 bits
Code:
$ calc -c
Expression : 2^32 -1
4294967295 ( 4,294,967,295 )
Expression : 2^64 - 1
18446744073709551615 ( 18,446,744,073,709,551,615 )
- Verifying the number of megabytes reported by 'dmesg'
Code:
dmesg | grep wd1
wd1 at pciide0 channel 1 drive 0: <WDC WD3200AAKS-00VYA0>
wd1: 16-sector PIO, LBA48, 305245MB, 625142448 sectors
First calculate size in bytes :
Code:
Expression : 625142448 * 512
320072933376 ( 320,072,933,376 )
So 320 decimal gigabytes.
To get the binary megabyte figure (1024^2):
Code:
Expression : 320,072,933,376 / 1024^2
305245.3359 ( 305,245.3359 )
- Cylinder size of a LBA disk with 255 heads @ 63 sectors:
Code:
Expression : 255*63
16065 ( 16,065 )
Size in bytes:
Code:
Expression : 255*63*512
8225280 ( 8,225,280 )
- Calculating the cylinders of the 'wd1' disk, using 255 heads and 63 sectors per track:
Code:
Expression : 625142448 / ( 255*63)
38913.3176 ( 38,913.3176 )
So 38,913 cylinders.
- Dividing this 38,913 cylinder disks in about 4 equal partitions:
Code:
Expression : 38,913 * 255 * 63
625137345 ( 625,137,345 )
Expression : 625,137,345 / 4
156284336.2500 ( 156,284,336.2500 )
Another approach by dividing the number of cylinders by 4:
Code:
Expression : 38,913 / 4
9728.2500 ( 9,728.2500 )
The 9,728 cylinders in sectors
Code:
Expression : 9,728 * 255 * 63
156280320 ( 156,280,320 )
First partition will have 63 sectors less because of the MBR.
Partitions two and three will both have: 156,280,320 sectors
Size for last partition
Code:
Expression : 38,913 * 255 * 63 - ( 3 * 156,280,320 )
156296385 ( 156,296,385 )
A double check
Code:
Expression : 38,913 * 255*63
625137345 ( 625,137,345 )
Expression : 156,280,320 + 156,280,320 + 156,280,320 + 156,296,385
625137345 ( 625,137,345 )
- End capital of your 1,000,000 dollar inheritance after 10 years compound interest of 4 %
Code:
Expression : 1,000,000 * 1.04^10
1480000.0000 ( 1,480,000.0000 )
Same but now invested in the Bernie Madoff investment fund with a guaranteed 10%
Code:
Expression : 1,000,000 * 1.10^10
2593600.0000 ( 2,593,600.0000 )
__________________
You don't need to be a genius to debug a pf.conf firewall ruleset, you just need the guts to run tcpdump
|