DaemonForums  

Go Back   DaemonForums > Miscellaneous > Guides

Guides All Guides and HOWTO's.

Reply
 
Thread Tools Display Modes
  #1   (View Single Post)  
Old 22nd February 2009
J65nko J65nko is offline
Administrator
 
Join Date: May 2008
Location: Budel - the Netherlands
Posts: 4,125
Default 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  )
Attached Files
File Type: tgz calc.tgz (1.3 KB, 80 views)
__________________
You don't need to be a genius to debug a pf.conf firewall ruleset, you just need the guts to run tcpdump
Reply With Quote
  #2   (View Single Post)  
Old 2nd February 2013
J65nko J65nko is offline
Administrator
 
Join Date: May 2008
Location: Budel - the Netherlands
Posts: 4,125
Default

To use a simpler version, called kal that only does a single calculation without formatting the numbers, you can use the following in your .profile:
Code:
# $OpenBSD: dot.profile,v 1.4 2005/02/16 06:56:57 matthieu Exp $
#
# sh/ksh initialization

PATH=$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin:/usr/games:.
export PATH HOME TERM
export PAGER=/usr/bin/less
export PS1="[\u@\h]\w: "

kal(){ echo "scale=4 ; $@" | bc ;}
One caveat: the multiplication operator '*' is also used one of the the shell file name wildcard/patterns. To prevent problems you have to either escape the '*' with a blackslash or use single quotes to delimit your arithmetic expression.

To stimulate the neurons in your gr[ae]y matter, an uncommented illustration of something you can run into:
Code:
$ ls -l 2013*

-rw-r--r--  1 j65nko j65nko  0 Feb  2 19:35 2013-02-01_data24
-rw-r--r--  1 j65nko j65nko  0 Feb  2 19:40 20134343

$  kal 2013*3
20134343

$ kal 2013\*3
6039

$  kal 2013*4 
bc: stdin:1: syntax error: _data24 unexpected

$ kal '2013*4'
8052
__________________
You don't need to be a genius to debug a pf.conf firewall ruleset, you just need the guts to run tcpdump
Reply With Quote
Reply

Tags
arithmetic, bc(1), calculate, calculator

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
A simple question Mr-Biscuit Off-Topic 1 16th April 2009 04:26 PM
Simple Firewall with PF jones FreeBSD General 3 7th November 2008 02:02 AM
Help With [seemingly] Simple Problem MustLearn FreeBSD General 3 7th October 2008 10:05 AM
Simple/easy ircd Weaseal FreeBSD Ports and Packages 0 17th July 2008 12:31 PM
RPN Calculator?? DrJ FreeBSD General 7 30th May 2008 01:16 AM


All times are GMT. The time now is 04:36 AM.


Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Content copyright © 2007-2010, the authors
Daemon image copyright ©1988, Marshall Kirk McKusick