ラベル アプリ開発 の投稿を表示しています。 すべての投稿を表示
ラベル アプリ開発 の投稿を表示しています。 すべての投稿を表示

2019年7月17日水曜日

電卓の隠れ機能

電卓には隠れ機能があり、

3+===

という操作で自分自身を足したり掛けたりといった操作のほかに


1000x2 = 2000

という計算をしたあとに

10=

とすると、x2が有効になり、20という計算結果がでる、という機能もあるようです。
(メモリ機能)

この機能はまったく知らなかったので、実装してみることにしましたが、一旦「=」で計算したあとで、前の計算結果を使うというのは想定外で、計算用のキュー を作り変えることにしました。

方法としては、数字をPushしてから演算子をPushするという仕組みで作っていたのですが、数字と演算子を一度にPushするという形態に変えました。

この場合、+=とかの機能では数字の入力が不要になりますので、数字の部分にはnilを入れることにします。

そうすると、計算用のキュー はこんな感じになります。
    // CalQueue.push()
    func push(_ inputOperand:String? , _ inputOperater:String) ->String?{

        var operater = inputOperater
        
        if let operand = inputOperand {
            if operand1 != nil && operand2 != nil{
                if operater != "=" {
                    operaterQueue = nil
                    operand1 = nil
                    operand2 = nil
                }
                else {
                    operand2 = operand
                    operater = "=="
                }
            }
            
            if operand1 == nil{
                operand1 = operand
            }
            else if operand2 == nil{
                operand2 = operand
            }
        }
        
        
        if operaterSet.contains(operater){
            switch operater {
            case "+":fallthrough
            case "-":fallthrough
            case "/":fallthrough
            case "*":
                if operand1 != nil {
                    if operand2 != nil{
                        if operaterQueue != nil{
                            let returnVal = equalOperation()
                            operaterQueue = operater
                            operand1 = String2formattedNumberString(returnVal!)
                            operand2 = nil
                            let ret =  String2formattedNumberString (returnVal!)
                            return ret
                        }
                        else{
                        }
                    }
                    else{
                        operaterQueue = operater
                    }
                }
                else{
                }
            case "=":
                if operand1 != nil {
                    if operand2 != nil{
                        if operaterQueue != nil{
                            let returnVal = equalOperation()
                            operand1 = returnVal
                            let ret =  String2formattedNumberString (returnVal!)
                            return ret
                        }
                    }
                    else{ //1000=
                        if operaterQueue != nil{
                            let returnVal = equalOperation()
                            let ret =  String2formattedNumberString (returnVal!)
                            return ret
                        }
                    }
                }
            case "==":
                if operand1 != nil {
                    if operand2 != nil{
                        if operaterQueue != nil{
                            let returnVal = equalOperation()
                            let ret =  String2formattedNumberString (returnVal!)
                            return ret
                        }
                    }
                }
            default: // += -= /= *=
                if operand1 != nil {
                    if operand2 == nil{
                        operand2 = operand1
                        operaterQueue = operater
                        let returnVal = equalOperation()
                        let ret =  String2formattedNumberString (returnVal!)
                        operand2 = ret
                        return ret
                    }
                    else {
                        operaterQueue = operater
                        let returnVal = equalOperation()
                        let ret =  String2formattedNumberString (returnVal!)
                        operand2 = ret
                        return ret
                    }
                }
                else {
                    print ("operand1 is nil")
                }
            }// end switch
            return nil
        }
        return nil
    }// end push

前半が数字の入力からオペランドに実装するところで、数字=が入力されたときは演算子を==として数字をオペランド1にいれることで今回の機能を実現しています。

通常演算でA+B=のあとに=を入れた場合はまた別の計算で、計算結果をオペランド1に入れて計算結果を残し、=のみが入った時にはその残したオペランドと演算子で計算をさせることになります。

結構複雑な処理を実装しましたが、コード自身の長さはそう変わらず、かなりいい感じで実装できています。

あとは有効桁の機能や通貨計算用機能を組み込んだらエンジンは完成、という感じです。

2019年7月16日火曜日

Singletonを使ったオブジェエク間の情報共有

電卓も機能が複雑になってくると、設定した変数を使った計算が必要になってきます。

具体的には、話題の「消費税」ですね。欧州などではVATとも言いますが、年々あがっていくので(?)、設定を変える必要があります。

設定情報は、iPhoneの場合「設定(setting)」画面から変更する場合もありますが、アプリから離れてしまうので使い勝っては今ひとつです。

ファイルを使って受け渡す方法もありますが、そんなに大量でなければアプリ内ではSingletonといって、インスタンスを共有する形で情報を受け渡すことになります。

Objective-Cでの実装方法はわかりますが、Swiftでの記述がどうなるか不明でしたので、ググってみたところ確認できました。

シングルトンのオブジェクトは次のように記述するようです。ここでのrateが共有する値になります。ここをタプルだとか、その他のオブジェクトにしてもいいかと思います。

class Tax: NSObject{
    var rate: String
    static let sharedTax: Tax = Tax()
    private override init(){
        rate = "10"
    }
}

このshardTaxというプロパティを呼び出すことでインスタンスがなければインスタンスを生成し、インスタンスがあれば特に生成せず既存のインスタンスがだけが、唯一つかわれるようです。また、initがprivateで記述されるために、2回目のオブジェクトの定義時にもデータは初期化されないようです。

では、インスタンスの利用側はどのように書くかというと、

    private let tax = Tax.sharedTax

ということで、taxというTaxインスタンスをTaxオブジェクトのshardTaxというプロパディで指定する、という文法らしいです。

これでなぜ記述できるのかは理解できていませんが、いまのところそう覚えるしかなさそうです。

実行の結果はこんな感じになります(Operatoer4のvが10%消費税を加えたところです)

0
1
1.
1.2
1.23
1.234
-1.234
-1.2340
Operater5 *
Operater2 /
Operater2 -
0.
0.1
0.10
Operater3 %
0.001
0.0010
0.00101
0.001010
Operater4 v
0.001111
Operater5 +
-1.235111
Operater2 -
Operater1 -=
0
Operater1 -=
1.235111
Operater1 -=
2.470222
Operater1 -=
3.705333

とりあえず演算子は一通り揃えたかな、というところで、次は有効桁の機能をつけくわえて行きたいと思います。

2019年7月15日月曜日

電卓のチャタリング対策、演算子修正操作、2バイト演算子を組み込む

電卓の場合、チャタリング対策で、**とかの操作で2重に演算がはじまらないようにすること、*のあとに/に修正するなどの演算子の操作謝りの修正、*=、ー=のような自分自身に対する演算操作を組み込む必要があります。

これが結構大変でした。

まず、演算子の2度押しかどうかは、演算子を押した結果を保存して、その後演算子が続いたときの処理を実装する、という基本的な識別を実装しました。

            if checkChattering {
                if previousOperater == String(char) {
                    return
                }
                else{
                    // =+ , -+ , +=
                    if char == "=" { // += -= /= *=
                        if previousOperater.count == 1 {
                            previousOperater = String(previousOperater) + String(char)
                        }
                        print("Operater1 " + previousOperater)
                        if let result = calQueue.push(previousOperater){
                            if let numberString = formattedNumberStringForPop(result){
                                print (numberString)
                            }
                        }
                        return
                    }
                    else {
                        calQueue.changeOperater(String(char))
                        previousOperater = String(char)
                        print("Operater2 " + String(char))
                        return
                    }
                }
            }

こんな感じです。

実装としては、checkChatterngという前が演算子だった場合の識別のほかに、前の演算子がなんであったのかを記憶しておく必要があり、previousOperaterというのを設けています。charはcurrentOperaterとかいう名前にすればよかったですね。

最初の処理は、同じ演算子が2度押しの場合は差し戻し処理です。

次に+=、*=などの自身への演算処理で2度押しかつ=であれば走ります。
前の演算子が、+=などではない場合は、+=などを演算子とします。
で、この演算子を計算キュー に送り込み、計算結果を得ることになります。

次に、+/などの修正処理ですが、計算キュー の演算子そのものをすり替える関数をもけてすり替え処理をおこなっている、というところになります。

そのほかに、数値のプラスマイナスを入れ替える処理と、クリア処理を加えました。

            else if char == "p" {
                    let q = queue.value()
                    if q.prefix(1) == "-" {
                        queue.setValue(String(q.suffix(q.count - 1)))
                        if let numberString = formattedNumberStringForPop(queue.value()){
                            print (numberString)
                        }
                    }
                    else if q.prefix(1) != "0" {
                        queue.setValue("-" + q)
                        if let numberString = formattedNumberStringForPop(queue.value()){
                            print (numberString)
                        }
                    }
                    return
            }
            else if char == "c"{
                self.initialize()
                print("Clear")
                print(queue.value())
            }

プラスマイナスの入れ替えは、はじめの1文字が”ー”ならば、マイナスを取る、初めの1文字が”0”でない場合は、”ー”をつけるという感じです。

次にクリアで苦労しました。キュー には数字のキュー と計算キュー の2つがあるのですが、計算キュー のクリア関数を実装しておらず、計算結果でキュー が残ったまま計算していました。なぜかわからず全体をたどるハメになりました・・

ちなみに、+=、ー=などの計算ですが、こんな感じになります。

        if noEqualOperaterSet.contains(operaterQueue!) {
            switch operaterQueue {
            case "+":
                let retVal = Decimal(string: operand1!)! + Decimal(string: operand2!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "-":
                let retVal = Decimal(string: operand1!)! - Decimal(string: operand2!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "/":
                let retVal = Decimal(string: operand1!)! / Decimal(string: operand2!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "*":
                let retVal = Decimal(string: operand1!)! * Decimal(string: operand2!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "+=":
                let retVal = Decimal(string: operand2!)! + Decimal(string: operand1!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "-=":
                let retVal = Decimal(string: operand2!)! - Decimal(string: operand1!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "/=":
                let retVal = Decimal(string: operand2!)! / Decimal(string: operand1!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "*=":
                let retVal = Decimal(string: operand2!)! + Decimal(string: operand1!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            default: return nil
            }
        }

ここでは、当初+=はうまくいくのに、ー=が上手く計算できず、なにがなんだかわかりませんでししたが、トレースした結果、この処理の場合、オペランドを入れ替えないと上手く動かないことがわかりました。頭の中で+=しか想像せずにコーディングした結果ですね・・・

つづいてこの2バイト演算子の計算をさせるための処理がこちらです。

            default: // += -= /= *=
                if operand1 != nil {
                    if operand2 == nil{
                        operand2 = operand1
                        operaterQueue = inputData
//                        print ("oprand1 operand2 " + operand1! + " " + operand2!)
                        let returnVal = equalOperation()
                        let ret =  String2formattedNumberString (returnVal!)
                        operand2 = ret
//                        print ("oprand1 operand2 " + operand1! + " " + operand2!)
                        return ret
                    }
                    else {
                        operaterQueue = inputData
//                        print ("oprand1 operand2 " + operand1! + " " + operand2!)
                        let returnVal = equalOperation()
                        let ret =  String2formattedNumberString (returnVal!)
                        operand2 = ret
 //                       print ("oprand1 operand2 " + operand1! + " " + operand2!)
                        return ret
                    }
                }
                else {
                    print ("operand1 is nil")
                }

オペランド2がない場合、自分自身をオペランド2にして、結果をオペランド2に渡します。そうすると自分自身がオペランド1に残り続け、オペランド2に結果が残り、結果と自分自身とを処理しつづけるということになります。

修正の結果、いろいろと計算してみて問題なさそうなので、計算エンジンはほぼできあがったという感じです。

以下は試験データです。上手く動いてるっぽいですw

0
1
12
123
1,234
12,345
123,451
123,451.
123,451.2
123,451.23
123,451.234
123,451.2345
123,451.234
123,451.23
123,451.2
123,451
12,345
1,234
123
12
1
0
1
1.
1.2
1.23
1.234
-1.234
-1.2340
Operater3 *
Operater2 /
Operater2 -
0.
0.1
0.10
Clear
0
1
10
Operater3 +
Operater2 -
Operater1 -=
0
Operater1 -=
-10
Operater1 -=
-20
Operater1 -=
-30

あとはユーザインタフェースをSwift UIでつくるか、ストーリーボードで作りつづけるか、というところが課題ですが微妙なところです。

 当面はユーザインタフェースは先送りにして、小数点以下の四捨五入だとか、小数点何桁で処理するだとか、消費税、%処理とかのエンジンの機能を優先していきたいと思います。

2019年7月14日日曜日

Swift4の文字列操作がややこしすぎる

Swiftは、どうやら文字列操作が結構ややこしいようです。
しかも、どうやらSwift3とSwift4でも違うらしく、この移行を乗り切った人はえらく大変だったと思います。

最後の文字を抜き出すだけでも苦労しました。どうやらこんな感じになるようです。

        if val[val.index(before: val.endIndex)] == "." {

ちなみに文字位置をしらべようとしましたが、ついに断念しましたw

そのうち分かるようになるのかしらんw

2019年7月13日土曜日

電卓エンジンを10進数にする

これまではとりあえず、無難に演算をDoubleでやってきましたが、演算部の分離に成功したので、そのままエンジンを10進演算に切り替えたいと思います。

これまでのエンジンはこちら。

    func equalOperation() -> Double?{
        if noEqualOperaterSet.contains(operaterQueue!) {
            switch operaterQueue {
            case "+": return Double(operand1!)! + Double(operand2!)!
            case "-": return Double(operand1!)! - Double(operand2!)!
            case "/": return Double(operand1!)! / Double(operand2!)!
            case "*": return Double(operand1!)! * Double(operand2!)!
            default: return nil
            }
        }
        else{
            return nil
        }
    }

イコールキーとかを押した時の演算関数になります。

では10進演算に置き換えましょう。それがこちら。

    func equalOperation() -> String?{
        if noEqualOperaterSet.contains(operaterQueue!) {
            switch operaterQueue {
            case "+":
                let retVal = Decimal(string: operand1!)! + Decimal(string: operand2!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "-":
                let retVal = Decimal(string: operand1!)! - Decimal(string: operand2!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "/":
                let retVal = Decimal(string: operand1!)! / Decimal(string: operand2!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            case "*":
                let retVal = Decimal(string: operand1!)! * Decimal(string: operand2!)!
                return NSDecimalNumber(decimal: retVal).stringValue
            default: return nil
            }
        }
        else{
            return nil
        }
    }

こんな感じです。この返り値をStringにする方法が結構わからなかったですね。

1
1
1.2
1.23
1.234
1.2345
Operator -
1
1
1.2
1.23
1.234
1.2340
Operator *
0.0005
1
10
100
1,000
Operator =
0.5

計算結果も1.2345 - 1.2340 *1000 = 0.5 となり、どうやら10進で計算できているようです。

計算キューの作成

前回までは数字の入力キューを作りましたが、今回は計算のためのキューを作ります

計算のためのキューとは、A + B = という計算をするのですが、この場合の計算は+を押したときではなく、=を押した時に実行されるため、AとBとを確保しておく必要があるため、キューを作る必要があります。

それが以下のものになります。
    // CalQueue.push()
    func push(_ inputData:String) ->String?{
//        if let operaterData = changeString2Character(inputData){
            
        if itsOperator(inputData){
            switch inputData {
                case "+":fallthrough
                case "-":fallthrough
                case "/":fallthrough
                case "*":
                    if operand1 != nil {
                        if operand2 != nil{
                            if operaterQueue != nil{
                                let returnVal = equalOperation()
                                operaterQueue = Character(inputData)
                                operand1 = String2formattedNumberString(returnVal!)
                                operand2 = nil
                                let ret =  String2formattedNumberString (returnVal!)
                                return ret
                            }
                        }
                        else{
                            operaterQueue = Character(inputData)
                            }
                    }
                case "=":
                    if operand1 != nil {
                        if operand2 != nil{
                            if operaterQueue != nil{
                                let returnVal = equalOperation()
                                operaterQueue = nil
                                operand1 = nil
                                operand2 = nil
                                let ret =  String2formattedNumberString (returnVal!)
                                return ret
                            }
                        }
                    }
               default:
                        print("Bad operator")
                }// end switch
            return nil
        }
        else {
        // changeString2CHaracter (not 1 char)
            if operand1 == nil{
                operand1 = inputData
                return operand1
            }
            else{
                operand2 = inputData
                return operand2
            }
        }
        
    }// end push

ここでプッシュされるのは、数字かもしくは演算子ということでPush関数に実装したのが、まず演算子かどうかの判別で、実際に演算を行います。

演算としては、=の場合に、キューに格納された数字同士を、同じくキューに格納された演算子で計算し、結果を返すことが基本になっています。

+ー/*の場合は、演算子を単にキューに入れるか、もしくは数字のキューが双方揃っているのであれば、=と同じような計算を行います(演算自体は継続)。

また、演算結果は数値キーでの入力とは異なり、小数点より後ろにゼロがつく場合があるため、最後がゼロとなる場合は桁を減らすようなフォーマット変換をしています。

あと、今回のコードで重要なのが、演算を独立させたところです。これでいつでも10進演算を実装できます。

いやぁ、Swift便利すぎだわ。

ちなみにこのコードは++だとか+=だとか+====だとか、特殊なオペレーションやキー入力が含まれていないので、追って実装していきます。