zoukankan      html  css  js  c++  java
  • 脚本命令高级Bash脚本编程指南(31):数学计算命令

    题记:写这篇博客要主是加深自己对脚本命令的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢。

        

    高等Bash脚本编程指南(31):数学盘算命令

        成于坚持,败于止步

        操作数字

        factor

        将一个正数分解为多个素数.

    root@ubuntu:~/resource/shell-study/0622-2013# factor 2374
    2374: 2 1187
    root@ubuntu:~/resource/shell-study/0622-2013# factor 27417
    27417: 3 13 19 37
    root@ubuntu:~/resource/shell-study/0622-2013#

        信不过他的话你可以自己去试试,O(∩_∩)O~

        bc

        Bash不能处置浮点运算, 并且缺乏特定的一些操作, 这些操作都是一些主要的盘算功能. 幸运的是, bc可以处理这个问题.

        bc不仅仅是个多功能灵巧的精确盘算工具, 且它还供给许多编程语言才具有的一些方便功能,bc比拟类似于C语言的语法,因为它是一个完全的UNIX工具, 所以它可以用在pipe中, bc在脚本中也是很常用的.

        这里有一个简单的使用bc命令的模版, 可以用来盘算脚本中的变量. 这个模版经常用于命令替换中.

        variable=$(echo "OPTIONS; OPERATIONS" | bc)

        一个实例,不过不推荐你去看其中的算法,你只要只要怎么用bc去盘算就够了

    #!/bin/bash
    # 盘算按月归还存款的数量.
    
    echo
    echo "Given the principal, interest rate, and term of a mortgage,"
    echo "calculate the monthly payment."
    
    bottom=1.0
    
    echo
    echo -n "Enter principal (no commas) "
    read principal
    echo -n "Enter interest rate (percent)"#如果是12%,那就键入"12",而不是".12".
    read interest_r
    echo -n "Enter term (months) "
    read term
    
    interest_r=$(echo "scale=9; $interest_r/100.0" | bc) # 转换成小数.
    # "scale"指定了有效数字的个数.
    
    interest_rate=$(echo "scale=9; $interest_r/12 + 1.0" | bc)
    
    top=$(echo "scale=9; $principal*$interest_rate^$term" | bc)
    
    echo; echo "Please be patient. This may take a while."
    
    let "months = $term - 1"
    # ====================================================================
    for ((x=$months; x > 0; x--))
    do
       bot=$(echo "scale=9; $interest_rate^$x" | bc)
       bottom=$(echo "scale=9; $bottom+$bot" | bc)
    #  bottom = $(($bottom + $bot"))
     done
    # ====================================================================
    
    # --------------------------------------------------------------------
    #  Rick Boivie给出了一个对上边循环的修改方案,
    #+ 这个修改更加有效率, 将会节省大概2/3的时光.
    
    # for ((x=1; x <= $months; x++))
    # do
    #   bottom=$(echo "scale=9; $bottom * $interest_rate + 1" | bc)
    # done
    
    
    #  然后他又想出了一个更加有效率的版本,
    #+ 将会节省95%的时光!
    
    # bottom=`{
    #     echo "scale=9; bottom=$bottom; interest_rate=$interest_rate"
    #     for ((x=1; x <= $months; x++))
    #     do
    #          echo 'bottom = bottom * interest_rate + 1'
    #     done
    #     echo 'bottom'
    #     } | bc`       # 在命令替换中嵌入一个'for循环'.
    # -------------------------------------------------------------------------
    #  另一方面, Frank Wang提议:
    #  bottom=$(echo "scale=9; nterest_rate^$term-1)/($interest_rate-1)" | bc)
    
    #  因为 . . .
    #  在循环后边的算法
    #+ 事实上是一个等比数列的求和公式.
    #  求和公式是 e0(1-q^n)/(1-q),
    #+ e0是第一个元素, q=e(n+1)/e(n),
    #+ n是元素数量.
    # --------------------------------------------------------------------------
    
    
     # let "payment = $top/$bottom"
     payment=$(echo "scale=2; $top/$bottom" | bc)
     # 使用2位有效数字来表示美元和美分.
    
     echo
     echo "monthly payment = $$payment"  # 在总和的前边表现美元符号.
     echo
    
     exit 0

        结果:

    root@ubuntu:~/resource/shell-study/0622-2013# ./test10.sh 
    
    Given the principal, interest rate, and term of a mortgage,
    calculate the monthly payment.
    
    Enter principal (no commas) 50000
    Enter interest rate (percent)#如果是12%,那就键入12,而不是.12.5
    Enter term (months) 12
    
    Please be patient. This may take a while.
    
    monthly payment = $4280.37
    
    root@ubuntu:~/resource/shell-study/0622-2013#

        接着看一个数制转换的实例,挺不错的实例啊,可以研究一下

    #!/bin/bash
    ##########################################################################
    # 脚本       :	base.sh - 用不同的数制来打印数字 (Bourne Shell)
    # 作者       :	Heiner Steven (heiner.steven@odn.de)
    # 日期       :	07-03-95
    # 类型       :	桌面
    # $Id: base.sh,v 1.2 2000/02/06 19:55:35 heiner Exp $
    # ==> 上边这行是RCS ID信息.
    ##########################################################################
    # 描述
    #
    # 修改纪录
    # 21-03-95 stv	fixed error occuring with 0xb as input (0.2)
    ##########################################################################
    
    # ==> 在本书中使用这个脚本通过了原作者的授权.
    # ==> 注释是本书作者添加的.
    
    NOARGS=65
    PN=`basename "$0"`			       # 程序名
    VER=`echo '$Revision: 1.2 $' | cut -d' ' -f2`  # ==> VER=1.2
    
    Usage () {
        echo "$PN - print number to different bases, $VER (stv '95)
    usage: $PN [number ...]
    
    If no number is given, the numbers are read from standard input.
    A number may be
        binary (base 2)		starting with 0b (i.e. 0b1100)
        octal (base 8)		starting with 0  (i.e. 014)
        hexadecimal (base 16)	starting with 0x (i.e. 0xc)
        decimal			otherwise (i.e. 12)" >&2
        exit $NOARGS
    }   # ==> 打印出用法信息的函数.
    
    Msg () {
        for i   # ==> 省略[list].
        do echo "$PN: $i" >&2
        done
    }
    
    Fatal () { Msg "$@"; exit 66; }
    
    PrintBases () {
        # 决定数字的数制
        for i      # ==> 省略[list]...
        do         # ==> 所以是对命令行参数停止操作.
    	case "$i" in
    	    0b*)		ibase=2;;	# 2进制
    	    0x*|[a-f]*|[A-F]*)	ibase=16;;	# 16进制
    	    0*)			ibase=8;;	# 8进制
    	    [1-9]*)		ibase=10;;	# 10进制
    	    *)
    		Msg "illegal number $i - ignored"
    		continue;;
    	esac
    
    	# 去掉前缀, 将16进制数字转换为大写(bc命令须要这么做)
    	number=`echo "$i" | sed -e 's:^0[bBxX]::' | tr '[a-f]' '[A-F]'`
    	# ==> 使用":" 作为sed分隔符, 而不使用"/".
    
    	# 将数字转换为10进制
    	dec=`echo "ibase=$ibase; $number" | bc`  # ==> 'bc'是个盘算工具.
    	case "$dec" in
    	    [0-9]*)	;;			 # 数字没问题
    	    *)		continue;;		 # 错误: 疏忽
    	esac
    
    	# 在一行上打印所有转换后的数字.
    	# ==> 'here document'供给命令列表给'bc'.
    	echo `bc <<!
    	    obase=16; "hex="; $dec
    	    obase=10; "dec="; $dec
    	    obase=8;  "oct="; $dec
    	    obase=2;  "bin="; $dec
    !` | sed -e 's: :	:g'
    
        done
    }
    
    while [ $# -gt 0 ]
    # ==>  这里必须使用一个"while循环",
    # ==>+ 因为所有的case都可能退出循环或者
    # ==>+ 结束脚本.
    # ==> (感谢, Paulo Marcel Coelho Aragao.)
    do
        case "$1" in
    	--)     shift; break;;
    	-h)     Usage;;                 # ==> 帮助信息.
    	-*)     Usage;;
        *)      break;;			# 第一个数字
        esac   # ==> 对于合法输入停止更严厉检查是非常有效的.
        shift
    done
    
    if [ $# -gt 0 ]
    then
        PrintBases "$@"
    else					# 从stdin中读取
        while read line
        do
    	PrintBases $line
        done
    fi
    
    
    exit 0

        结果:

    root@ubuntu:~/resource/shell-study/0622-2013# ./test11.sh -h
    test11.sh - print number to different bases, 1.2 (stv '95)
    usage: test11.sh [number ...]
    
    If no number is given, the numbers are read from standard input.
    A number may be
        binary (base 2)		starting with 0b (i.e. 0b1100)
        octal (base 8)		starting with 0  (i.e. 014)
        hexadecimal (base 16)	starting with 0x (i.e. 0xc)
        decimal			otherwise (i.e. 12)
    root@ubuntu:~/resource/shell-study/0622-2013# ./test11.sh 
    6
    hex=6	dec=6	oct=6	bin=110
    15
    hex=F	dec=15	oct=17	bin=1111
    D
    hex=D	dec=13	oct=15	bin=1101
    ^C
    root@ubuntu:~/resource/shell-study/0622-2013#

        当一个脚本须要将一个选项列表和多个命令传递到bc中时, 这种方法就显得非常合适了.

    variable=`bc << LIMIT_STRING
    options
    statements
    operations
    LIMIT_STRING
    `
    ...or...
    
    variable=$(bc << LIMIT_STRING
    options
    statements
    operations
    LIMIT_STRING
    )

        其实上面的第一种方式在上面的实例中已经使用过了,很好,很强大

        接着看一个实例:使用"here document"来调用bc

        每日一道理
    因为自信,在呀呀学语时,我靠着纤嫩的双腿,迈出人生的第一步;因为自信,我一次次将第一名的奖状高高举起;因为自信,我毫不吝惜地剪掉飘逸的长发,在运动场上展现风采……感谢自信,它给了我一双翅膀,让我在电闪雷鸣中去飞翔,在风雨中去搏击人生!
    #!/bin/bash
    # 使用命令替换来调用'bc'
    # 并与'here document'相结合.
    
    var1=`bc << EOF
    18.33 * 19.78
    EOF
    `
    echo $var1       # 362.56
    
    #  使用$( ... )这种标记法也可以.
    v1=23.53
    v2=17.881
    v3=83.501
    v4=171.63
    
    var2=$(bc << EOF
    scale = 4
    a = ( $v1 + $v2 )
    b = ( $v3 * $v4 )
    a * b + 15.35
    EOF
    )
    echo $var2       # 593487.8452
    
    
    var3=$(bc -l << EOF
    scale = 9
    s ( 1.7 )
    EOF
    )
    # 返回弧度为1.7的正弦.
    # "-l"选项将会调用'bc'算数库.
    echo $var3       # .991664810
    
    
    # 现在, 在函数中试一下...
    hyp=             # 声明全局变量.
    hypotenuse ()    # 盘算直角三角形的斜边.
    {
    hyp=$(bc -l << EOF
    scale = 9
    sqrt ( $1 * $1 + $2 * $2 )
    EOF
    )
    # 不幸的是, 不能从bash函数中返回浮点值.
    }
    
    hypotenuse 3.68 7.31
    echo "hypotenuse = $hyp"    # 8.184039344
    
    exit 0

        结果:

    root@ubuntu:~/resource/shell-study/0622-2013# chmod +x test12.sh 
    root@ubuntu:~/resource/shell-study/0622-2013# ./test12.sh 
    362.56
    593487.8452
    .991664810
    hypotenuse = 8.184039344
    root@ubuntu:~/resource/shell-study/0622-2013#

        一个模拟盘算圆周率的实例,不过先说明,这个盘算须要统计,执行的次数越多越好,我只让他跑了10次,差得十万八千里可以说,我只是测试,准不准我可不管,O(∩_∩)O~

    #!/bin/bash
    
    # 这事实上是一个"Monte Carlo"蒙特卡洛模拟的非常简单的实例:
    #+ 蒙特卡洛模拟是一种由现实事件抽象出来的数学模型,
    #+ 由于要使用随机抽样统计来估算数学函数,所以使用伪随机数来模拟真正的随机数.
    
    #  想象有一个完美的正方形土地, 边长为10000个单位.
    #  在这块土地的中间有一个完美的圆形湖,
    #+ 这个湖的直径是10000个单位.
    #  这块土地的绝大多数面积都是水, 当然只有4个角上有一些土地.
    #  (可以把这个湖想象成为这个正方形的内接圆.)
    #
    #  我们将使用老式的大炮和铁炮弹
    #+ 向这块正方形的土地上开炮.
    #  所有的炮弹都会击中这块正方形土地的某个地方.
    #+ 或者是打到湖上, 或者是打到4个角的土地上.
    #  因为这个湖占据了这个区域大部分地方,
    #+ 所以大部分的炮弹都会"扑通"一声落到水里.
    #  而只有很少的炮弹会"砰"的一声落到4个
    #+ 角的土地上.
    #
    #  如果我们发出的炮弹足够随机的落到这块正方形区域中的话,
    #+ 那么落到水里的炮弹与打出炮弹总数的比率,
    #+ 大概非常接近于PI/4.
    #
    #  原因是所有的炮弹事实上都
    #+ 打在了这个土地的右上角,
    #+ 也就是, 笛卡尔坐标系的第一象限.
    #  (之前的解释只是一个简化.)
    #
    #  理论上来说, 如果打出的炮弹越多, 就越接近这个数字.
    #  然而, 对于shell 脚本来说一定会做些让步的,
    #+ 因为它肯定不能和那些内建就支持浮点运算的编译语言相比.
    #  当然就会降低精度.
    
    
    DIMENSION=10000  # 这块土地的边长.
                     # 这也是所产生随机整数的上限.
    
    MAXSHOTS=10    # 开炮次数.
                     # 10000或更多次的话, 效果应该更好, 但有点太浪费时光了.
    PMULTIPLIER=4.0  # 接近于PI的比例因子.
    
    get_random ()
    {
    	SEED=$(head -1 /dev/urandom | od -N 1 | awk '{ print $2 }')
    	RANDOM=$SEED                                  #  来自于"seeding-random.sh"
    		                                          #+ 的例子脚本.
    	let "rnum = $RANDOM % $DIMENSION"             #  范围小于10000.
    	echo $rnum
    }
    
    distance=        # 声明全局变量.
    hypotenuse ()    # 从"alt-bc.sh"例子来的,
    {                # 盘算直角三角形的斜边的函数.
    	distance=$(bc -l << EOF
    	scale = 0
    	sqrt ( $1 * $1 + $2 * $2 )
    EOF
    )
    #  设置 "scale" 为 0 , 好让结果四舍五入为整数值,
    #+ 这也是这个脚本中必须折中的一个地方.
    #  不幸的是, 这将降低模拟的精度.
    }
    
    # 初始化变量.
    shots=0
    splashes=0
    thuds=0
    Pi=0
    
    while [ "$shots" -lt  "$MAXSHOTS" ]           # 主循环.
    do
    
      xCoord=$(get_random)                        # 取得随机的 X 与 Y 坐标.
      yCoord=$(get_random)
      hypotenuse $xCoord $yCoord                  #  直角三角形斜边 =
                                                  #+ distance.
      ((shots++))
    
      printf "#%4d   " $shots
      printf "Xc = %4d  " $xCoord
      printf "Yc = %4d  " $yCoord
      printf "Distance = %5d  " $distance         #  到湖中心的
                                                  #+ 距离 --
                                                  #  起始坐标点 --
                                                  #+  (0,0).
    
      if [ "$distance" -le "$DIMENSION" ]
      then
        echo -n "SPLASH!  "
        ((splashes++))
      else
        echo -n "THUD!    "
        ((thuds++))
      fi
    
      Pi=$(echo "scale=9; $PMULTIPLIER*$splashes/$shots" | bc)
      # 将比例乘以4.0.
      echo -n "PI ~ $Pi"
      echo
    
    done
    
    echo
    echo "After $shots shots, PI looks like approximately $Pi."
    # 如果不太准的话, 那么就提高一下运行的次数. . .
    # 可能是由于运行错误和随机数随机程度不高造成的.
    
    exit 0

        结果:

    root@ubuntu:~/resource/shell-study/0622-2013# ./test13.sh 
    #   1   Xc = 9684  Yc = 9797  Distance = 13775  THUD!    PI ~ 0
    #   2   Xc = 8460  Yc = 6413  Distance = 10615  THUD!    PI ~ 0
    #   3   Xc = 1111  Yc = 1189  Distance =  1627  SPLASH!  PI ~ 1.333333333
    #   4   Xc = 4230  Yc = 4986  Distance =  6538  SPLASH!  PI ~ 2.000000000
    #   5   Xc = 9403  Yc =   58  Distance =  9403  SPLASH!  PI ~ 2.400000000
    #   6   Xc = 9345  Yc = 5325  Distance = 10755  THUD!    PI ~ 2.000000000
    #   7   Xc = 6826  Yc = 4682  Distance =  8277  SPLASH!  PI ~ 2.285714285
    #   8   Xc = 3762  Yc = 4721  Distance =  6036  SPLASH!  PI ~ 2.500000000
    #   9   Xc = 7446  Yc = 5189  Distance =  9075  SPLASH!  PI ~ 2.666666666
    #  10   Xc = 1337  Yc = 9442  Distance =  9536  SPLASH!  PI ~ 2.800000000
    
    After 10 shots, PI looks like approximately 2.800000000.
    root@ubuntu:~/resource/shell-study/0622-2013#

        

        dc

        

        dc(桌面盘算器desk calculator)工具是面向栈的, 并且使用RPN(逆波兰表达式"Reverse Polish Notation"又叫"后缀表达式"). 与bc命令很相似, 但是这个工具具有好多只有编程语言才具有的能力.

        正常表达式   逆波兰表达式
    a+b                a,b,+
    a+(b-c)          a,b,c,-,+
    a+(b-c)*d      a,d,b,c,-,*,+

        绝大多数人都避免使用这个工具, 因为它须要非直观的RPN输入, 但是, 它却有特定的用途.

        一个实例:将10进制数字转换为16进制数字

    #!/bin/bash
    # hexconvert.sh: 将10进制数字转换为16进制数字.
    
    E_NOARGS=65 # 缺少命令行参数错误.
    BASE=16     # 16进制.
    
    if [ -z "$1" ]
    then
      echo "Usage: $0 number"
      exit $E_NOARGS
      # 须要一个命令行参数.
    fi
    # 练习: 添加命令行参数检查.
    
    
    hexcvt ()
    {
    	if [ -z "$1" ]
    	then
    	  echo 0
    	  return    # 如果没有参数传递到这个函数中的话就"return" 0.
    	fi
    
    	echo ""$1" "$BASE" o p" | dc
    	#                 "o" 设置输出的基数(数制).
    	#                   "p" 打印栈顶.
    	# 参考dc的man页来了解其他的选项.
    	return
    }
    
    hexcvt "$1"
    
    exit 0

        结果:

    root@ubuntu:~/resource/shell-study/0622-2013# chmod +x test14.sh 
    root@ubuntu:~/resource/shell-study/0622-2013# ./test14.sh 15 
    F
    root@ubuntu:~/resource/shell-study/0622-2013# ./test14.sh 6
    6
    root@ubuntu:~/resource/shell-study/0622-2013# ./test14.sh 20
    14
    root@ubuntu:~/resource/shell-study/0622-2013#

        

        awk

        

        在脚本中使用浮点运算的另一种方法是使用awk内建的数学运算函数

        看一个用awk的实例:盘算直角三角形的斜边

    #!/bin/bash
    # hypotenuse.sh: 返回直角三角形的斜边.
    #               (直角边长的平方和,然后对和取平方根)
    
    ARGS=2                # 须要将2个直角边作为参数传递进来.
    E_BADARGS=65          # 错误的参数值.
    
    if [ $# -ne "$ARGS" ] # 测试传递到脚本中的参数值.
    then
      echo "Usage: `basename $0` side_1 side_2"
      exit $E_BADARGS
    fi
    
    
    AWKSCRIPT=' { printf( "%3.7f
    ", sqrt($1*$1 + $2*$2) ) } '
    #             命令 / 传递给awk的参数
    
    
    # 现在, 将参数通过管道传递给awk.
    echo -n "Hypotenuse of $1 and $2 = "
    echo $1 $2 | awk "$AWKSCRIPT"
    
    exit 0

        结果:

    root@ubuntu:~/resource/shell-study/0622-2013# ./test16.sh 
    Usage: test16.sh side_1 side_2
    root@ubuntu:~/resource/shell-study/0622-2013# ./test16.sh 4 6
    Hypotenuse of 4 and 6 = 7.2111026
    root@ubuntu:~/resource/shell-study/0622-2013#

        先到这里了,O(∩_∩)O~

        

    我的专栏地址:http://blog.csdn.net/column/details/shell-daily-study.html

        待续。。。。

    文章结束给大家分享下程序员的一些笑话语录: 神灯新篇
    一个程序员在海滩上发现了一盏神灯。他在灯上擦了几下,一个妖怪就从灯里跳出来说:“我是世界上法术最强的妖怪。我可以实现你的任何梦想,但现在,我只能满足你一个愿望。”程序员摊开了一幅中东地图说:“我想让中东得到永久的和平。”妖怪答道:“哦,我没办法。自打创世纪以来,那里的战火就没有停息过。这世上几乎没有我办不到的事,但这件事除外。”程序员于是说:“好吧,我是一个程序员,为许多用户编写过程序。你能让他们把需求表述得更清楚些,并且让我们的软件项目有那么一两次按进度按成本完成吗?”妖怪说:“唔,我们还是来看中东地图吧。”

    --------------------------------- 原创文章 By
    脚本和命令
    ---------------------------------

  • 相关阅读:
    前沿技术解密——VirtualDOM
    Ques核心思想——CSS Namespace
    Unix Pipes to Javascript Pipes
    Road to the future——伪MVVM库Q.js
    聊聊CSS postproccessors
    【译】十款性能最佳的压缩算法
    Kafka Streams开发入门(9)
    Kafka Streams开发入门(8)
    【译】Kafka Producer Sticky Partitioner
    【译】99th Percentile Latency at Scale with Apache Kafka
  • 原文地址:https://www.cnblogs.com/xinyuyuanm/p/3150372.html
Copyright © 2011-2022 走看看