blechmusikの日記

キー・カスタマイズ・ソフトウェア "DvorakJ" の覚え書きをはじめとして様々なことを書いています。

逆ポーランド記法用の簡易計算機を作成した

概要

通常の四則演算をはじめとして、三角関数、順列や組み合わせを後置記法で出力できるようにした*1。たとえば、"10 5 c" と入力すれば、袋に入っている"10" 個の玉から "5" 個を選び出す組み合わせ("C"ombination)を出力する。プログラムは上記のリンク先からダウンロード出来る。

これにより、数値の計算に括弧を使用する必要がなくなった。従来、"( (51 / 23) + (84 * 92) ) * 21" のように丸括弧を使用していた式を、"51 23 / 84 92 * + 21 *" のように、テンキーのみで入力できる式として記述できるようになったわけだ*2

仕様一覧

演算・変数 - AutoHotkeyJpを参照のこと。

記号や文字列 使用例 出力例 説明
+ 1 2 + 3
- 1 2 - -1
* 1 2 * 2
/ 1 2 / 0.500000
// 1 2 // 0.5
% 7 6 % 1
^ 7 6 ^ 117649
P 7 6 p 5040 順列(permutation)
C 7 6 c 7 組み合わせ(combination)
記号や文字列 使用例 出力例 説明
Abs -10 Abs 10 絶対値
Ceil 10.456 ceil 11
Floor 10.456 Floor 10
Round 10.456 Round 10
Sqrt 10 Sqrt 3.162278
Exp 10 Exp 22026.465795
Log 10 Log 1.000000
Ln 10 Ln 2.302585
Sin 10 Sin -0.544021
Cos 10 Cos -0.839072
Tan 10 Tan 0.648361
ASin 1 ASin 1.570796
ACos 0.1 ACos 1.470629
ATan 10 ATan 1.471128
記号や文字列 使用例 出力例 説明
! 20! 2432902008176640000 階乗

課題

AutoHotkey 本家から続いている桁あふれの問題には、後に対処することにしよう。

ソース

ソースは以下の通りである。

#persistent
#singleinstance force

;;; [AHK_L] Arrays
;;; http://www.autohotkey.com/forum/topic49736.html
#include Array.ahk

SetTitleMatchMode,3


Gui, Font , S14, consolas

Gui, Add, Edit
    , vargv gInputEditForm R1 W300
    , Input:

Gui, Add, Edit
    , vOutputVar R1 W300 ReadOnly
    , Result:

Gui, Show, , RPN calculator

return



#IfWinActive, RPN calculator
Enter::
    Send,{Space}
return

NumpadEnter::
    Send,{Space}
return


InputEditForm:
    Gui, Submit, NoHide
    rpn(argv)
return



rpn(argv)
{
    stack := Array()
    i := 0

    Loop, Parse, argv, %A_Space%
    {
        if (A_LoopField = "")
            continue


        if A_LoopField is number
        {
            stack.append(A_LoopField)
        }
        else
        {
            if (RegExMatch(A_LoopField, "(\d+)!$", num)) ; 階乗 !
            {
                stack.append(factorial(num1))
            }
            else
            {
                if (i < 1)
                    exit

                b := stack_pop(stack, i)
                i := i - 1

                if (A_LoopField = "Abs")
                    stack.append(Abs(b))
                else
                if (A_LoopField = "Ceil")
                    stack.append(Ceil(b))
                else
                if (A_LoopField = "Floor")
                    stack.append(Floor(b))
                else
                if (A_LoopField = "Round")
                    stack.append(Round(b))
                else
                if (A_LoopField = "Sqrt")
                    stack.append(Sqrt(b))
                else
                if (A_LoopField = "Exp")
                    stack.append(Exp(b))
                else
                if (A_LoopField = "Log")
                    stack.append(Log(b))
                else
                if (A_LoopField = "Ln")
                    stack.append(Ln(b))
                else
                if (A_LoopField = "Sin")
                    stack.append(Sin(b))
                else
                if (A_LoopField = "Cos")
                    stack.append(Cos(b))
                else
                if (A_LoopField = "Tan")
                    stack.append(Tan(b))
                else
                if (A_LoopField = "ASin")
                    stack.append(ASin(b))
                else
                if (A_LoopField = "ACos")
                    stack.append(ACos(b))
                else
                if (A_LoopField = "ATan")
                    stack.append(ATan(b))
                else
                {
                    ;; 数値が何も残っていないときには
                    ;; 何も処理せず終了する
                    if (i < 1)
                        exit

                    a := stack_pop(stack, i)
                    i := i - 1


                    ;; 演算・変数 - AutoHotkeyJp
                    ;; https://sites.google.com/site/autohotkeyjp/reference/commands/index_calc

                    if (A_LoopField = "+")
                        stack.append(a + b)
                    else
                    if (A_LoopField = "-")
                        stack.append(a - b)
                    else
                    if (A_LoopField = "*")
                        stack.append(a * b)
                    else
                    if (A_LoopField = "/")
                        stack.append(a / b)
                    else
                    if (A_LoopField = "//")
                        stack.append(a // b)
                    else
                    if (A_LoopField = "%")
                        stack.append( Mod(a, b) )
                    else
                    if (A_LoopField = "^")
                        stack.append( a ** b )
                    else
                    if (A_LoopField = "p") ; 順列 (permutation)
                    {
                        if (a < b)
                            exit

                        stack.append( factorial(a) // factorial(a - b) )
                    }
                    else
                    if (A_LoopField = "c") ; 組み合わせ (combination)
                    {
                        if (a < b)
                            exit

                        stack.append( factorial(a) // (factorial(b) * factorial(a - b)) )
                    }
                }
            }
        }

        i := i + 1
    }


    ;; show_stack(stack)

    if (stack.len() = 1)
        show_result( stack[1] )
    else
        show_result( "" )

    return
}



stack_pop(ByRef obj, index)
{
    item := obj[index]
    obj.pop()

    return item
}


show_result(item)
{
    GuiControl, , OutputVar, % item

    return
}

show_stack(obj)
{
    all_stack := ""
    loop,% obj.len()
        all_stack := obj[A_index] . "`n" . all_stack 

    tooltip,% all_stack

    return
}




;;; 階乗
factorial(n)
{
    return % (n = 0) ? 1
                     : n * factorial(n -1)
}

後日追記:逆ポーランド記法用の計算機で、スタックの内容を表示できるようにした - blechmusik2の日記を公開した。

*1:逆ポーランド記法 - Wikipedia

*2:[Enter] で空白を出力するよう調整している。