参考文章:
一、内容简介
利用Java程序绘制一个简单的科学计算器的UI界面,并能实现基本的加减乘除运算,在本项目中,计算器除实现了上述功能之外,还能够进行三角函数的运算(sin,cos,tan),指数运算,幂运算,对数运算,获取系统时间,进制转换,阶乘,取余,取倒数,退位,清零以及对一元二次函数进行定积分和微分的操作。
二、设计步骤
参考网络开源代码的写作格式,应用程序继承自框架类(JFrame),采用BorderLayout边缘布局。在本程序中按钮面板JPanel采用的是6行7列的网络布局,为了美观在最后补齐了两位空按钮。
1.实现计算功能的方法思路
首先区分出两类大方向:单运算操作还是双运算操作。单运算操作用input捕获,双运算操作用command捕获。触发按钮事件时,先判断是或不是数字、“+/-”、小数点“.”,是的话就将“-”、数字、小数点“.”分别写入文本框并进行相应的处理,都不是的话则跳到doOperation()执行运算同时将运算符存放在preOperater中。触发按钮事件时,要进一步分析,是重新开始计算时触发按钮事件还是计算中间触发的按钮事件。
2.主体代码
具体代码如下:
public class Calculator extends JFrame{
//用于判断是否重新开始
private boolean start = true;
private double result = 0;
//存放加减乘除等于等
private String command = "=";
private JTextField jTextField;
private JPanel jPanel = new JPanel();
private JButton[] jButtons;
//设置输出格式
private DecimalFormat dof;
//用构造方法进行必要的设置
public Calculator() {
this.setTitle("科学计算器");
this.setSize(600, 400);
this.setLocationRelativeTo(null);
dof = new DecimalFormat("#.#####");//保留五位小数
//添加文本域
jTextField = new JTextField(30);
jTextField.setText("");
jTextField.setEditable(false);
this.add(jTextField,"North");
//添加按钮
jPanel.setLayout(new GridLayout(6,7,3,3));
String name[] = {
"+/-","π","1/X","AC","/","*","DEL","X^2","X^3",
"X^y","7","8","9","-","X!","√X","3^√X","4","5",
"6","+","sin","cos","tan","1","2","3","%",
"2进制","10进制","定积分","微分","0",".","=",
"exp","log","ln","cot","time"," "," "
};
jButtons = new JButton[name.length];
MyActionListener actionListener= new MyActionListener();
//利用循环创建按钮对象并添加事件监听器
for(int i = 0; i < name.length; i++) {
jButtons[i] = new JButton(name[i]);
jButtons[i].addActionListener(actionListener);
//设置按钮背景颜色
jButtons[i].setBackground(Color.lightGray);
if(name[i].equals("="))
jButtons[i].setBackground(Color.RED);
else if((int)name[i].charAt(0)>=48 && (int)name[i].charAt(0)<=57
&& name[i].length() == 1)
jButtons[i].setBackground(Color.WHITE);
// else if(name[i].equals("DEL"))
// jButtons[i].setBackground(Color.GRAY);
jPanel.add(jButtons[i]);
}
this.add(jPanel);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
}
//用内部类实现事件监听器
class MyActionListener implements ActionListener{
//按钮被单击
public void actionPerformed(ActionEvent e) {
String input = e.getActionCommand();
//开始
if(start) {
if((int)input.charAt(0)>=48 && (int)input.charAt(0)<=57
&& input.length() == 1 ) {
jTextField.setText(""+input);
}
if(input.equals("+/-")) {
jTextField.setText("-");
}
if(input.equals("π")) {
jTextField.setText(""+Math.PI);
}
start = false;
if(input.equals("AC"))
jTextField.setText("");
}
//0~9数字等非运算符
else if((int)input.charAt(0)>=48 && (int)input.charAt(0)<=57
&& input.length() == 1 || input.equals(".")){
jTextField.setText(jTextField.getText()+input);
}
//实现所有 数组+运算符 的运算
//实现清零键
else if(input.equals("AC"))
jTextField.setText("");
//实现退格键
else if(input.equals("DEL")) {
if(jTextField.getText().length() > 0){
jTextField.setText(jTextField.getText().substring(0,jTextField.getText().length()-1));
}
}
//实现正弦三角函数
else if(input.equals("sin")) {
result = Math.sin(Double.parseDouble(jTextField.getText()));
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
jTextField.setText(dof.format(result));
start = true;
}
//实现余弦三角函数
else if(input.equals("cos")) {
result = Math.cos(Double.parseDouble(jTextField.getText()));
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
//实现余切三角函数
else if(input.equals("cot")) {
result = 1.0/Math.tan(Double.parseDouble(jTextField.getText()));
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
//实现正切三角函数
else if(input.equals("tan")) {
result = Math.tan(Double.parseDouble(jTextField.getText()));
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
//实现十进制到二进制的转化
else if(input.equals("2进制")) {
String result2 = Integer.toBinaryString(Integer.parseInt(jTextField.getText()));
jTextField.setText(""+getPrettyNumber(result2));
start = true;
}
//实现二进制到十进制的转化
else if(input.equals("10进制")) {
try {
String result2 = Integer.valueOf(jTextField.getText(),2).toString();
jTextField.setText(""+getPrettyNumber(result2));
}catch(NumberFormatException exception) {
JOptionPane.showMessageDialog(null, "对不起,数字错误,请重新输入!", "Error!", JOptionPane.ERROR_MESSAGE);
throw new NumberFormatException("数字格式错误");
}finally {
start = true;
}
}
//实现1/x
else if(input.equals("1/X")) {
result = 1 / Double.parseDouble(jTextField.getText());
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
//实现平方计算
else if(input.equals("X^2")) {
result = Math.pow(Double.parseDouble(jTextField.getText()), 2);
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
//实现立方计算
else if(input.equals("X^3")) {
result = Math.pow(Double.parseDouble(jTextField.getText()), 3);
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
//实现阶乘
else if(input.equals("X!")) {
if(Double.parseDouble(jTextField.getText()) < 0) {
JOptionPane.showMessageDialog(null, "对不起,阶乘计算不能为负数", "Error!", JOptionPane.ERROR_MESSAGE);
jTextField.setText("对不起,阶乘计算不能为负数");
start = true;
throw new IllegalArgumentException("阶乘计算出现负数");
}else {
int sum;
sum = factorial(Integer.parseInt(jTextField.getText()));
jTextField.setText(Integer.toString(sum));
start = true;
}
}
//实现百分号计算
else if(input.equals("%")) {
result = Double.parseDouble(jTextField.getText())/ 100.0;
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
//实现开平方根
else if(input.equals("√X")) {
if(Double.parseDouble(jTextField.getText()) < 0){
JOptionPane.showMessageDialog(null, "负数不能开根", "Error!", JOptionPane.ERROR_MESSAGE);
jTextField.setText("对不起,开方计算不能为负数");
start = true;
throw new IllegalArgumentException("开方计算出现负数");
}else{
result = Math.sqrt(Double.parseDouble(jTextField.getText()));
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
}
//实现开立方根
else if(input.equals("3^√X")) {
result = Math.pow(Double.parseDouble(jTextField.getText()),1.0/3);
jTextField.setText(""+getPrettyNumber(Double.toString(result)));
start = true;
}
//实现获取当前时间
else if(input.equals("time")) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
jTextField.setText(df.format(System.currentTimeMillis()));
start = true;
}
else if(input.equals("微分")){
String title = JOptionPane.showInputDialog(null, "请输入一元二次方程的三个参数和微分点,以空格分开:\n", "title", JOptionPane.PLAIN_MESSAGE);
String[] strs = title.split(" ");
result = derivative(Double.parseDouble(jTextField.getText()),0.0000001, strs);
// jTextField.setText(""+getPrettyNumber(Double.toString(result)));
jTextField.setText(dof.format(result));
start = true;
}
else if(input.equals("exp")){
result = Math.exp(Double.parseDouble(jTextField.getText()));
jTextField.setText(dof.format(result));
start = true;
}
else if(input.equals("ln")){
result = Math.log(Double.parseDouble(jTextField.getText()));
jTextField.setText(dof.format(result));
start = true;
}
else if(input.equals("定积分")){
String title = JOptionPane.showInputDialog(null, "请输入一元二次方程的三个参数和积分区间,以空格分开:\n", "title", JOptionPane.PLAIN_MESSAGE);
String[] str = title.split(" ");
// result = defintegration(result, Double.parseDouble(jTextField.getText()), 10000);
result = defintegration(Double.parseDouble(str[3]), Double.parseDouble(str[4]), 10000, str);
jTextField.setText(dof.format(result));
start = true;
}
//实现加减乘除等 数字+运算符+数字 形式的运算
else {
if(!start) {
if(command.equals("+"))
result += Double.parseDouble(jTextField.getText());
else if(command.equals("-"))
result -= Double.parseDouble(jTextField.getText());
else if(command.equals("*"))
result *= Double.parseDouble(jTextField.getText());
else if(command.equals("/")) {
if(Double.parseDouble(jTextField.getText()) != 0) {
result /= Double.parseDouble(jTextField.getText());
}else {
jTextField.setText(""+"对不起,除数不能为零");
JOptionPane.showMessageDialog(null, "对不起,除数不能为零", "Error!", JOptionPane.ERROR_MESSAGE);
command = "=";
start = true;
throw new ArithmeticException("除数为零");
}
}
else if(command.equals("="))
result = Double.parseDouble(jTextField.getText());
else if(command.equals("X^y"))
result = Math.pow(result, Double.parseDouble(jTextField.getText()));
else if(command.equals("log")) //先输入底数再输入真数
result = Math.log( Double.parseDouble(jTextField.getText()))/Math.log(result);
// jTextField.setText(""+getPrettyNumber(Double.toString(result)));
jTextField.setText(dof.format(result));
command = input;
start = true;
}
}
}
}
//去掉小数点后没用的0
public static String getPrettyNumber(String number) {
return BigDecimal.valueOf(Double.parseDouble(number))
.stripTrailingZeros().toPlainString();
}
//用循环计算阶乘
public static int factorial(int num) {
int sum = 1;
for(int i = 1;i <= num; i++){
sum *= i;
}
return sum;
}
//面积法计算定积分
public static double defintegration(double a, double b, int accuracy, String[] str){
double sum = 0;
// int accuracy = ;
double step = (b - a)/accuracy;
for(int i = 1;i <= accuracy; i++){
double x = a + step*i;
sum = sum + f(x,str) * step;
}
return sum;
}
//定义函数
public static double f(double x, String[] str){
return Double.parseDouble(str[0])*x*x + Double.parseDouble(str[1])*x + Double.parseDouble(str[2]);
}
//定义法求微分
public static double derivative(double a,double accuracy, String[] str){
return (f(a + accuracy, str)-f(a - accuracy, str))/(2*accuracy);
}
public static void main(String[] args) {
Calculator Calculator = new Calculator();
}
}
3.运行结果
计算器的UI界面如下图所示:
这里需要对积分和微分按钮解释一下:如果需要进行积分和微分操作,点击相应按钮,会弹出相应的窗口,如下所示:
该项目只能对一元二次方程进行相应操作,需要手动输入一元二次方程的a,b,c三个参数,和积分区间的上下取值(或微分点),点击确定按钮后计算结果显示到文本域内,设定输出格式如下,结果保留五位小数。
dof = new DecimalFormat("#.#####");//保留五位小数
对于计算的方法原理,积分与微分运算的代码实现均是从定义出发,比如积分运算结果是利用面积法将每一个微小步长的长方形面积累加得到,微分运算是利用求导的基础定义式,相关代码如下:
积分运算:
//面积法计算定积分
public static double defintegration(double a, double b, int accuracy, String[] str){
double sum = 0;
// int accuracy = ;
double step = (b - a)/accuracy;
for(int i = 1;i <= accuracy; i++){
double x = a + step*i;
sum = sum + f(x,str) * step;
}
return sum;
}
微分运算:
//定义法求微分
public static double derivative(double a,double accuracy, String[] str){
return (f(a + accuracy, str)-f(a - accuracy, str))/(2*accuracy);
}
关于函数f(x)的定义如下,这里的f(x)相当于定义了一个一元二次方程的外壳,只需要输入方程的abc三个参数:
//定义函数
public static double f(double x, String[] str){
return Double.parseDouble(str[0])*x*x + Double.parseDouble(str[1])*x + Double.parseDouble(str[2]);
}
关于这里定义的str字符串数组,其实是给f(x)函数传递一元二次方程的三个参数用的。下面简单介绍一下代码中获取到弹窗中输入的值并给f(x)函数传参的方法,如下所示:
在showInnputDialog的弹窗中输入的数值以字符串的形式返回给title,这里用到了split()方法将title中的字符串以空格为分隔符分别存储为str数组的各个值,在使用这些数值时只是简单进行了类型转换。
对于其他的计算器的功能由于只是用到了一些现成的方法函数,并且网络上可参考的代码足够多,这里就不再过多赘述。
对于该程序里的对数log运算,这里用到了对数计算的换底公式,因为java的方法Math.log的底数默认为e,计算时先输入底数,点击log按钮,再输入真数,点击“=”可得计算结果。
总结
一些函数的说明:
(1)getActionCommand () : 获取按钮的变量名,该方法返回的是事件源组件的“Label”标签,在事件监听方法public void actionPerformed(ActionEvent e)中使用.
(2)点击按钮获取弹窗的方式采用JOptionPane方法,该程序中使用的是JOptionPane.showInputDialog()方法弹出可输入的文本框。对于JOptionPane方法包含的方法的介绍如下:
showMessageDialog(); 消息对话框
showConfirmDialog(); 选择对话框
showOptionDialog(); 自定义选择对话框
showInputDialog(); 输入对话框
JOptionPane的一些参数介绍如下:
parentComponent 设置对话框的父级容器
message 消息内容
initialSelectionValue 设置默认选中的选项
initialValue 设置默认选中的按钮
title 对话框标题
icon 设置自定义对话框的图像
messageType 消息类型,每种消息类型提供一个默认的图像
optionType 按钮类型
options 自定义按钮数组,自定义选项按钮的文字
selectionValues 自定义选项数组,用于定义下拉框
说明:对于本代码中一些稍显笨拙的操作,还请使用者自行优化并赐教。