2019年7月31日水曜日
2019年7月27日土曜日
delegate関係のXCodeのバグ
delegateを実装しているとXCodeのバグにいくつか出会いました。
(1)プロトコルの実装
まず、プロトコルを採用したクラスでのこと。プロトコルで定義した関数を実装してもグリーンにならず白いままで、クラス部分には「プロトコルの関数実装してねーだろ」と出ます。
しかし、どう見てもスペルミスはない。悩んでエラーメッセージをクリックすると、なんとFixボタンが出てきてボタンを押すと、勝手に関数が表示されます。
しかたないので、関数の記述をそちらに移すとエラーは無くなりました。
どうやら、プロトコルの関数を手動で実装すると認識してくれない場合があるようです。
(2)関数の利用
おそらく同じ起源のバグと思われますが、関数を利用する側でも起こります。 delegate設定したオブジェクトの関数を認識してくれません。上記(1)でうまく言っていない場合もありますが、(1)で問題を解決しても、オブジェクトと関数は白いまま、という場合もあります。
そんなときは、一旦記述を消してから、表示されるガイドから関数を選択してあげるとうまくいきます。
ということで、Apple社内でもあまりデリゲートは使われていないのかもしれませんが、そんなバグがあったので気をつけてください。
(1)プロトコルの実装
まず、プロトコルを採用したクラスでのこと。プロトコルで定義した関数を実装してもグリーンにならず白いままで、クラス部分には「プロトコルの関数実装してねーだろ」と出ます。
しかし、どう見てもスペルミスはない。悩んでエラーメッセージをクリックすると、なんとFixボタンが出てきてボタンを押すと、勝手に関数が表示されます。
しかたないので、関数の記述をそちらに移すとエラーは無くなりました。
どうやら、プロトコルの関数を手動で実装すると認識してくれない場合があるようです。
(2)関数の利用
おそらく同じ起源のバグと思われますが、関数を利用する側でも起こります。 delegate設定したオブジェクトの関数を認識してくれません。上記(1)でうまく言っていない場合もありますが、(1)で問題を解決しても、オブジェクトと関数は白いまま、という場合もあります。
そんなときは、一旦記述を消してから、表示されるガイドから関数を選択してあげるとうまくいきます。
ということで、Apple社内でもあまりデリゲートは使われていないのかもしれませんが、そんなバグがあったので気をつけてください。
2019年7月24日水曜日
電卓画面の実装とデリゲートによる値の受け渡し
いよいよ電卓画面を実装します。
ViiewControllerからキーボードの値だけをNumberQueueオブジェクトに入れ込めば済むように作ってある(ハズな)ので、本当に画面を作るだけで済むはずです。
で、できあがった画面は次のものになります。
なかなか真っ暗でしょ(ww)
まぁ色合いはさておき、下のボタン類をNumberQueueに受け渡すところは簡単にできました。単にイベントを受け取ったら所定の値を渡すだけです。
問題は、結果を表示するところです。
NumberQueueオブジェクト自身は計算結果を返してくれません。値を返してもいいのですが、そうするとボタンごとに結果を表示するという、変な話になってしまいますので、それではNGです。
ということで、delegateを実装していくことにしました。前にも作っていましたが、objective-cでもあり、もう忘れているのもあり、調べながら作っていきました。
まずはプロトコロル(空の関数)の実装からです。
この空の関数を定義するのは、ViewControllerですので、このDelegateを採用しますよ、というのをViewControllerで宣言してあげます。
print(s)は単にデバック用の表示です。
ViewConroller側はこれでOK(ではありません。あとで問題になります)。
NumberQueue側では、このデリゲート使いますよ、というのを宣言します。
この!は、デリゲートができていない場合nilになるので、!を書くようですが、これ自身に問題がありませんが、あとで問題に。
で、このデリゲートオブジェクトの関数を直接使ってもいいのですが、使うための関数を作ってあげます。
これで実装は完了(のはずでしたが・・)ですので、これまでデバッグ用に実装していた結果の表示のprintをdispに置き換えてあげると表示されるはずでした。
しかし、実行時にエラーが。
unexpectedly found nil while implicity unwrapping an optional value
なんじゃこれ、というので調べてみると!を?にして関数にも?をつければいいという、デマ回答が乗っていてさらに混乱しました。
いろいろとググっていくと、質問者の質問に「何かを実装していないけれども、実装していると仮定して」とかいう前提付きで回答している人がいました。
これは意地悪な回答だわ、と思いつつ、そういえばViewConroller側ではNumberQueueから呼び出されるのがわからないし、このdisplayというオブジェクトが生成されていないということなんだよなぁ、ということでこんなコードを入れてみました。
はい、ビンゴです。
numberQueueのdisplayというオブジェクトは、自分が実装してますよ、と言ってあげているんですね。これで上のエラーがでなくなりましたので、無事displayのオブジェクトができてViewControllerで受け取ることができました。
しかし、delegateの解説が少なすぎて、あっても複雑に書かれすぎでよくわからんわ。まぁ解決したのでOK牧場ですね。
あとは、この電卓をつかいながらデバッグしていきます。
ViiewControllerからキーボードの値だけをNumberQueueオブジェクトに入れ込めば済むように作ってある(ハズな)ので、本当に画面を作るだけで済むはずです。
で、できあがった画面は次のものになります。
なかなか真っ暗でしょ(ww)
まぁ色合いはさておき、下のボタン類をNumberQueueに受け渡すところは簡単にできました。単にイベントを受け取ったら所定の値を渡すだけです。
@IBAction func AC(_ sender: Any) {
numberQueue.calc("c")
}
@IBAction func c(_ sender: Any) {
numberQueue.pop()
}
@IBAction func nine(_ sender: Any) {
numberQueue.push("9")
}
@IBAction func eight(_ sender: Any) {
numberQueue.push("8")
}
@IBAction func seven(_ sender: Any) {
numberQueue.push("7")
}
@IBAction func six(_ sender: Any) {
numberQueue.push("6")
}
@IBAction func five(_ sender: Any) {
numberQueue.push("5")
}
@IBAction func four(_ sender: Any) {
numberQueue.push("4")
}
@IBAction func three(_ sender: Any) {
numberQueue.push("3")
}
@IBAction func two(_ sender: Any) {
numberQueue.push("2")
}
@IBAction func one(_ sender: Any) {
numberQueue.push("1")
}
@IBAction func zero(_ sender: Any) {
numberQueue.push("0")
}
@IBAction func zerozero(_ sender: Any) {
numberQueue.push("0")
numberQueue.push("0")
}
@IBAction func dot(_ sender: Any) {
numberQueue.push(".")
}
@IBAction func plus(_ sender: Any) {
numberQueue.calc("+")
}
@IBAction func minus(_ sender: Any) {
numberQueue.calc("-")
}
@IBAction func x(_ sender: Any) {
numberQueue.calc("*")
}
@IBAction func dev(_ sender: Any) {
numberQueue.calc("/")
}
@IBAction func equal(_ sender: Any) {
numberQueue.calc("=")
}
問題は、結果を表示するところです。
NumberQueueオブジェクト自身は計算結果を返してくれません。値を返してもいいのですが、そうするとボタンごとに結果を表示するという、変な話になってしまいますので、それではNGです。
ということで、delegateを実装していくことにしました。前にも作っていましたが、objective-cでもあり、もう忘れているのもあり、調べながら作っていきました。
まずはプロトコロル(空の関数)の実装からです。
protocol DisplayDelegate{
func disp(_ s:String)
}
この空の関数を定義するのは、ViewControllerですので、このDelegateを採用しますよ、というのをViewControllerで宣言してあげます。
class ViewController: UIViewController ,DisplayDelegate{
そうすると、「ViewControllerにこの関数を実装していませんよ」と怒られますので、実装してあげます。
func disp(_ s: String) {
print(s)
mainDisplay.text = s
}
ViewConroller側はこれでOK(ではありません。あとで問題になります)。
NumberQueue側では、このデリゲート使いますよ、というのを宣言します。
var display: DisplayDelegate!
で、このデリゲートオブジェクトの関数を直接使ってもいいのですが、使うための関数を作ってあげます。
func disp(_ s:String){
self.display.disp(s)
}
これで実装は完了(のはずでしたが・・)ですので、これまでデバッグ用に実装していた結果の表示のprintをdispに置き換えてあげると表示されるはずでした。
しかし、実行時にエラーが。
unexpectedly found nil while implicity unwrapping an optional value
なんじゃこれ、というので調べてみると!を?にして関数にも?をつければいいという、デマ回答が乗っていてさらに混乱しました。
いろいろとググっていくと、質問者の質問に「何かを実装していないけれども、実装していると仮定して」とかいう前提付きで回答している人がいました。
これは意地悪な回答だわ、と思いつつ、そういえばViewConroller側ではNumberQueueから呼び出されるのがわからないし、このdisplayというオブジェクトが生成されていないということなんだよなぁ、ということでこんなコードを入れてみました。
override func viewDidLoad() {
super.viewDidLoad()
numberQueue.display = self
// Do any additional setup after loading the view.
}
はい、ビンゴです。
numberQueueのdisplayというオブジェクトは、自分が実装してますよ、と言ってあげているんですね。これで上のエラーがでなくなりましたので、無事displayのオブジェクトができてViewControllerで受け取ることができました。
しかし、delegateの解説が少なすぎて、あっても複雑に書かれすぎでよくわからんわ。まぁ解決したのでOK牧場ですね。
あとは、この電卓をつかいながらデバッグしていきます。
2019年7月23日火曜日
メモリ演算のサポート
=による計算のうち、数字=の演算で、1つ前の数字と演算子の組み合わせでモードを変えながらの通常演算とメモリ演算(結果、オペランド)計算を組み込みました。
また、これまでオペランド(数値)がnilの場合の処理を十分考えていなかったので、取り敢えずインプットステータス(モード)のみを更新する処理を組み込みました。
そして=の演算をモードによって以下のとおり使い分けます。
あとは数値入力時と演算子が入ってきたときに、インプットステータス(モード)をそれぞれ変更します。
また、=演算のあとに、インプットステータスをクリア(false,false)しなくてはいけません。
結果、通常演算、結果メモリ演算、オペランドメモリ演算がいかのとおりできました。ただし、他のモードでどう動くかはまだ検証していません。
とりあえずエンジンはここまでにしてUIを実装していくかなぁ
モードは、演算キュー の中に以下の入力モード(タプル)を組み込んでいます。
演算をする前にこのメモリを呼び出してから計算に入ります。
private var memorizedInputMode : (num: Bool, calc: Bool)
let previousInputMode = memorizedInputMode
memorizedInputMode = inputMode
if inputOperand == nil {
operaterQueue = inputOperater
return nil
}
そして=の演算をモードによって以下のとおり使い分けます。
case "=":
switch previousInputMode {
case (num:true, calc:true): //normal operation <1:true +:true> (2=)
print ("normal operation")
let returnVal = equalOperation()
operand1 = returnVal
let ret = String2formattedNumberString (returnVal!)
return ret
case (num:false, calc:true): //result operation <nil:false +:true> (2=)
print ("result operation")
operand2 = inputOperand
let returnVal = equalOperation()
operand1 = returnVal
let ret = String2formattedNumberString (returnVal!)
return ret
case (num:false, calc:false): // operand operation < (2=) >
operand1 = inputOperand
print ("operand operation " + operand1! + operaterQueue! + operand2!)
let returnVal = equalOperation()
operand1 = returnVal
let ret = String2formattedNumberString (returnVal!)
return ret
default:
print ("bat input status")
}
あとは数値入力時と演算子が入ってきたときに、インプットステータス(モード)をそれぞれ変更します。
また、=演算のあとに、インプットステータスをクリア(false,false)しなくてはいけません。
結果、通常演算、結果メモリ演算、オペランドメモリ演算がいかのとおりできました。ただし、他のモードでどう動くかはまだ検証していません。
2
Operater5 *
2
Operater5 =
normal operation
4
Operater5 +
1
Operater5 =
result operation
5
1
Operater5 -
2
Operater5 =
normal operation
-1
3
Operater5 =
operand operation 3-2
1
5
Operater5 =
operand operation 5-2
3
とりあえずエンジンはここまでにしてUIを実装していくかなぁ
2019年7月22日月曜日
計算キュー の改善
さて計算キュー の改善に移りたいと思いますが、
これまでの検討結果から、
(1)通常演算では、
1+2=3
1+3=?
つまり「1+」「3=」
となりますので「3=」の前の入力ステータスは、
演算して結果が出た(numInputting = false calInputting = false)
のち、数値も演算子も入力されるため、
数値+(numInputting = true calInputting = true)となります。
(2)一方、結果に対する演算は、
1+2=3
「+」「3=」
となりますので、「3=」の前の入力ステータスは、
演算して結果が出た(numInputting = false calInputting = false)
ののちに、演算子しか操作されないため、
(numInputting = false calInputting = true)となります。
(3)オペランドメモリ演算の場合、
1+2=3
「3=」
となりますので、「3=」の前の入力ステータスは、
演算して結果が出た(numInputting = false calInputting = false)
つまり、一つ前の入力ステータスを確認することで、
numInputting = true calInputting = trueの場合は通常
numInputting = false calInputting = trueの場合は結果演算
numInputting = false calInputting = falseの場合はオペランド演算
これまでの検討結果から、
(1)通常演算では、
1+2=3
1+3=?
つまり「1+」「3=」
となりますので「3=」の前の入力ステータスは、
演算して結果が出た(numInputting = false calInputting = false)
のち、数値も演算子も入力されるため、
数値+(numInputting = true calInputting = true)となります。
(2)一方、結果に対する演算は、
1+2=3
「+」「3=」
となりますので、「3=」の前の入力ステータスは、
演算して結果が出た(numInputting = false calInputting = false)
ののちに、演算子しか操作されないため、
(numInputting = false calInputting = true)となります。
(3)オペランドメモリ演算の場合、
1+2=3
「3=」
となりますので、「3=」の前の入力ステータスは、
演算して結果が出た(numInputting = false calInputting = false)
のち、何も入力せずに演算要求(3=)がとなるので、
numInputting = false calInputting = falseとなります。
つまり、一つ前の入力ステータスを確認することで、
numInputting = true calInputting = trueの場合は通常
numInputting = false calInputting = trueの場合は結果演算
numInputting = false calInputting = falseの場合はオペランド演算
と切り分ければよいことがわかります。
ただ、3=自身で入力ステータスが変わってしまうので、
3=の前の入力ステータスを覚える必要があります。
この方針でメモリ演算をサポートしたいと思います。
ただ、3=自身で入力ステータスが変わってしまうので、
3=の前の入力ステータスを覚える必要があります。
この方針でメモリ演算をサポートしたいと思います。
電卓プログラムの根本的問題
=の計算での処理を考えていたところ、根本的問題として、数字キュー の初期値が0になっている点がありました。
数字キュー は、どのような状態にあっても0が表示されるとか、0のあとに0を入れさせないだとか、「.」をいれると「0.」とするとかの表示処理もあり、リセットしても0になります。
そのため、数字と演算子を入れて一旦リセットしても、リセットの事実は残ることはなく、「0」になってしまいます。
そのため、0+と、+のみとの区別がつかないという問題がありました。
もう0+を+のみと考えてもいいのですが、本当に0+としたいときに問題がでます。
一旦冷静になって考えましたが、
・数字の入力が少しでもあった場合のフラグ
numInputting
・演算子の入力があった場合のフラグ
calcInputting
・=があった場合のこれらのリセット
という形であれば、入力順での文法解釈などなしでいけるように思い始めました。
とりあえずその方向で実装していきましょう。
数字キュー は、どのような状態にあっても0が表示されるとか、0のあとに0を入れさせないだとか、「.」をいれると「0.」とするとかの表示処理もあり、リセットしても0になります。
そのため、数字と演算子を入れて一旦リセットしても、リセットの事実は残ることはなく、「0」になってしまいます。
そのため、0+と、+のみとの区別がつかないという問題がありました。
もう0+を+のみと考えてもいいのですが、本当に0+としたいときに問題がでます。
一旦冷静になって考えましたが、
・数字の入力が少しでもあった場合のフラグ
numInputting
・演算子の入力があった場合のフラグ
calcInputting
・=があった場合のこれらのリセット
という形であれば、入力順での文法解釈などなしでいけるように思い始めました。
とりあえずその方向で実装していきましょう。
2019年7月21日日曜日
ここまで実装して電卓の再設計が必要だと思う点
電卓をここまで実装してきましたが、どうやら再設計が必要になってきそうです。
あまりにも大胆な再設計であったため、コーディング中にわけがわからなくなり、一つ手前のバージョンまでCtl+Zで戻るはめに。
再設計の原因は=後の処理にあります。
(1)通常演算
電卓の機能としては=の機能には通常の
1+2=3
のほかに、
(2)演算結果メモリ演算
1+2=3
+1=(3+1)=4
といった継続的な計算処理があります。
(3)オペランドのメモリ演算
1+2=3
3=(3+2)=5
という、演算子と演算結果のメモリによる追加の演算というものもあります。
この場合、演算結果や演算子を覚えさせて、=があったときに処理させるのはいいのですが、通常演算なのか、演算結果に対する演算なのか、メモリ演算なのかを識別してあげる必要があります。
現在のキュー では、オペランドと演算子(含む=)を組みとして
CalQueue.Push (オペランド、演算子)という入力をもとに演算を組み立てているのですが、そうすると、
上にある入力は、いずれの場合も=としてしか認識できません。
2=
1=
3=
とりあえず(3)を置いておいて、
(1)と(2)については処理的には以下の処理となるのですが、パラメータを受け取ったときに、メモリ演算なのか、通常演算なのかの区別がつきません。
(1)通常演算
・オペランド1に数値が入っている
・演算子は前の演算子
・オペランド2に数値が入っていない
・入力された数値はオペランド2に入れる
(2)演算結果のメモリ演算
・計算結果は、オペランド1に入れる
・演算子は前の演算子
・オペランド2に数値が入っていない
・入力された数値はオペランド2に入れる
通常演算と演算結果メモリ演算との違いは、演算子との間に数値が入るかどうかです。
(1)通常演算
1+2=3
1+1=2
(2)演算結果メモリ演算
1+2=3
+1=
そのため、演算後に数値を入ればモードを変える(通常演算)にするという仕組みが必要です。しかしCalQueueは、数値と演算子だけを受け付けるので、そのようなモードは外部に持たせなくてはいけません。
文法としては、
数字、演算子、数字、=
数字、演算子、数字、=
演算子、=
数字、演算子、数字、=
数字、=
といった文脈を解釈して演算モードを切り替えさせなくてはいけません。
normalCal
operaterCal
operandCal
と名付けますか。
ちょっと実装していきたいと思います・・・
=>この実装はやめました・・・
あまりにも大胆な再設計であったため、コーディング中にわけがわからなくなり、一つ手前のバージョンまでCtl+Zで戻るはめに。
再設計の原因は=後の処理にあります。
(1)通常演算
電卓の機能としては=の機能には通常の
1+2=3
のほかに、
(2)演算結果メモリ演算
1+2=3
+1=(3+1)=4
といった継続的な計算処理があります。
(3)オペランドのメモリ演算
1+2=3
3=(3+2)=5
という、演算子と演算結果のメモリによる追加の演算というものもあります。
この場合、演算結果や演算子を覚えさせて、=があったときに処理させるのはいいのですが、通常演算なのか、演算結果に対する演算なのか、メモリ演算なのかを識別してあげる必要があります。
現在のキュー では、オペランドと演算子(含む=)を組みとして
CalQueue.Push (オペランド、演算子)という入力をもとに演算を組み立てているのですが、そうすると、
上にある入力は、いずれの場合も=としてしか認識できません。
2=
1=
3=
とりあえず(3)を置いておいて、
(1)と(2)については処理的には以下の処理となるのですが、パラメータを受け取ったときに、メモリ演算なのか、通常演算なのかの区別がつきません。
(1)通常演算
・オペランド1に数値が入っている
・演算子は前の演算子
・オペランド2に数値が入っていない
・入力された数値はオペランド2に入れる
・計算結果は、オペランド1に入れる
・演算子は前の演算子
・オペランド2に数値が入っていない
・入力された数値はオペランド2に入れる
通常演算と演算結果メモリ演算との違いは、演算子との間に数値が入るかどうかです。
(1)通常演算
1+2=3
1+1=2
(2)演算結果メモリ演算
1+2=3
+1=
そのため、演算後に数値を入ればモードを変える(通常演算)にするという仕組みが必要です。しかしCalQueueは、数値と演算子だけを受け付けるので、そのようなモードは外部に持たせなくてはいけません。
文法としては、
数字、演算子、数字、=
数字、演算子、数字、=
演算子、=
数字、演算子、数字、=
数字、=
といった文脈を解釈して演算モードを切り替えさせなくてはいけません。
normalCal
operaterCal
operandCal
と名付けますか。
ちょっと実装していきたいと思います・・・
=>この実装はやめました・・・
オーバーフローによるストップ処理
オーバーフロが生じた場合は、オーバーフローシングルトンのオーバフローステータスを見て、文字入力を受け付けないようにします。
オーバーフロのチェックは、各計算結果に対して行うだけです。エラー表示はオーバーフローオブジェクト側で実施します。
で、計算の結果は以下のような感じです。
最後E表示のあと8桁のオーバーフロー数値を示すことができました。
ということで、UIなしで電卓の処理の実装は少しづ完成に近づいてきています。
func push(_ s:String){
if flow.overFlow { return }
}// end NumberQueue.push
func pop() {
if flow.overFlow { return }
}// end NumberQueue.pop
func calc(_ char:Character){
if flow.overFlow { return }
}
オーバーフロのチェックは、各計算結果に対して行うだけです。エラー表示はオーバーフローオブジェクト側で実施します。
if var result = calQueue.push(nil,previousOperater){ // 演算子を投入し、演算結果を得る
// ----------------------------------
let ret = flow.checkOverFlow(result)
result = ret.retValString
// ----------------------------------
if let numberString = formattedNumberStringForPop(result){ //演算結果があれば整形して表示する
print (numberString)
で、計算の結果は以下のような感じです。
2
Operater5 *
Operater1 *=
4
Operater1 *=
16
Operater1 *=
256
Operater1 *=
65,536
Operater1 *=
4,294,967,296
Operater1 *=
E
18,446,744.0737
ということで、UIなしで電卓の処理の実装は少しづ完成に近づいてきています。
電卓のオーバーフロー処理
電卓の小数点以下処理が実装できたので、これに従ったオーバーフロー処理を実装してみました。
ここでは、数値を文字列として入れて、これが指定桁(初期値12桁)を超えているかどうかを判定し、0以上であれば小数点以下を処理した上で、オーバーフロー桁でピリオドを打ちます。0未満であれば小数点12桁未満の処理をして値を返します。
オーバーフロ・アンダーフローが起きたかどうかは、返り値のoverFlowフラグで返します。
まだ動かしていないのでこれでいいかどうかわかりませんが、とりあえずこのように設計してみました。さて、どうなることやら。
class Flow{
var queueDigit: Int
var overFlow: Bool
private let cut54up = CUT54UP.sharedCUT54UP
init(){
queueDigit = 12
overFlow = false
}
func checkOverFlow(_ input: String) -> (overFlow: Bool, retValString: String) {
var retnum = input
let dotRemoved = input.removeCharacters(from:".")
overFlow = dotRemoved.count > queueDigit
if overFlow {
if !(retnum.contains(".")) {
var startVal: Decimal
startVal = 1.0
for _ in 1 ... (dotRemoved.count - queueDigit) {
startVal = startVal * 0.1
}
var decimalNo = Decimal(string: retnum)! * startVal
retnum = NSDecimalNumber(decimal: decimalNo).stringValue
retnum = cut54up.change(retnum)
startVal = 1.0
for _ in 1 ... (retnum.count + queueDigit - dotRemoved.count) {
startVal = startVal * 0.1
}
decimalNo = Decimal(string: retnum)! * startVal
retnum = NSDecimalNumber(decimal: decimalNo).stringValue
print ("E")
}
}
else{
var startVal: Decimal
startVal = 1.0
for _ in 1 ... (queueDigit) {
startVal = startVal * 10
}
var decimalNo = Decimal(string: retnum)! * startVal
retnum = NSDecimalNumber(decimal: decimalNo).stringValue
retnum = cut54up.change(retnum)
decimalNo = Decimal(string: retnum)! / startVal
retnum = NSDecimalNumber(decimal: decimalNo).stringValue
}
return (overFlow, retnum)
}
}
ここでは、数値を文字列として入れて、これが指定桁(初期値12桁)を超えているかどうかを判定し、0以上であれば小数点以下を処理した上で、オーバーフロー桁でピリオドを打ちます。0未満であれば小数点12桁未満の処理をして値を返します。
オーバーフロ・アンダーフローが起きたかどうかは、返り値のoverFlowフラグで返します。
まだ動かしていないのでこれでいいかどうかわかりませんが、とりあえずこのように設計してみました。さて、どうなることやら。
小数点以下の処理
Swiftの文字列操作を使って小数点以下の処理を実装してみました。
モード切り替えがあり、モードを覚えていないといけないので、シングルトンで実装しています。
こんな感じでしょうか。このコードはまだテストしていないので、ただしく動くかわかりませんが、こんな感じで書けそうです。
class CUT54UP: NSObject{
var mode: String
static let sharedCUT54UP: CUT54UP = CUT54UP()
private override init(){
mode = "CUT"
}
func change(_ input: String) -> String {
if input.contains(".") {
switch mode {
case "CUT":
let text = String(input[input.startIndex ..< input.firstIndex(of:".")!])
return text
case "5/4":
var text = String(input[input.startIndex ..< input.firstIndex(of:".")!])
let afterTheDecimalPoint = String(input[input.firstIndex(of:".")! ... input.endIndex])
let decimalPoint = String(afterTheDecimalPoint.prefix(1))
if Decimal(string: decimalPoint)! >= 5 {
let retVal = Decimal(string: text)! + Decimal(integerLiteral: 1)
text = NSDecimalNumber(decimal: retVal).stringValue
}
return text
case "UP":
var text = String(input[input.startIndex ..< input.firstIndex(of:".")!])
let afterTheDecimalPoint = String(input[input.firstIndex(of:".")! ... input.endIndex])
let decimalPoint = String(afterTheDecimalPoint.prefix(1))
if Decimal(string: decimalPoint)! > 0 {
let retVal = Decimal(string: text)! + Decimal(integerLiteral: 1)
text = NSDecimalNumber(decimal: retVal).stringValue
}
return text
default:
return input
}
}
else{
return input
}
}
}
モード切り替えがあり、モードを覚えていないといけないので、シングルトンで実装しています。
こんな感じでしょうか。このコードはまだテストしていないので、ただしく動くかわかりませんが、こんな感じで書けそうです。
Swiftの文字列操作が混乱する理由
Swiftの文字列操作をどうするのか混乱する理由は、
・String.indexという文字列操作は通常indexで処理する一方で、
・String.prefix(n)とか、String.suffix(n)、string.count
という感じで、先頭や後ろからn文字という関数も存在しているため、どちらで実装すべきかが混乱するためです。
さらにここでnは数値なのですが、String.indexから簡単にnが得られないというのが問題です。
また、String.indexを使うにしても幅のある型のため、先頭のみを使う場合に混乱することになります。さらにその範囲を含むか、含まないか、という違いで実装も変わるという、非常に複雑なものになっています。
そもそも文字列操作は、Swiftのバージョンによって大きく見直しがかけれており、それも混乱する一つの要因になっています。
いろいろといじってみると、先頭から特定の文字の前までの文字列は、Swift4.2では基本以下になるようです。
let text = String(input[input.startIndex ..< input.firstIndex(of:".")!])
やはり、prefixとかは使えないので、rangeで指定します。先頭はstrtIndexで、文字列のindexはfirstIndex(of:文字列)となるようです。さらに見つからない場合はnilになるので、!でオプショナルバインディングをアンラップします(もちろんその前にnilにならないようにしておきます)。
・String.indexという文字列操作は通常indexで処理する一方で、
・String.prefix(n)とか、String.suffix(n)、string.count
という感じで、先頭や後ろからn文字という関数も存在しているため、どちらで実装すべきかが混乱するためです。
さらにここでnは数値なのですが、String.indexから簡単にnが得られないというのが問題です。
また、String.indexを使うにしても幅のある型のため、先頭のみを使う場合に混乱することになります。さらにその範囲を含むか、含まないか、という違いで実装も変わるという、非常に複雑なものになっています。
そもそも文字列操作は、Swiftのバージョンによって大きく見直しがかけれており、それも混乱する一つの要因になっています。
いろいろといじってみると、先頭から特定の文字の前までの文字列は、Swift4.2では基本以下になるようです。
let text = String(input[input.startIndex ..< input.firstIndex(of:".")!])
やはり、prefixとかは使えないので、rangeで指定します。先頭はstrtIndexで、文字列のindexはfirstIndex(of:文字列)となるようです。さらに見つからない場合はnilになるので、!でオプショナルバインディングをアンラップします(もちろんその前にnilにならないようにしておきます)。
いやぁ、たどり着くまでに結構な時間が必要でした。勘弁してほしいわ。
2019年7月20日土曜日
電卓キュー のオーバーフローとアンダーフロー
現在の電卓キュー でのオーバーフローとアンダーフローがどうなるかをべき乗計算を繰り返してためしてみました。
試したところべき乗になってなかったので、計算自身を見直しましたが(w)、以下のような結果になりました。
(1)オーバーフロー
以下のようにオーバーフローした時点で0になっているようです。
(2)アンダーフロー
こちらもゼロになっています。
普通Decimal型の場合、オーバーフローではNanが返され、アンダーフローでは数値が1以上になったりするようですが、この電卓キュー では計算結果を文字列で受け取っているため、ゼロになっているようです。
とりあえずいずれも12桁や14桁は計算できているようなので、計算できていることを前提に、そのまま電卓のオーバーフロー処理を書いていきます。
試したところべき乗になってなかったので、計算自身を見直しましたが(w)、以下のような結果になりました。
(1)オーバーフロー
以下のようにオーバーフローした時点で0になっているようです。
2
20
Operater5 *
Operater1 *=
400
Operater2 *
Operater1 *=
160,000
Operater2 *
Operater1 *=
25,600,000,000
Operater2 *
Operater1 *=
655,360,000,000,000,000,000
Operater2 *
Operater1 *=
429,496,729,600,000,000,000,000,000,000,000,000,000,000
Operater2 *
Operater1 *=
0
Operater2 *
Operater1 *=
0
Operater2 *
Operater1 *=
0
(2)アンダーフロー
こちらもゼロになっています。
0.
0.2
Operater5 *
Operater1 *=
0.04
Operater2 *
Operater1 *=
0.0016
Operater2 *
Operater1 *=
0.00000256
Operater2 *
Operater1 *=
0.0000000000065536
Operater2 *
Operater1 *=
0
Operater2 *
Operater1 *=
0
Operater2 *
Operater1 *=
普通Decimal型の場合、オーバーフローではNanが返され、アンダーフローでは数値が1以上になったりするようですが、この電卓キュー では計算結果を文字列で受け取っているため、ゼロになっているようです。
とりあえずいずれも12桁や14桁は計算できているようなので、計算できていることを前提に、そのまま電卓のオーバーフロー処理を書いていきます。
2019年7月18日木曜日
文字列から文字を取り除く(Swift)
Swiftのインターネット上の日本語のコンテンツはやはり極端に少ないですね。使っている人がかなり少ないような気がします。
で、Swiftの文字列操作は、バージョンによってかなり変わってきているようで、インターネットで検索したものも、使えるものがなかったりします。
文字列から文字を取り除く方法も見つけづらかったですが、見つけることができました。
こんな感じでStringを拡張しないと簡単には操作できないようです。
Appleさんは、Swiftの文字列操作にもう少し基本的なメソッドを実装したほうがいいような気がします。
ちなみにここの引数名はfromじゃないだろ、という気もしないでもないですが、とりあえず動くのでこのままにしておきます・・
で、Swiftの文字列操作は、バージョンによってかなり変わってきているようで、インターネットで検索したものも、使えるものがなかったりします。
文字列から文字を取り除く方法も見つけづらかったですが、見つけることができました。
こんな感じでStringを拡張しないと簡単には操作できないようです。
extension String{
func removeCharacters(from forbiddenChars: CharacterSet)-> String{
let passed = self.unicodeScalars.filter {!forbiddenChars.contains($0)}
return String(String.UnicodeScalarView(passed))
}
func removeCharacters(from: String) -> String{
return removeCharacters(from: CharacterSet(charactersIn: from))
}
}
Appleさんは、Swiftの文字列操作にもう少し基本的なメソッドを実装したほうがいいような気がします。
ちなみにここの引数名はfromじゃないだろ、という気もしないでもないですが、とりあえず動くのでこのままにしておきます・・
電卓の有効桁の処理
電卓の小数点などの処理を実装しようとしたところ、そもそもがその処理を理解していないことに気がつき、いろいろと調べてみました。
初めは、Doubleとかの不動小数点について調べましたが、どうやら2進数での有効桁とかなんとか、ということで、10進数化したエンジンとは別の話になりそうなので、深くはみませんでした。
次に、数学的な有効数字と丸めについても調べましたが、結局それらら「測定値」の話であって、測定値かどうかが明確でない電卓の問題ではなさそうなので、とりあえず見送りました。もっとも、測定値を前提とする有効桁の機能を設けると、その世界ではニーズはありそうですが、ユーザ数も限られるのでとりあえず見送りとしました。
結局は電卓の取り扱い説明書で、どのような表示になるのか確認しようとしましたが、エラーの話がちょっと出ているだけで、これも役に立ちませんでした。
そうこうして、調べるなかで、「統計学 細く文書 電卓の使い方」という謎の文書が登場してきました。それがこれです。
http://www.sguc.ac.jp/i/st/learning/statistics/calculator/index1.pdf
すると、さらに知らなかった電卓の裏機能の解説が満載。どうやらカシオ系とシャープ系でそれぞれ操作が違うようです。ぱっと見はシャープ系の方が余計な操作が不要っぽいので、シャープ系の方がよさそうな気がしますが、とりあえず裏機能の実装は優先度を下げて、表示機能に焦点を当てます。
(1)オーバーフロー
電卓は桁が決まっている点が、普通のコンピュータでの演算と違うところです。そういう意味ではdecimal演算とNumberFormatterとの相性はいいかもしれません。
12桁で1000億円の計算ができるで、一般の利用には十分かと思うので、とりあえず12桁でいきますかね。
で、オーバーフローはとりあえず例で示されていました。
1234567x1234567=
で12桁の場合は、こんな感じになりそうです。
E 1.52415567748
この小数点は桁あふれということらしいです。計算値は「1524155677489」なので、この9があふれたということを示しています。
(2)オーバーフローの判定
問題はあふれたかどうかの判定をどう計算するか、ですね。「1000000000000」以上かどうかで判定し、あふれ桁は割った結果で判断という感じですかねぇ。ちょっと複雑そうなので桁あふれ判定オブジェクトを作ってみたいと思います(電卓の中はどうなっているんだか想像できないすけど・・)。
(3)オーバーフロー後のエラー処理
桁があふれた場合、ACなどのキーを押さない限り、処理はストップするようです。桁あふれでCを押すとエラーだけが解除されるそうですが、そのあとの処理は、カシオとシャープで異なるようです。どう違うかは謎ですが、そのまま計算させるのがいいのかもしれません。
(4)小数点以下の桁あふれ
ちなみに小数点の場合は、あふれ桁は切ってしまうようです。
1.234567x1.234567 =
「1.52315567748」ですね。9が切られます。
(5)小数点以下の桁あふれの処理
解説にはこの最後の桁の処理をどうするのかはかかれていないのですが、とりあえず「切る」でいいかと思います。
ただフォーマッターでは四捨五入をしていたような気がするので、桁あふれ部分の標準は四捨五入でいいような気もします。
(6)小数点以下の桁あふれに関連する処理
巻紙電卓では、これまで通常(デフォルト)の浮動小数モードFだと、小数点処理を明示していなかったのですが、5/4を表示させたいと思います(これまで4/5になっていた・・誰か指摘してくれよ・・)。だた結局はCUTモードやHIDEモードを作らなくてはいけないので、作る必要があるのには変わりないんですけどねぇ。
ということで、桁の処理については大凡の機能は固まりました、次回はその実装です。
初めは、Doubleとかの不動小数点について調べましたが、どうやら2進数での有効桁とかなんとか、ということで、10進数化したエンジンとは別の話になりそうなので、深くはみませんでした。
次に、数学的な有効数字と丸めについても調べましたが、結局それらら「測定値」の話であって、測定値かどうかが明確でない電卓の問題ではなさそうなので、とりあえず見送りました。もっとも、測定値を前提とする有効桁の機能を設けると、その世界ではニーズはありそうですが、ユーザ数も限られるのでとりあえず見送りとしました。
結局は電卓の取り扱い説明書で、どのような表示になるのか確認しようとしましたが、エラーの話がちょっと出ているだけで、これも役に立ちませんでした。
そうこうして、調べるなかで、「統計学 細く文書 電卓の使い方」という謎の文書が登場してきました。それがこれです。
http://www.sguc.ac.jp/i/st/learning/statistics/calculator/index1.pdf
すると、さらに知らなかった電卓の裏機能の解説が満載。どうやらカシオ系とシャープ系でそれぞれ操作が違うようです。ぱっと見はシャープ系の方が余計な操作が不要っぽいので、シャープ系の方がよさそうな気がしますが、とりあえず裏機能の実装は優先度を下げて、表示機能に焦点を当てます。
(1)オーバーフロー
電卓は桁が決まっている点が、普通のコンピュータでの演算と違うところです。そういう意味ではdecimal演算とNumberFormatterとの相性はいいかもしれません。
12桁で1000億円の計算ができるで、一般の利用には十分かと思うので、とりあえず12桁でいきますかね。
で、オーバーフローはとりあえず例で示されていました。
1234567x1234567=
で12桁の場合は、こんな感じになりそうです。
E 1.52415567748
この小数点は桁あふれということらしいです。計算値は「1524155677489」なので、この9があふれたということを示しています。
(2)オーバーフローの判定
問題はあふれたかどうかの判定をどう計算するか、ですね。「1000000000000」以上かどうかで判定し、あふれ桁は割った結果で判断という感じですかねぇ。ちょっと複雑そうなので桁あふれ判定オブジェクトを作ってみたいと思います(電卓の中はどうなっているんだか想像できないすけど・・)。
桁があふれた場合、ACなどのキーを押さない限り、処理はストップするようです。桁あふれでCを押すとエラーだけが解除されるそうですが、そのあとの処理は、カシオとシャープで異なるようです。どう違うかは謎ですが、そのまま計算させるのがいいのかもしれません。
(4)小数点以下の桁あふれ
ちなみに小数点の場合は、あふれ桁は切ってしまうようです。
1.234567x1.234567 =
「1.52315567748」ですね。9が切られます。
(5)小数点以下の桁あふれの処理
解説にはこの最後の桁の処理をどうするのかはかかれていないのですが、とりあえず「切る」でいいかと思います。
ただフォーマッターでは四捨五入をしていたような気がするので、桁あふれ部分の標準は四捨五入でいいような気もします。
(6)小数点以下の桁あふれに関連する処理
巻紙電卓では、これまで通常(デフォルト)の浮動小数モードFだと、小数点処理を明示していなかったのですが、5/4を表示させたいと思います(これまで4/5になっていた・・誰か指摘してくれよ・・)。だた結局はCUTモードやHIDEモードを作らなくてはいけないので、作る必要があるのには変わりないんですけどねぇ。
ということで、桁の処理については大凡の機能は固まりました、次回はその実装です。
2019年7月17日水曜日
電卓の隠れ機能
電卓には隠れ機能があり、
3+===
という操作で自分自身を足したり掛けたりといった操作のほかに
1000x2 = 2000
という計算をしたあとに
10=
とすると、x2が有効になり、20という計算結果がでる、という機能もあるようです。
(メモリ機能)
この機能はまったく知らなかったので、実装してみることにしましたが、一旦「=」で計算したあとで、前の計算結果を使うというのは想定外で、計算用のキュー を作り変えることにしました。
方法としては、数字をPushしてから演算子をPushするという仕組みで作っていたのですが、数字と演算子を一度にPushするという形態に変えました。
この場合、+=とかの機能では数字の入力が不要になりますので、数字の部分にはnilを入れることにします。
そうすると、計算用のキュー はこんな感じになります。
前半が数字の入力からオペランドに実装するところで、数字=が入力されたときは演算子を==として数字をオペランド1にいれることで今回の機能を実現しています。
通常演算でA+B=のあとに=を入れた場合はまた別の計算で、計算結果をオペランド1に入れて計算結果を残し、=のみが入った時にはその残したオペランドと演算子で計算をさせることになります。
結構複雑な処理を実装しましたが、コード自身の長さはそう変わらず、かなりいい感じで実装できています。
あとは有効桁の機能や通貨計算用機能を組み込んだらエンジンは完成、という感じです。
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に入れて計算結果を残し、=のみが入った時にはその残したオペランドと演算子で計算をさせることになります。
結構複雑な処理を実装しましたが、コード自身の長さはそう変わらず、かなりいい感じで実装できています。
あとは有効桁の機能や通貨計算用機能を組み込んだらエンジンは完成、という感じです。
登録:
投稿 (Atom)