dyerac  
dyerac 的天天天蓝
公告
  • blue sky blue sea I love u ^_^
日历
<2006年11月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
统计
  • 随笔 - 36
  • 文章 - 10
  • 评论 - 74
  • 引用 - 0

导航

常用链接

留言簿(1)

随笔分类(49)

随笔档案(36)

文章分类(11)

文章档案(10)

相册

华中科技大学IBM俱乐部

搜索

  •  

积分与排名

  • 积分 - 27778
  • 排名 - 346

最新随笔

最新评论

阅读排行榜

评论排行榜

 

   有感于windows自带计算器的不好使,最近心血来潮按自己需求开发了一个多功能计算器。其核心的一部分来自于网上的学习资料,因此我也不敢私藏代码,特拿出来和大家分享心得。
   计算器功能如下:
    1.支持运行时更换界面皮肤,水印和按钮形状等外观显示。
    2.支持sin, cos, tan, cot, arcsin, arccos, arctan, arccot, !, log, ln, +, -, *, /, %, 乘方等基本运算,支持连续运算并自带PI,E的精确值。
    3.支持表达式计算,支持设置变量,可以轻松的实现公式运算。
    4.无限的结果保存,完全对用户透明,用户可以轻松的存储和读取先前的操作结果. 
    5.能够分析用户操作尤其是表达式中的语法错误,提醒用户而不是返回错误结果。

    

   

 

   

设计思路:

   整个程序的 GUI 由两部分组成。结果存储由 JDialog 完成, JDialog 内含一个 JList 存放结果。主界面由 JMenuBar JTabbedPane 组成, JTabbedPane 的两个子界面分别用作按键式计算器和表达式式计算器。

   关于界面的构建工作全部由 CalculatorFace 类完成,同时按键式计算器的后台计算工作也有该类完成。 Parser 类则负责解析表达式返回计算结果。 CalculatorFace 调用 Parser 进行表达式计算工作。

  在处理按键式计算时,各种计算操作符号显然可以分为两类:单操作数和双操作数。单操作数计算只需要取出显示框中内容,进行计算再将结果显示即可。双操作数计算则显得比较麻烦。因为需要实现连续计算,所以必须保存上次的操作符号和上次输入的结果。为此增加lastOperator字段和number1,number2字段分别存储前次操作符和对应的操作数。
   运行机制如下:
   当按下新键时,将number2->number1,显示框中内容取出作为number2,同时根据lastOperator中内容进行计算(number1 lastOperator number2),计算完成后,将这次按键内容保存到lastOperator中。要注意的是,当“=”按下时,将取消连续运算。因此添加boolean字段continued用来判断是否进行连续运算。
   考虑到用户的误操作,即可能不小心按错了按钮,因此每次运算应以输入新数字前最后一个操作符号为准。因此,continued事实上也控制运算是否进行,每次双操作运算后,continued赋值为false,输入数字后才重新为真。当continued为false时,程序只是简单的把按键值赋予lastOperator而不进行运算工作。
    而对于数字按键,唯一需要注意的是按下操作键后,再输入数据需要将显示框清空后从头输入;否则新数字将在显示框原有内容尾部添加。completed字段用于控制该情形。上述例外在于,第一次使用计算器时,即使没有按下操作键,也应该重新显示数字。First字段将处理这一情形。

    表达式解析则通过Parser类完成。getToken方法负责将表达式中基本的单元取出。这些基本单元包括:变量,数字和操作符号。运行时,首先将表达式中变量替换成其值,对处理好的表达式从左至右取出单元进行计算。计算采用递归下降的流式处理,调用按计算优先级排列好的各种函数,每种函数处理同一优先级的运算。
    优先级列表为:
括号,阶乘,正负号,三角函数/对数函数/反三角函数,乘方,乘/除/模运算,加减运算。
   下面举例说明表达式的解析过程。表达式
    10 + 5 * B
    有两个项:10和5*B,第二项包括两个因数:5和B,分别是一个数字和一个变量
   再看另外一个例子。表达式
   14 * (7 – C)
   有两个因数:14和(7-C),分别是一个数字和一个圆括号表达式。圆括号表达式包括两个项:一个数字和一个变量。
   上述过程形成了递归下降解析器的基础。递归下降解析器是一组互相递归的方法,这些方法以一种链式方式实现生成规则。在每个适当的步骤上,解析器以代数学规定的正确顺序执行指定的操作。为了解释如何使用生成规则来解析表达式,采用下面的表达式来跟踪解析过程:
   9/3 – (100 + 56)
   整个解析过程如下:
(1) 获得第一项9/3;
(2) 获得第一项的两个因数并完成除法运算,得到结果3;
(3) 获得第二项(100+56)。在这一步启动递归分析过程处理括号内的子表达式;
(4) 获得其中的两项并完成加法运算,得到结果156;
(5) 从第二项的递归计算过程中返回;
(6) 3减去156,答案是-153。

Bugs:
      经过同学的友情测试,现发现bug如下:
(1)无法将――2形式识别为2
(2)乘方运算时,当数字和’!’中出现空格或者括号时无法处理
(3)类似sinlog10形式无法识别,只能识别sin(log10)
以上将在下一版本中修订。

Uncompleted features:
(1)任意进制的计算和转换
(2)角度与弧度的转换
(3)扩大计算范围(即应该使用BigDemical而不是double)
 以上同样会在下一版本中实现。

下面是程序的源代码:

package  cn.com.w4t.calculator;

import  java.awt.BorderLayout;
import  java.awt.Color;
import  java.awt.GridLayout;
import  java.awt.event.ActionEvent;
import  java.awt.event.ActionListener;
import  java.util.Vector;

import  javax.swing.JButton;
import  javax.swing.JDialog;
import  javax.swing.JFrame;
import  javax.swing.JLabel;
import  javax.swing.JList;
import  javax.swing.JMenu;
import  javax.swing.JMenuBar;
import  javax.swing.JMenuItem;
import  javax.swing.JPanel;
import  javax.swing.JScrollPane;
import  javax.swing.JTabbedPane;
import  javax.swing.JTextArea;
import  javax.swing.JTextField;
import  javax.swing.UIManager;
import  javax.swing.border.EtchedBorder;
import  javax.swing.border.LineBorder;
import  javax.swing.border.TitledBorder;

import  org.jvnet.substance.SubstanceLookAndFeel;
import  org.jvnet.substance.theme.SubstancePurpleTheme;
import  org.jvnet.substance.watermark.SubstanceBinaryWatermark;

/**
 * Main Application Interface
 * 
 * 
@author  td
 * 
 
*/

@SuppressWarnings(
" serial " )
public   class  CalculatorFace  extends  JFrame  implements  ActionListener  {

    
/**
     * 按键计算的结果
     
*/

    
private   double  result1;

    
/**
     * 表达式计算的结果
     
*/

    
private   double  result2;

    
/**
     * 二元计算是否完成,完成后下次输入数据时将从头开始而不是在原有数据后添加
     
*/

    
private   boolean  completed  =   false ;

    
/**
     * 是否是第一次进行操作
     
*/

    
private   boolean  first  =   true ;

    
/**
     * 是否进行连续计算,比如"="将取消连续运算
     
*/

    
private   boolean  continued  =   false ;

    
/**
     * 被保存的先前计算结果集
     
*/

    
private  Vector < String >  resultVector  =   new  Vector < String > ();

    
/**
     * 计算表达式
     
*/

    
private  String express;

    
/**
     * 变量值
     
*/

    
private  String variables;

    
/**
     * 表达式解析式
     
*/

    
private  Parser parser  =   new  Parser();

    
/**
     * 操作按键
     
*/

    
private  JButton[] operatorBtns  =   new  JButton[ 20 ];

    
/**
     * 操作符号
     
*/

    
private  String[] op  =   " + " " - " " * " " / " " % " " ^ " " ! " " = " " ln " ,
            
" log " " sin " " cos " " tan " " cot " " arcsin " " arccos " " arctan " ,
            
" arccot " " PI " " E "  }
;

    
/**
     * 数字按键
     
*/

    
private  JButton[] numberBtns  =   new  JButton[ 12 ];

    
/**
     * 保持结果的对话框
     
*/

    
private  JDialog resultHolder;

    
/**
     * 保持结果的列表
     
*/

    
private  JList resultList;

    
/**
     * 一些系统功能按键,显示、隐藏结果记录对话框,存储计算结果,清屏,退格
     
*/

    
private  JButton clearBtn, saveBtn, saveBtn2, backBtn, shBtn, shBtn2,
            submitBtn;

    
/**
     * 主界面
     
*/

    
private  JTabbedPane mainTab;

    
/**
     * 输入框
     
*/

    
private  JTextField resultField1, resultField2, inputField;

    
/**
     * 错误标签
     
*/

    
private  JLabel errorLabel1, errorLabel2;

    
/**
     * 监听器
     
*/

    
private  ActionListener numberListener  =   new  NumberListener(),
            oneOperatorListener 
=   new  OneOperatorListener(),
            twoOperatorListener 
=   new  TwoOperatorListener();

    
/**
     * 菜单
     
*/

    
private  JMenu fileM, setM, helpM;

    
/**
     * 菜单栏
     
*/

    
private  JMenuBar menu;

    
/**
     * 变量输入框
     
*/

    
private  JTextArea variableArea;

    
/**
     * 操作数
     
*/

    
private  String number1  =   " 0.0 " , number2  =   " 0.0 " ;

    
/**
     * 上一次操作符号
     
*/

    
private  String lastOperator;

    
//  private JMenuItem fileItem, settingsItem, helpItem;

    
public  CalculatorFace()  {
        
super ();
        init();
    }


    
/**
     * 初始化属性
     * 
     
*/

    
private   void  initReference()  {
        
//  init all buttons
         for  ( int  i  =   0 ; i  <   10 ; i ++ {
            numberBtns[i] 
=   new  JButton(Integer.toString(i));
            numberBtns[i].addActionListener(numberListener);
            numberBtns[i].setBorder(
null );
        }

        numberBtns[
10 =   new  JButton( " . " );
        numberBtns[
10 ].addActionListener(oneOperatorListener);
        numberBtns[
10 ].setBorder( null );
        numberBtns[
11 =   new  JButton( " +/- " );
        numberBtns[
11 ].setBorder( null );
        numberBtns[
11 ].addActionListener(oneOperatorListener);
        
for  ( int  i  =   0 ; i  <  op.length; i ++ {
            operatorBtns[i] 
=   new  JButton(op[i]);
            
if  (i  <   6 )
                operatorBtns[i].addActionListener(twoOperatorListener);
            
else
                operatorBtns[i].addActionListener(oneOperatorListener);
            operatorBtns[i].setBorder(
null );
        }

        clearBtn 
=   new  JButton( " Clear " );
        clearBtn.addActionListener(
this );
        clearBtn.setBorder(
new  EtchedBorder(EtchedBorder.LOWERED));
        saveBtn 
=   new  JButton( " Save " );
        saveBtn.addActionListener(
this );
        saveBtn.setBorder(
new  EtchedBorder(EtchedBorder.LOWERED));
        saveBtn2 
=   new  JButton( " Save " );
        saveBtn2.addActionListener(
this );
        saveBtn2.setBorder(
new  EtchedBorder(EtchedBorder.LOWERED));
        backBtn 
=   new  JButton( " Back " );
        backBtn.addActionListener(
this );
        backBtn.setBorder(
new  EtchedBorder(EtchedBorder.LOWERED));
        shBtn 
=   new  JButton( " S/H " );
        shBtn.addActionListener(
this );
        shBtn.setBorder(
new  EtchedBorder(EtchedBorder.LOWERED));
        shBtn2 
=   new  JButton( " S/H " );
        shBtn2.addActionListener(
this );
        shBtn2.setBorder(
new  EtchedBorder(EtchedBorder.LOWERED));
        submitBtn 
=   new  JButton( " Submit " );
        ParserListener p 
=   new  ParserListener();
        submitBtn.addActionListener(p);
        submitBtn.setBorder(
new  EtchedBorder(EtchedBorder.LOWERED));

        
//  init textfileds
        resultField1  =   new  JTextField( " 0.0 " );
        resultField1.setEditable(
false );
        resultField2 
=   new  JTextField( " 0.0 " );
        resultField2.setEditable(
false );
        inputField 
=   new  JTextField();
        inputField.addActionListener(p);
        inputField.setToolTipText(
" 输入表达式,可以含有变量,但是变量必须在下面定义 " );
        variableArea 
=   new  JTextArea();
        variableArea.setToolTipText(
" 输入变量值,约定形式为a=1,b=2,请勿使用s,c,t等关键字作为变量名 " );

        
//  init labels
        errorLabel1  =   new  JLabel();
        errorLabel1.setForeground(Color.red);
        errorLabel2 
=   new  JLabel();
        errorLabel2.setForeground(Color.red);

        mainTab 
=   new  JTabbedPane();

        
//  init menu
        menu  =   new  JMenuBar();
        fileM 
=   new  JMenu( " File " );
        fileM.setMnemonic(
' F ' );
        setM 
=   new  JMenu( " Settings " );
        setM.setMnemonic(
' S ' );
        helpM 
=   new  JMenu( " Help " );
        helpM.setMnemonic(
' H ' );
    }


    
/**
     * 初始化菜单
     * 
     
*/

    
private   void  initMenu()  {
        menu.add(fileM);
        menu.add(setM);
        menu.add(helpM);
        fileM.add(
new  JMenuItem( " Exit " ));
        setM.add(
new  JMenuItem( " 二进制 " ));
        setM.add(
new  JMenuItem( " 八进制 " ));
        setM.add(
new  JMenuItem( " 十进制 " ));