菜鸟的安卓计算器之路

因课程安排,需要在安卓上实现一个简易的计算器。老师已经给出界面,让我们在网上查阅相关代码后着手开始进行逻辑代码方面的书写。
但是想挑战一下自己,尝试在不参考别人的代码情况下能否自行开发。目前思路是,为每一个按钮绑定一个事件,每一次点击按钮后都从文本框中获取相应的文本并且转为StringBuffer对象,然后添加对应按钮的字符,调用setText从而实现对文本框的更新,期间应包括多种规范输入的控制,比如一个数不能出现两次以上小数点,其他非数字字符同理,开头和结尾也不能有除了等号外的非数字字符;最后再按下等号时截取文本框表达式,考虑到优先级问题,选用正则表达式匹配出乘法和除法先行运算,然后计算加减法,运算出的结果再和匹配出的表达式进行置换,最后得出结果。

后续逐步更新进度。

———————————————————————————
在一小时的奋斗下,终于成功地写出了乘法的算法。其间碰到了不少坑,我原来的想法是从正则表达式匹配出来的乘法字符串通过split方法分解出数字,再由String转double进行计算,计算出来的结果再转回String后通过replace方法替换掉匹配出来的乘法表达式。但是在运行后发现这并不能成功实现,原因在于double和float在计算的情况下都涉及到精度的问题,无法进行准确的计算,这时候就需要引入一个BigDecimal类了。
要说花费时间最久的应该就是正则表达式的书写了吧,因为一开始就定下一个有缺陷的正则表达式了,导致后面的逻辑代码自己也写得糊里糊涂的。
在最后总归是写出来了,这里贴上代码,仅实现了乘法,代码有点粗糙,毕竟是纯开发性的,不考虑到美观,后面完成以后会进行改良。

import java.math.BigDecimal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author:perchersummer
 * @Date:2019/3/12 17:02
 * @Version 1.8
 */
public class main {
    public static void main(String args[]){ 
        String str1 = "1.3*2.3*3.14+4-5*5";
        char mark[]={'+','-','*','/','.'};
        for(int i=0;i<5;i++){
            if(str1.startsWith(String.valueOf(mark[i])))System.out.println("error");
        } //是否以符号开头
        for(int i=0;i<5;i++){
            if(str1.endsWith(String.valueOf(mark[i])))System.out.println("error");
        } //是否以符号结尾

        String regex = "\\d+(\\.\\d+)?(\\*\\d+(\\.\\d+)?)+"; // 数*数*数……
        Pattern r = Pattern.compile(regex);
        Matcher m = r.matcher(str1);

        while (m.find()){
            String []temp=m.group(0).split("\\*"); //在匹配结果中获取乘号两边的数
            System.out.println(m.group(0)); //匹配的表达式
            String result = getResult(temp);
            System.out.println(result); //理论上变为9.3886
            str1 = str1.replace(m.group(0),result);   //理论上最后结果应为9.3886+4-25
            System.out.println(str1);
        }//else { System.out.println("no match");}
    }

    static String getResult(String temp[]){
        //计算两数相乘的结果 因需要精确计算,所以使用BigDecimal类
        BigDecimal big = new BigDecimal(temp[0]);
        for(int i =1;i<temp.length;i++){
            big = big.multiply(new BigDecimal(temp[i]));
        }
        return big.toString();
    }
}

这是运行结果图:
运行结果


重新对逻辑代码进行了完善,并且加入一点简易的函数封装。代码如下
public class main {
    public static void main(String args[]){ 
        String str1 = "3*3*2+3+4+5*5+9/3";
        char mark[]={'+','-','*','/','.'};
        for(int i=0;i<5;i++){
            if(str1.startsWith(String.valueOf(mark[i])))System.out.println("error");
        } //是否以符号开头
        for(int i=0;i<5;i++){
            if(str1.endsWith(String.valueOf(mark[i])))System.out.println("error");
        } //是否以符号结尾


        String regexMul = "\\d+(\\.\\d+)?(\\*\\d+(\\.\\d+)?)+";
        String regexDvi = "\\d+(\\.\\d+)?(\\/\\d+(\\.\\d+)?)+";
        String regexAdd = "\\d+(\\.\\d+)?(\\+\\d+(\\.\\d+)?)+";
        String regexSub = "\\d+(\\.\\d+)?(\\-\\d+(\\.\\d+)?)+";

        str1=lastResult(str1,regexDvi,"\\/",'/');   //必须按顺序/ * - +
        str1=lastResult(str1,regexMul,"\\*",'*');
        str1=lastResult(str1,regexSub,"\\-",'-');
        str1=lastResult(str1,regexAdd,"\\+",'+');
        
        System.out.println("最终结果:"+str1);

    }

    static String lastResult(String str1,String regex,String re,char ch){

        Pattern r = Pattern.compile(regex);
        Matcher m = r.matcher(str1);
        
        while (m.find()){
            String []temp=m.group(0).split(re); //在匹配结果中获取符号两边的数
            System.out.println(m.group(0)); //匹配的表达式
            String result = getResult(temp,ch);
            str1 = str1.replace(m.group(0),result);   //
        }
        return str1;
    }

    static String getResult(String temp[],char ch){
        //需要精确计算,所以使用BigDecimal类
        BigDecimal big = new BigDecimal(temp[0]);
        switch (ch){
            case '*':
                for(int i =1;i<temp.length;i++){
                    big = big.multiply(new BigDecimal(temp[i]));
                }break;
            case '/':
                for(int i =1;i<temp.length;i++){
                    big = big.divide(new BigDecimal(temp[i]));
                }break;
            case '+':
                for(int i =1;i<temp.length;i++){
                    big = big.add(new BigDecimal(temp[i]));
                }break;
            case '-':
                for(int i =1;i<temp.length;i++){
                    big = big.subtract(new BigDecimal(temp[i]));
                }break;
        }
        return big.toString();
    }
}

332+3+4+55+9/3 运行结果如下
在这里插入图片描述
在这一次的代码完善中,技术难度不高,但是在考虑问题上全面考虑。比如一开始我用用正则表达式对字符串进行匹配时,考虑的优先级是先乘除,后加减,但是在后来进行大量数据测试的时候发现是不正确的。举个简单的例子:
正常计算 6/3
3=6
先算乘法 6/33=2/3
先算除法 6/3
3=6

正常计算 3*6/3=6
先算乘法 3*6/3=6
先算除法 3*6/3=6

显然,乘除两者之间应先计算除法后算乘法,对于加减而言,亦是同理,先减后加。所以在进行分步匹配运算的时候必须按照顺序 除→乘→减→加。

尽管如此,这个程序还不是一个正确的程序,因为当减法计算后出现了负数时,就会变成以下运行结果:(3*3*2+3+4+5*5+9/3-43)

在这里插入图片描述
目前已有解决思路,空闲时会进行改进。


根据程序逻辑的总体思路分析,对于给定的不规则表达式字符串,使用正则表达式先匹配出所有的除法表达式,使用除法符号作为分隔符得到数据,进行相关计算后将结果返回并取代匹配出来的表达式。后续乘法和减法同理。

经过三轮的 匹配-计算-取代 的迭代后,最后的表达式就应该是一个纯加法的表达,或者说是带有负数的加法表达式。这种情况下已经不需要正则表达式了,直接对表达式以加法符号进行分割然后计算即可,因为BigDecimal类是支持初始化负数对象的。

本次更改:
1.加法的计算使用独立的方法
2.对除法计算可能出现的无限小数位数进行了限制
3.对主函数部分功能进行了封装

以下是完整代码,基本逻辑已经完成。

public class main {
    public static void main(String args[]){ 
        String str1 = "3*3*2+3+4+5*5+9/7/2-43/2";
        char mark[]={'+','-','*','/','.'};
        for(int i=0;i<5;i++){
            if(str1.startsWith(String.valueOf(mark[i])))System.out.println("error");
        } //是否以符号开头
        for(int i=0;i<5;i++){
            if(str1.endsWith(String.valueOf(mark[i])))System.out.println("error");
        } //是否以符号结尾

        calculate(str1);
    }

     static void calculate(String str1){

        String regexMul = "\\d+(\\.\\d+)?(\\*\\d+(\\.\\d+)?)+";//乘法正则表达式
        String regexDvi = "\\d+(\\.\\d+)?(\\/\\d+(\\.\\d+)?)+";//加法正则表达式
        String regexSub = "\\d+(\\.\\d+)?(\\-\\d+(\\.\\d+)?)+";//除法正则表达式

        str1=lastResult(str1,regexDvi,"\\/",'/');   //必须按顺序/ * - +
        str1=lastResult(str1,regexMul,"\\*",'*');
        str1=lastResult(str1,regexSub,"\\-",'-');

        str1=lastAddResult(str1);
        System.out.println("最终结果:"+str1);
    }

    static String lastResult(String str1,String regex,String re,char ch){

        Pattern r = Pattern.compile(regex);
        Matcher m = r.matcher(str1);
        
        while (m.find()){
            String []temp=m.group(0).split(re); //在匹配结果中获取符号两边的数
            String result = getResult(temp,ch); //返回计算结果
            str1 = str1.replace(m.group(0),result);   //进行取代
        }
        return str1;
    }

    //加法计算单独使用方法
    static String lastAddResult(String str1){
        String []temp=str1.split("\\+");
        String result = getResult(temp,'+');
        return result;
    }

    //这是一个用于计算的方法
    static String getResult(String temp[],char ch){
        //需要精确计算,所以使用BigDecimal类
        BigDecimal big = new BigDecimal(temp[0]);
        switch (ch){
            case '*':
                for(int i =1;i<temp.length;i++){
                    big = big.multiply(new BigDecimal(temp[i]));
                }break;
            case '/':
                for(int i =1;i<temp.length;i++){
                    big = big.divide(new BigDecimal(temp[i]),2,BigDecimal.ROUND_HALF_UP);
                    //考虑到除法可能会出现无限小数问题,采用四舍五入方法保留两位小数
                }break;
            case '+':
                for(int i =1;i<temp.length;i++){
                    big = big.add(new BigDecimal(temp[i]));
                }break;
            case '-':
                for(int i =1;i<temp.length;i++){
                    big = big.subtract(new BigDecimal(temp[i]));
                }break;
        }
        return big.toString();
    }
}

运行结果:
在这里插入图片描述


这是最后一次更新了,本次是直接在安卓端开发了。在逻辑算法上做了一些小的改动,之前是创建一个符号数组在每次输入的时候对表达式进行尾首检查,确认是否是符号,但是这样是无法检测到输入过程中是否有输入了两次符号这种情况的。于是重新定义了一个变量flag,用于检测是否开头、结尾、以及连续输入了符号,初始值为0,即文本中不存在任何表达式的情况,当按下符号按键时需要检查flag是否为0或者1,并且将flag的值设置为1;按下数字键的时候将flag的值改为2,在最后计算时需要对flag值进行检查,在其值非1(非符号结尾)的情况下才会进行计算。

以下是源码

界面文件 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <GridLayout
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:rowCount="6"
        android:columnCount="4">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_columnSpan="4"
            android:layout_marginLeft="4px"
            android:layout_gravity="left"
            android:text="0"
            android:textSize="50dp"
            android:id="@+id/text"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_columnSpan="4"
            android:text="清除"
            android:textSize="26sp"
            android:id="@+id/clean"/>

        <Button android:text="1" android:textSize="26sp" android:id="@+id/button1"/>
        <Button android:text="2" android:textSize="26sp" android:id="@+id/button2"/>
        <Button android:text="3" android:textSize="26sp" android:id="@+id/button3"/>
        <Button android:text="+" android:textSize="26sp" android:id="@+id/buttonAdd"/>
        <Button android:text="4" android:textSize="26sp" android:id="@+id/button4"/>
        <Button android:text="5" android:textSize="26sp" android:id="@+id/button5"/>
        <Button android:text="6" android:textSize="26sp" android:id="@+id/button6"/>
        <Button android:text="-" android:textSize="26sp" android:id="@+id/buttonSub"/>
        <Button android:text="7" android:textSize="26sp" android:id="@+id/button7"/>
        <Button android:text="8" android:textSize="26sp" android:id="@+id/button8"/>
        <Button android:text="9" android:textSize="26sp" android:id="@+id/button9"/>
        <Button android:text="*" android:textSize="26sp" android:id="@+id/buttonMul"/>
        <Button android:text="." android:textSize="26sp" android:id="@+id/buttonDot"/>
        <Button android:text="0" android:textSize="26sp" android:id="@+id/button0"/>
        <Button android:text="=" android:textSize="26sp" android:id="@+id/buttonEq"/>
        <Button android:text="/" android:textSize="26sp" android:id="@+id/buttonDiv"/>

    </GridLayout>

</android.support.constraint.ConstraintLayout>
逻辑java文件 MainActivity.java
package com.example.calculator;


import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.math.BigDecimal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MainActivity extends AppCompatActivity {

    private int flag=0;//用于判断 开头和结尾 以及不能有连续符号出现 和文本是否为空
    private Button bt0,bt1,bt2,bt3,bt4,bt5,bt6,bt7,bt8,bt9,btadd,btsub,btdiv,btmul,btdot,cl,bteq;
    private TextView txt;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txt=findViewById(R.id.text);

        bt0=findViewById(R.id.button0);
        bt1=findViewById(R.id.button1);
        bt2=findViewById(R.id.button2);
        bt3=findViewById(R.id.button3);
        bt4=findViewById(R.id.button4);
        bt5=findViewById(R.id.button5);
        bt6=findViewById(R.id.button6);
        bt7=findViewById(R.id.button7);
        bt8=findViewById(R.id.button8);
        bt9=findViewById(R.id.button9);
        btadd=findViewById(R.id.buttonAdd);
        btsub=findViewById(R.id.buttonSub);
        btdiv=findViewById(R.id.buttonDiv);
        btmul=findViewById(R.id.buttonMul);
        btdot=findViewById(R.id.buttonDot);
        bteq=findViewById(R.id.buttonEq);
        cl=findViewById(R.id.clean);

        cl.setOnClickListener(new Click1());

        btadd.setOnClickListener(new Click2(btadd.getText().toString()));
        btsub.setOnClickListener(new Click2(btsub.getText().toString()));
        btdiv.setOnClickListener(new Click2(btdiv.getText().toString()));
        btmul.setOnClickListener(new Click2(btmul.getText().toString()));
        btdot.setOnClickListener(new Click2(btdot.getText().toString()));

        bt0.setOnClickListener(new Click3(bt0.getText().toString()));
        bt1.setOnClickListener(new Click3(bt1.getText().toString()));
        bt2.setOnClickListener(new Click3(bt2.getText().toString()));
        bt3.setOnClickListener(new Click3(bt3.getText().toString()));
        bt4.setOnClickListener(new Click3(bt4.getText().toString()));
        bt5.setOnClickListener(new Click3(bt5.getText().toString()));
        bt6.setOnClickListener(new Click3(bt6.getText().toString()));
        bt7.setOnClickListener(new Click3(bt7.getText().toString()));
        bt8.setOnClickListener(new Click3(bt8.getText().toString()));
        bt9.setOnClickListener(new Click3(bt9.getText().toString()));

        bteq.setOnClickListener(new Click4());

    }

    //按下清除键的事件处理
    class Click1 implements View.OnClickListener{
        @Override
        public void onClick(View v){
            txt.setText("0");//清除文本框的内容
            flag=0;
        }
    }
    //符号键的事件处理
    class Click2 implements View.OnClickListener{
        private String str;
        Click2(String str){ this.str=str;}
        @Override
        public void onClick(View v){

            if(flag==0)
            {
                txt.setText("不能以符号开头");
            }
            if(flag==1)//上一个按键按的是符号键
            {
                txt.setText("不能连续输入符号");
            }
            else
            {
                flag=1;//按下了符号键
                String tmp = txt.getText().toString()+this.str;
                txt.setText(tmp);
            }
        }
    }
    //数字键的事件处理
    class Click3 implements View.OnClickListener{
        private String str;
        Click3(String str){ this.str=str;}
        @Override
        public void onClick(View v){
            if(flag==0)
            {
                txt.setText(this.str);
            }
            else
            {
                String tmp = txt.getText().toString()+ this.str;
                txt.setText(tmp);
            }
            flag=2;//按下了数字键
        }

    }
    //等号键事件处理
    class Click4 implements View.OnClickListener{
        @Override
        public void onClick(View v){
            if(flag==1)
            {
                txt.setText("不能以符号结尾");
            }
            else
            {
                txt.setText(calculate(txt.getText().toString()));
            }
        }
    }


    static String calculate(String str1){

        String regexMul = "\\d+(\\.\\d+)?(\\*\\d+(\\.\\d+)?)+";//乘法正则表达式
        String regexDvi = "\\d+(\\.\\d+)?(\\/\\d+(\\.\\d+)?)+";//加法正则表达式
        String regexSub = "\\d+(\\.\\d+)?(\\-\\d+(\\.\\d+)?)+";//除法正则表达式

        str1=lastResult(str1,regexDvi,"\\/",'/');   //必须按顺序/ * - +
        str1=lastResult(str1,regexMul,"\\*",'*');
        str1=lastResult(str1,regexSub,"\\-",'-');

        return lastAddResult(str1);
    }
    static String lastResult(String str1,String regex,String re,char ch){

        Pattern r = Pattern.compile(regex);
        Matcher m = r.matcher(str1);

        while (m.find()){
            String []temp=m.group(0).split(re); //在匹配结果中获取符号两边的数
            String result = getResult(temp,ch); //返回计算结果
            str1 = str1.replace(m.group(0),result);   //进行取代
        }
        return str1;
    }
    //加法计算单独使用方法
    static String lastAddResult(String str1){
        String []temp=str1.split("\\+");
        String result = getResult(temp,'+');
        return result;
    }
    //这是一个用于计算的方法
    static String getResult(String temp[],char ch){
        //需要精确计算,所以使用BigDecimal类
        BigDecimal big = new BigDecimal(temp[0]);
        switch (ch){
            case '*':
                for(int i =1;i<temp.length;i++){
                    big = big.multiply(new BigDecimal(temp[i]));
                }break;
            case '/':
                for(int i =1;i<temp.length;i++){
                    big = big.divide(new BigDecimal(temp[i]),2,BigDecimal.ROUND_HALF_UP);
                    //考虑到除法可能会出现无限小数问题,采用四舍五入方法保留两位小数
                }break;
            case '+':
                for(int i =1;i<temp.length;i++){
                    big = big.add(new BigDecimal(temp[i]));
                }break;
            case '-':
                for(int i =1;i<temp.length;i++){
                    big = big.subtract(new BigDecimal(temp[i]));
                }break;
        }
        return big.toString();
    }
}

到此本次的安卓小作业就正式宣告完成了。没有参考别人的代码,完完全全的独立解决,虽然用的时间很久,但独立解决了问题还是很有成就感的。

要说不足之处还是有的,因为除法算法我选择了保留两位小数,所以对于类似6/9*3这样的式子。正确结果应该是2,但是我设计的算法最后得出的结果是2.01.解决办法还是有的,就是除法的小数位数保留多几位,然后在返回最终计算结果的时候再进行小数取舍,我就不进行完善了。

其实这是一个很有趣的问题:
1➗3 × 3这个式子
用分数计算结果是1
用小数计算结果是0.999999999999999……
就有 1 = 0.99999999999999999……
这应该就是高数中所说的极限吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值