# bc - 算术操作精密运算工具

bc命令 是一种支持任意精度的交互执行的计算器语言。bash内置了对整数四则运算的支持,但是并不支持浮点运算,而bc命令可以很方便的进行浮点运算,当然整数运算也不再话下。

bc是一种算数语言,其语法和c语言类似,可以交互执行。通过命令行选项可以获得一个标准的数学库。如果请求,在处理任何文件之前定义数学库。BC从处理所有文件的代码开始。命令行中列出的文件按所列顺序排列。在处理完所有文件后,BC从标准输入中读取。所有代码都在读取时执行。(如果文件包含停止处理器的命令,BC将永远不会从标准输入中读取。

# 适用范围

RedHat
RHEL
Ubuntu
CentOS
Debian
Deepin
SUSE
openSUSE
Fedora
Linux Mint
Alpine Linux
Arch Linux

# 语法

bc  [ -hlwsqv ]  [long-options]  [  file ... ]

# 选项

-h, --help           # 帮助信息
-v, --version        # 显示命令版本信息
-l, --mathlib        # 定义标准数学库
-i, --interactive    # 强制交互
-w, --warn           # 显示POSIX的警告信息
-s, --standard       # 使用POSIX标准来处理
-q, --quiet          # 不显示欢迎信息

# 说明

1.数据

bc中最基本的元素是数字。数字是任意精度的数字。这种精度既包括整数部分,也包括分数部分。所有的数字在内部用十进制表示,所有的计算都用十进制来表示。(此版本截断除法和乘运算的结果。)数字有两个属性:长度和小数位。长度是数字中有效位的总数,小数位是小数点之后的的有效位数。例如,0.000001的长度是6,小数位是6;1935.000的长度是7,小数位是3。

2.变量

数字存储在两种类型的变量中,简单变量和数组。变量名称以字母开头,后面跟着任意数量的字母、数字和下划线。所有字母都必须小写。(全字母-数字名是扩展名。在POSIX bc中,所有名称都是一个小写字母。)变量的类型在上下文中是明确的,因为所有数组变量名称后面都会有方括号([])。

有四个特殊的变量:scale、ibase、obase和last。scale定义了一些操作在小数点之后是如何使用数字的,默认值是0。ibase和obase定义输入和输出数字的转换基。默认值都是基数10。last(扩展)是一个变量。它是最后打印的数字的值。所有这些变量可能都有分配给它们的值,以及表达式中使用的值。

3.注释

bc中的注释以字符“/”开头,以字符“/”结尾。注释可以从任何地方开始,并显示为输入中的单个空格。(这会导致注释分隔其他输入项。例如,在变量名的中间找不到注释。)注释包括注释开始和结束之间的任何新行(行尾)。

为了支持bc脚本的使用,添加了行注释作为扩展。行注释从“#”字符开始,继续到行的结束。行结束字符不是注释的一部分,而是正常处理的。

如果命令行上的任何文件无法打开,bc将报告该文件不可用并终止。

# 关于表达式

这些数字由表达式和语句操作。由于语言设计为交互式,所以语句和表达式会尽快执行。没有“主”程序,而是在遇到时执行代码。

一个简单的表达式只是一个常量。BC使用变量ibase指定的当前输入基将常量转换为内部十进制数。(函数中有一个例外。)ibase的有效值为2到16,将超出此范围之后,分配给ibase的值可能是2或16。输入数字可能包含字符0-9和A-F。(注:它们必须是大写字母,小写字母是变量名称。)无论ibase的值是多少,单数数字总是有该数字的值。(即A=10。)对于多位数字,bc将所有大于或等于ibase的输入数字更改为IBASE-1的值。这使得FFF始终输入的最大3位数。

完全表达式类似于许多其他高级语言。由于只有一种数字,所以没有混合类型的规则。相反,有关于表达式精度的规则。每个表达式都有一个精度。这是从原始数字的精度、所执行的操作以及在许多情况下变量scale的值导出的。变量scale的有效值为0到可由c语言整数表达的最大值。

在以下合法表达式的描述中,“expr”指的是一个完整的表达式,“var”指的是一个简单的或数组变量。除非特别提到,结果的精度是所涉及的表达式的最大精度。

普通表达式 说明
-expr 结果是对表达式的否定。
++var 变量增加1,而新值是表达式的结果
--var 变量减1,而新值是表达式的结果
var++ 表达式的结果是变量的值,然后变量增加1
var-- 表达式的结果是变量的值,然后变量减1
expr * expr 表达式的结果是这两个表达式的乘积
expr + expr 表达式的结果是这两个表达式的和
expr – expr 表达式的结果是这两个表达式的差
expr / expr 表达式的结果是这两个表达式的商。结果的精度是变量scale的值。
expr % expr 表达式的结果是“余数”
expr ^ expr 表达式的结果是n次方,第二个表达式必须是整数。如果指数是负数,那么结果的精度是scale
(expr) 强制对表达式进行计算
var = expr 变量就是表达式的值
var = expr 相当于“var=varexpr”,例如”var -= expr” 等价于“var=var-expr”
关系表达式 关系表达式是一种特殊的表达式,计算结果总是0或1。如果关系为真,则计算为1;如果关系为假,则结果是0.。这些表达式可能出现在任何合法表达式中。(POSIX bc要求关系表达式仅用于if、while和语句,并且只能在其中进行一次关系测试。)
expr1 < expr2 expr1 <= expr2 expr1 > expr2 expr1 >= expr2
expr1 == expr2 expr1 != expr2 expr1 && expr2 expr1 || expr2
!expr

运算符的优先级如下,从上到下依次增加:

运算符 结合方式
|| 左结合
&& 左结合
不结合
关系运算符 左结合
赋值运算符 右结合
+和- 左结合
*、/、% 左结合
^ 右结合
一元运算符 - 不结合
++和-- 不结合

选择此优先级是为了使符合POSIX的bc程序能够正确运行。这将导致关系运算符和逻辑运算符在与赋值表达式一起使用时有一些不寻常的行为。例如下面的表达式:

a = 3<5

大多数C程序员会假设这会将“3<5”(值1)的结果赋给变量“a”,这在bc中所做的是将值3赋给变量“a”,然后比较3到5。在使用关系运算符和逻辑运算符与赋值运算符时,最好使用括号。

在bc中还提供了一些特殊的表达式。这些表达式与用户定义的函数和标准函数有关。它们都以“名称(参数)”的形式出现。有几个标准函数:

  1. length(expr),计算表达式结果的有效位数。
  2. read(),Read函数(一个扩展)将从标准输入中读取一个数字,而不管该函数发生在何处。注意,这可能会导致标准输入中的数据和程序混合出现问题。这个函数的最佳使用是在一个已经编写好的程序中,这个程序需要用户输入,但绝不允许从用户输入程序代码。读函数的值是从标准输入中读取的数字,使用转换基的变量ibase的当前值。
  3. scale ( expr ),这个函数的值是expr表达式中小数点之后的位数。
  4. sqrt ( expression ),函数的结果是表达式的开方值。

# 关于语句

语句(在大多数代数语言中)提供表达式计算的顺序。在bc中,语句被“尽快”执行。执行发生在遇到的换行符的时候,并且有一个或多个完整的语句。由于这种立即执行,换行符在bc中非常重要。事实上,分号和换行符都用作语句分隔符。如果换行符放置不当,将导致语法错误。因为换行符是语句分隔符,所以可以使用反斜杠字符隐藏换行符。()在bc中显示为空格而不是新行。语句列表是由分号和换行符分隔的一系列语句。

1.表达式

这条语句做两件事之一。如果表达式以“<变量><赋值>.”开头,则被认为是赋值语句。如果表达式不是赋值语句,则计算表达式并将表达式打印到输出。在打印数字之后,将打印换行符。例如,“a=1”是一个赋值语句和“(a=1)”是一个具有内嵌赋值的表达式。输出基obase的有效值是2~ BC_BASE_MAX。对于基数2至16,通常采用书写数字的方法。对于大于16的基数,bc使用多字符数字方法将每个较高的基数打印成以10为基数的数据。由于数字具有任意精度,一些数字可能无法在一条输出线上打印。这些长数字将被分割,以“\”作为一行上的最后一个字符,每行打印的最大字符数为70个。由于bc的交互性,打印一个数字会导致最后将打印值赋值给特殊变量“last”的副作用。这允许用户恢复打印的最后一个值,而不必重新键入打印数字的表达式。将last变量赋值为“最后一个值”是合法的,并将最后一个打印的值用指定的值覆盖。新赋值将保持不变,直到打印下一个数字或将另一个值分配给“last”为止。

2.字符串

字符串被打印到输出。字符串以双引号开始,包含所有字符直到下一个双引号字符。所有字符都是字面意思,包括任何换行符。字符串后不打印换行符。

3.打印列表

print语句(扩展)提供了另一种输出方法。“list”是由逗号分隔的字符串和表达式的列表。每个字符串或表达式都按列表的顺序打印。不打印终止换行符。表达式的将被计算出值,最后将其值打印并分配给变量“last”。打印语句中的字符串将打印到输出中,并可能包含特殊字符。特殊字符以反斜杠字符“\”开始。bc识别的特殊字符是“a”(警报或钟)、“b”(反斜杠)、“f”(表单提要)、“n”(换行符)、“r”(回车)、“q”(双引号)、“t”(制表符)和“\”(反斜杠)。反斜杠后面的任何其他字符都将被忽略。

4.语句列表

这是复合语句。它允许将多个语句组合在一起执行。

5.if (表达式) statement 1 [else statement 2]

if语句根据表达式的值决定执行statement 1或statement 2。如果表达式为非零,则执行statement 1。如果存在statement 2,且表达式的值为0的时候执行statement 2。

6.while ( expression ) statement

while语句将在表达式为非零时执行语句。它在每次执行语句之前计算表达式。循环的终止是由零表达式值或break语句的执行引起的。

7.for ( [expression1] ; [expression2] ; [expression3] ) statement

for语句控制语句的重复执行。表达式1是在循环之前计算的。表达式2是在每次语句执行之前计算的。如果表达式2为非零,则计算语句;如果为零,则终止循环。每次执行语句后,计算表达式3。在重新计算表达式2之前,如果未找到表达式1或表达式3,则不会在计算值的点上对其进行任何计算。

8.break

break语句用来强制退出,通常用在for语句或者while语句中。

9.continue

continue语句用来结束本次循环。

10.halt

halt语句会导致bc程序退出。

11.return

函数返回0.

12.return expr

返回表达式的值。

13.伪语句,这些语句不会执行,他们在编译的时候才会起作用。下面列出伪语句

​ - a. limits,打印由于bc版本而产生的限制 - b. quit,遇到quit指令的时候就会退出bc,无论它出现在什么地方。例如“if (0 == 1) quit”就会导致退出bc - c. warranty,打印较长的授权通知

# 函数

1.函数

bc中的函数总是计算一个值并将其返回给调用者。函数定义是“动态的”,在输入中遇到定义之前,函数是未定义的。然后使用该定义,直到遇到相同名称的另一个定义函数。然后,新定义取代旧的定义。函数定义方式如下:

define name ( parameters ) { newline
auto_list  statement_list }

函数的调用很简单“name(parameters)”。

2.参数

参数是数字或数组。在函数定义中,可以有0个或者多个参数,通过逗号分隔开。所有参数都是通过值参数调用的。数组是通过符号“name[]在参数定义中指定的。在函数调用中,实参是数字参数的完整表达式。相同的符号。数组的定义和传值使用相同的符号。命名数组通过值传递给函数。由于函数定义是动态的,因此在调用函数时会检查参数号和类型。参数数量或类型的不匹配都会导致运行时错误。对未定义函数的调用也会出现运行时错误。

3.auto_list

“auto_list”是供“本地”使用的变量的可选列表。auto_list的语法(如果存在)是“autoname,…;”。(分号是可选的。)每个名称都是自动变量的名称。数组可以使用与参数相同的表示法来指定。这些变量的值在函数开始时被推入堆栈中。然后将变量初始化为零,并在函数的整个执行过程中使用。在函数退出时,这些变量被弹出,以便恢复这些变量的原始值(在函数调用时)。这些参数实际上是自动变量,它们被初始化为函数调用中提供的值。自动变量不同于传统局部变量,因为如果函数A调用函数B,B可以使用相同的名称访问函数A的自动变量,除非函数B调用它们为自动变量。由于自动变量和参数被推到堆栈上,bc支持递归函数。

4.函数体

函数体是一系列bc语句的列表。同样,语句用分号或换行符分隔。返回语句导致函数的终止和值的返回。返回语句有两个版本。第一个形式“return”将值0返回给调用表达式。第二种形式“return (表达式)”计算表达式的值并将该值返回给调用表达式。在每个函数的末尾有一个隐含的“return (0)”。这允许一个函数终止并返回0,而不需要显式返回语句。

函数还会改变变量ibase的用法。函数体中的所有常量都将在函数调用时使用ibase的值进行转换。在函数执行过程中,ibase的更改将被忽略,但标准函数读取除外,后者将始终使用ibase的当前值来转换数字。

当前版本的bc,在函数中添加了几个扩展。首先,定义的格式稍微放松了一些。标准要求开始大括号与定义关键字在同一行,所有其他部分必须在下面的行上。这个版本的bc将允许之前的任何数目的换行符。在函数的开头支撑之后,例如,下面的定义是合法的:

define  d  (n)  { return  (2*n); }
define  d  (n)
{ return  (2*n); }

5.void类型

函数可以定义为void。空函数不返回值,因此可能不会在任何需要值的地方使用。空函数在输入行调用时不会产生任何输出。关键字void放在关键字定义和函数名称之间。例如,请考虑下面的例子

define  py (y)  { print "--->", y, "<---", "0; }
define  void  px (x)  { print "--->", x, "<---", "0; }
py(1)
--->1<---
0          //由于py不是void,因此有默认返回值,因此这里打印了它的返回值,
px(1)
--->1<---      //px是void类型,最后不会打印返回值

此外,还为数组添加了按变量调用。为了申明一个数组变量,函数中的数组参数是这样定义的“name[]” 。

# 数学库

1.如果使用“-l”选项调用bc,则预加载一个数学库,并将默认精度设置为20。数学库中有一下的函数:

  • s(x),计算x的正弦值,x是弧度值。
  • c(x),计算x的余弦值,x是弧度值。
  • a(x),计算x的反正切值,返回弧度。
  • l(x),计算x的自然对数。
  • e(x),e的x次方。
  • j(n,x),从n到x的阶数。

2.例子

下面的句子可以将“pi”的值赋值给shell变量pi

pi = $(echo  "scale=10; 4*a(1)" ,  bc  -l)

下面的句子就是数学库中e的次方定义方式

scale = 20
/* Uses the fact that e^x = (e^(x/2))^2
    When x is small enough, we use the series:
    e^x = 1 + x + x^2/2! + x^3/3! + ...
*/
define e(x) {
    auto  a, d, e, f, i, m, v, z 

    /* Check the sign of x. */
    if (x<0) {
        m = 1
        x = -x
    }
 
    /* Precondition x. */
    z = scale;
    scale = 4 + z + .44*x;
    while (x > 1) {
        f += 1;
        x /= 2;
    }

    /* Initialize the variables. */
    v = 1+x
    a = x
    d = 1
    for (i=2; 1; i++) {
        e = (a *= x) / (d *= i)
        if (e == 0) {
            if (f>0) while (f--)  v = v*v;
            scale = z
            if (m) return (1/v);
        return (v/1);
        }
        v += e
    }
}

下面的语句实现一个计算支票簿余额的简单程序

scale=2
print "\nCheck book program!\n"
print "  Remember, deposits are negative transactions.\n"
print "  Exit by a 0 transaction.\n\n" 

print "Initial balance? "; bal = read()
bal /= 1
print "\n"
while (1) {
    "current balance = "; bal
    "transaction? "; trans = read()
    if (trans == 0) break;
    bal -= trans
    bal /= 1
}
quit

下面的语句采用递归的方式计算x的阶乘

define f (x) {
    if (x <= 1) return (1);
    return (f(x-1) * x);
}

# readline和libedit选项

可以编译GNU bc(通过一个配置选项)来使用GNU readline输入编辑器库或bsd libedit库。这允许用户在将行发送到bc之前进行编辑。它还允许保存以前键入的行的历史记录。当选择此选项时,bc还有一个特殊变量。变量“history”是保留的历史记录行数。对于readline,值-1表示不限制历史记录的行数,0将禁用历史记录功能,默认值为100。

# 差别

这个版本的bc是从POSIX P 1003.2/D11草案中实现的,包含了与草案和传统实现相比的一些区别和扩展,它不是以传统的方式使bc(1)实现的,这个版本是一个解析和运行程序字节代码转换的单一进程。这里有一个“无文档”选项(-c),它导致程序将字节码输出到标准输出,而不是运行它。它主要用于调试解析器和准备数学库。差异的一个主要来源是扩展,下面列出一些差异和扩展:

  1. LANG环境变量,此版本在处理lang环境变量和从lc_开始的所有环境变量时不符合POSIX标准。
  2. 名字,传统和POSIX bc都有用于函数、变量和数组的单字母名称。它们被扩展为以字母开头的多字符名称,可以包含字母、数字和下划线字符。
  3. 字符串,字符串不允许包含NUL字符。POSIX表示所有字符都必须包含在字符串中。
  4. last,POSIX bc中没有last变量。
  5. 比较,POSIX bc只允许在if语句、while语句和for语句的第二个表达式中进行比较。
  6. if语句,POSIX bc中if语句没有else。
  7. for语句,POIX bc中要求for语句中的3个表达式都必须具备。
  8. &&,||,!,POSIX bc中没有逻辑运算。
  9. read,POSIX bc没有read功能。
  10. 打印语句,POSIX bc没有打印语句。
  11. continue语句,POSIX bc没有continue语句。
  12. return,POSIX bc要求return的表达式加括号。
  13. 数组参数,POSIX bc不(目前)完全支持数组参数。POSIX语法允许函数定义中的数组,但没有提供将数组指定为实际参数的方法。(这很可能是语法上的疏忽。)传统的bc实现只通过值数组参数进行调用。
  14. 函数,POSIX bc要求函数开头的大括号和define关键字在同一行,语句在下一行。
  15. =+, =-, =*, =/, =%, =^。POSIX bc不要求定义这些“旧样式”赋值操作符。此版本可能允许这些“旧样式”赋值。使用限制语句查看安装的版本是否支持它们。如果它确实支持“旧样式”赋值运算符,则“a=-1”语句将使a减少1,而不是将a设置为值-1。
  16. 数字中的空格,bc的其他实现允许数字空格。例如,“x=1 3”将值13赋值给变量x。相同的语句将导致bc版本中的语法错误。
  17. 错误和执行,在程序中发现语法和其他错误时,此实现与其他实现的代码不同。如果在函数定义中发现语法错误,则错误恢复机制将尝试查找语句的开头并继续解析函数。一旦在函数中发现语法错误,该函数将不可调用并变为未定义。交互执行代码中的语法错误将使当前执行块失效。执行块由在完整语句序列之后出现的行尾终止。例如

a = 1 b = 2

这个语句有两个执行块,而下面的语句

{a = 1 b = 2}

只有一个执行块。任何运行时错误都会终止当前的执行块,而警告则不会。

  1. 中断,在交互会话期间,SIGINT信号(通常由终端上的“ctrl+c“生成)将导致当前执行块的执行中断。它将显示一个“运行时”错误,指示哪个功能被中断。在所有运行时结构被清除后,将打印一条消息通知用户bc准备好接收更多的输入。所有先前定义的函数都保留定义,所有非自动变量的值是中断点的值。在清理过程中,所有自动变量和函数参数都会被移除。对于一个非交互式会话,SIGINT信号将终止bc的整个运行。

# 限制

下面列出当前bc程序的一些限制,有一些限制可能已经被用户修改过。

  1. BC_BASE_MAX,最大输出基设置为999。最大输入基为16。
  2. BC_DIM_MAX,这是当前分布的65535以内的任意限制,每个机器可能都不一样。
  3. BC_SCALE_MAX,小数点前后的位数都由INT_MAX限制。
  4. BC_STRING_MAX,字符串中的字符字数由INT_MAX限制。
  5. exponent,指数运算中的指数值由LONG_MAX限制。
  6. variable names,当前对每个简单变量、数组和函数名字的限制32767。

# 环境变量

下面的环境变量由bc程序来控制

  1. POSIXLY_CORRECT,和“-s”选项一样。
  2. BC_ENV_ARGS,这是另一种获取bc参数的机制。格式与命令行参数相同。这些参数是先处理的,因此环境参数中列出的任何文件在任何命令行参数文件之前都会被处理。这允许用户设置“标准”选项和文件,以便在每次调用环境变量中的文件通常包含用户希望在每次运行bc时定义的函数定义。
  3. BC_LINE_LENGTH,这应该是一个整数,指定数字输出行中的字符数。这包括用于长数字的反斜杠和换行符。,如果值是0,将禁用多行功能。此变量的任何其他值如果小于3,则将行长设置为70。

# 举例

简单计算

[root@192 ~]$ bc
bc 1.06.95   # 欢迎语句
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type 'warranty'.
12+23    # 输入加法表达式,回车
35       # 得到结果
100/25   # 输入除法表达式,回车
4        # 得到结果
quit     # 退出指令
[root@192 ~]$

执行for循环语句

for(i=0; i<3; i++){print "hello\n"}  #这是一个打印语句
hello
hello
hello

从文件读取内容并且执行bc

[root@localhost /]$ cat test.c    #查看文件的内容,里面全是bc语句
/*define 3 functions add,sub,mul*/
define add(x,y){
    return x+y;
}

define sub(x,y){
    return x-y;
}

define mul(x,y){
    return x*y;
}

/*for statement*/
for(i=0;i<3;i++){
    print "bc test ",i,"\n";
}
/*print statement*/
print "10+5=",add(10,5),"\n"
print "10-5=",sub(10,5),"\n"
print "10&5=",mul(10,5),"\n"
/*quit bc program*/
quit

[root@localhost /]$ bc test.c  #bc程序从文件获取到代码,然后执行
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
bc test 0
bc test 1
bc test 2
10+5=15
10-5=5
10&5=50