Програмуємо калькулятор на андроїд. Урок 7

Урок 7. Обробка чисел з комою.

Преамбула до уроку: Коли я почав писати код для цього уроку, я вирішив зберігати цілу і дробову частину числа, що вводиться в одній змінної типу double, програмно обмеживши кількість розрядів, які можна ввести. Дрібна частина вводиться за формулою: попереднє значення + натиснута цифра / 10 в ступені рівному вводиться розряду. Все ніби правильно, але періодично при введенні з’являлися значення на кшталт XXX.YYY000000001 або XXX.YYY99999999. Справа в тому, комп’ютер оперує числами у двійковому поданні і, загалом, це нормальний результат для двійковій математики. Порившись в інеті, я з’ясував, що інші мови програмування згладжують результат до правильного значення, а Java, як найчесніша у світі мова, показує всю таємницю. “Зараз округлимо” – подумав я. Але все не так просто. У Java немає бібліотечної функції для округлення дробового числа до певного розряду після коми, можна округлити тільки до цілого числа. Правильно було б змінити підхід до задачі – зберігати цілу і дробову частину числа в різних цілочисельних змінних, але я вирішив піти до кінця, округляючи числа за допомогою математичних обчислень і подивитися, що вийде. Довелося вводити багато перевірок і обчислень. Я майже домігся правильного відображення чисел (трохи незручно для сприйняття відображається число при видаленні останнього введеного символу), але код став кілька заплутаний. Переробляти код не хочеться, щоб не затримувати вихід наступного уроку, тому наведу код в такому вигляді як є зараз. Але якщо ви збираєтеся випустити додаток в світло, код потрібно піддати ретельній корекції, а краще переробити і зберігати дробову частину операндів окремо від цілої.

Отже, три нових константи в string.xml і повний код MainActivity.java

Весь новий код розкоментував, тому пояснень не пишу.


string.xml
<String name = "limit_before"> Обмеження розрядності = 8 </ string>
<String name = "limit_after"> Обмеження знаків після коми = 2 </ string>
<String name = "degree_overflow"> Помилка: переповнення розрядності. </ String>

MainActivity.java


package ru.urok.super_calc;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements OnClickListener {
Button btOne, btTwo, btThree, btFour, btFive;
Button btSix, btSeven, btEight, btNine, btZero;
Button btPlus, btMinus, btMulti, btDiv, btEqual, btClear;
Button btPoint, btSquare, btRadical, btBack;
TextView tvLCD;
double operand1, operand2;
int flagAction;
double result;
SharedPreferences sp;
Boolean add_button;
String style_calc;
LinearLayout llExtra;
int[] bt_ids;
Button[] bt_array;
int len;

//прапорець натиснення цятки
Boolean flagPoint;

//лічильник розрядів до коми і після
int degree_before, degree_after;

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   sp = PreferenceManager.getDefaultSharedPreferences(this);
   tvLCD = (TextView) findViewById(R.id.tvLCD);
   llExtra = (LinearLayout) findViewById(R.id.llExtra);
   bt_ids = new int[]{
      R.id.btOne, R.id.btTwo, R.id.btThree, R.id.btFour,
      R.id.btFive, R.id.btSix, R.id.btSeven, R.id.btEight,
      R.id.btNine, R.id.btZero, R.id.btPlus, R.id.btMinus,
      R.id.btMulti, R.id.btDiv, R.id.btEqual, R.id.btClear,
      R.id.btPoint, R.id.btSquare, R.id.btRadical, R.id.btBack};

bt_array = new Button[]{
   btOne, btTwo, btThree, btFour, btFive, btSix,
   btSeven, btEight, btNine,btZero, btPlus, btMinus, btMulti,
   btDiv, btEqual, btClear, btPoint, btSquare, btRadical, btBack};
   len = bt_array.length;
   for(int i = 0; i < len; i++){
      bt_array[i] = (Button) findViewById(bt_ids[i]);
      bt_array[i].setOnClickListener(this);
   }
   clearVariables();
   showNumber(operand1);
}

protected void onResume() {
   add_button = sp.getBoolean("add_button", false);
   style_calc = sp.getString("style_calc", "1");
   int flag_visiblity = View.INVISIBLE;
   if(add_button)flag_visiblity = View.VISIBLE;
   llExtra.setVisibility(flag_visiblity);
   super.onResume();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
   getMenuInflater().inflate(R.menu.main, menu);
   return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch(item.getItemId()){
      case R.id.options_menu:
      startActivity(new Intent(this, PrefActivity.class));
   break;
   case R.id.about_menu:
      startActivity(new Intent(this, AboutActivity.class));
   break;
   }
   return super.onOptionsItemSelected(item);
}

@Override
public void onClick(View v) {
   switch(v.getId()){
      case R.id.btOne:
      ClickNumber(1);
   break;
   case R.id.btTwo:
      ClickNumber(2);
   break;
   case R.id.btThree:
      ClickNumber(3);
   break;
   case R.id.btFour:
      ClickNumber(4);
   break;
   case R.id.btFive:
      ClickNumber(5);
   break;
   case R.id.btSix:
      ClickNumber(6);
   break;
   case R.id.btSeven:
      ClickNumber(7);
   break;
   case R.id.btEight:
      ClickNumber(8);
   break;
   case R.id.btNine:
      ClickNumber(9);
   break;
   case R.id.btZero:
      ClickNumber(0);
   break;
   case R.id.btPlus:
      if(flagAction == 0){
         flagAction = 1;
//обнулимо лічильники й прапорці
   clearDegree();
   }
   break;
   case R.id.btMinus:
      if(flagAction == 0){
         flagAction = 2;
//обнулимо лічильники й прапорці
   clearDegree();
   }
   break;
   case R.id.btMulti:
      if(flagAction == 0){
         flagAction = 3;
//обнулимо лічильники й прапорці
   clearDegree();
   }
   break;
   case R.id.btDiv:
      if(flagAction == 0){
         flagAction = 4;
//обнулимо лічильники й прапорці
   clearDegree();
   }
   break;
   case R.id.btPoint:

//змінюємо значення флага на true
   flagPoint = true;
   break;
   case R.id.btSquare:
      if(flagAction == 0){
         result = Math.pow(operand1, 2);

//якщо результат не є цілим встанавлюємо лічильник після коми = 2
   if(result%1 != 0)degree_after = 2;
   showNumber(result);
   clearVariables();
   }else{
   Toast.makeText(this, R.string.other_operation, Toast.LENGTH_LONG).show();
}
   break;
   case R.id.btRadical:
   if(flagAction == 0){
      result = Math.sqrt(operand1);
//якщо результат не є цілим, встановлюємо лічильник після коми = 2
   if(result%1 != 0)degree_after = 2;
   showNumber(result);
   clearVariables();
   }else{
      Toast.makeText(this, R.string.other_operation, Toast.LENGTH_LONG).show();
   }
   break;
   case R.id.btBack:
   String Temp;
   if(flagAction == 0){

// перевіряємо чи був введений нуль після коми
   if(operand1%1 == 0 & degree_after == 0){
      Temp = Integer.toString((int)operand1);
   }else{Temp = Double.toString(operand1);}
   Temp = Temp.substring(0, Temp.length()-1);
   if(Temp.length() > 0){
      operand1 = Double.parseDouble(Temp);
   }else{operand1 = 0;}
      showNumber(operand1);
   }else{
// перевіряємо чи був введений нуль після коми
   if(operand2%1 == 0 & degree_after == 0){
      Temp = Integer.toString((int)operand2);
   }else{Temp = Double.toString(operand2);}
   Temp = Temp.substring(0, Temp.length()-1);
   if(Temp.length() > 0){
      operand2 = Double.parseDouble(Temp);
   }else{operand2 = 0;}
   showNumber(operand2);
}
// якщо лічильник після коми більше 0, зменшуємо на 1
// якщо дорівнює 0, прапор точки ставимо false
   if(degree_after > 0){
      degree_after--;
   }else{
   flagPoint = false;
}
   break;
   case R.id.btEqual:
   switch(flagAction){
   case 1:
      result = operand1 + operand2;
   break;
   case 2:
      result = operand1 - operand2;
   break;
   case 3:
      result = operand1 * operand2;
   break;
   case 4:
      result = operand1 / operand2;
   break;
   default:
      Toast.makeText(this, R.string.no_operation, Toast.LENGTH_LONG).show();
}
// якщо результат не цілий встановлюємо лічильник після коми = 2
   if(result%1 != 0)degree_after = 2;
   if(flagAction != 0){
   showNumber(result);
   clearVariables();
}
   break;
   case R.id.btClear:
   clearVariables();
   showNumber(operand1);
   break;
   }
}

private void ClickNumber(int num){
   if(flagAction == 0){
// перевіряємо прапорець цятки, тобто вводимо або дробову частину                                
   if(flagPoint){
// збільшуємо лічильник після точки на 1 / перевіряємо обмеження розрядності
// якщо перевищена - повідомлення, якщо немає додаємо до операнду цифру
   degree_after++;
   if(degree_after <= 2){
      operand1 += num / Math.pow(10, degree_after);
   }else{
      degree_after = 2;
      Toast.makeText(this, R.string.limit_after, Toast.LENGTH_LONG).show();
   }
}else{
// збільшуємо лічильник до точки на 1
// перевіряємо обмеження розрядності
// якщо перевищена - повідомлення, якщо немає додаємо до операнду цифру                             degree_before++;
   if(degree_before <= 8){
   operand1 = operand1*10 + num;
}else{
   degree_before = 8;
   Toast.makeText(this, R.string.limit_before, Toast.LENGTH_LONG).show();
   }
}
   showNumber(operand1);
   }else{
// перевіряємо прапор точки, тобто вводимо цілу або дробову частину                                                  
   if(flagPoint){
// збільшуємо лічильник після точки на 1
// перевіряємо обмеження розрядності
// якщо перевищена - повідомлення, якщо немає додаємо до операнду цифру 1                          
   degree_after++;
   if(degree_after <= 2){
      operand2 += num / Math.pow(10, degree_after);
   }else{
      degree_after = 2;
      Toast.makeText(this, R.string.limit_after, Toast.LENGTH_SHORT).show();
   }
}else{
// збільшуємо лічильник до точки на 1
// перевіряємо обмеження розрядності
// якщо перевищена - повідомлення, якщо немає додаємо до операнду цифру 1
   degree_before++;
   if(degree_before <= 8){
      operand2 = operand2*10 + num;
   }else{
      degree_before = 8;
      Toast.makeText(this, R.string.limit_before, Toast.LENGTH_SHORT).show();
   }
}
   showNumber(operand2);
   }
}

private void showNumber(double num){
// корегуюча змінна
   String corr = "";
// перевіряємо чи не перевищує значення num
// максимального значення int
// якщо перевищує - повідомлення про переповнення розрядності                
   if(num > Integer.MAX_VALUE){
      tvLCD.setText("ERROR");
      Toast.makeText(this, R.string.degree_overflow, Toast.LENGTH_LONG).show();
   }else{
// перевіряємо ціле число або дробове
if(num%1 == 0 & degree_after == 0){
   tvLCD.setText(Integer.toString((int) num));
   }else{
// якщо дробове відокремлюємо цілу і дробову частину
int part_int = (int) num;
int part_frac = (int) Math.round(num%1 * Math.pow(10, degree_after));
// якщо лічильник після коми = 2
// а дрібна частина займає лише один розряд
// значить є тільки соті частки числа, десятих немає
// присвоюємо коректує змінної значення 0
if(degree_after == 2 & part_frac < 10)corr = "0";
   tvLCD.setText(part_int + "." + corr + part_frac);
   }
  }
}

private void clearVariables(){
   operand1 = 0;
   operand2 = 0;
   result = 0;
   flagAction = 0;
   clearDegree();
}

// метод обнулює лічильники і прапорець цятки
private void clearDegree(){
   degree_before = 0;
   degree_after = 0;
   flagPoint = false;
   }
}