2019年3月24日日曜日

Windows電卓の実装を見てみる


お久しぶりです。やっまむーです。
プログラミングの練習で電卓を作ったことってありませんか?
私も最初の頃にコマンドライン上で動作する簡単な練習アプリを作ったことがあります。
四則演算するだけのものでしたが、動いたときは楽しかった事を覚えています。

3月6日にMicrosoftが「Windows電卓」をオープンソース化してGithubで公開しました。
たかが電卓、されど電卓。
前々から気になっていた、
「Windows電卓は普通に考える電卓と比較して何が違うのか」
を調べてみようと思います。

ソースコードを入手

さっそくGithubからソースコード一式を入手します。
https://github.com/Microsoft/calculator
公開後も更新されています

計算処理を探そう

続けてVisualStudioでソリューションを開きます。
なんか色々います。

UWP(ユニバーサルWindowsプラットフォーム)に対応しており、ソースコードはC++で書かれています。


調べていくとCalcManagerに計算関連の処理がまとめられていました。
この中で四則演算処理をしているのはscioper.cppのCCalcEngine::DoOperation()になります。

引数で演算子(operand)と左辺、右辺の数値(lhs、rhs)を受け取り、計算をしています。
左辺、右辺の型であるRationalクラスは次のようになっています。

m_p、m_qと値を二つ持っています。
なぜ二つあるのかはわかりませんが、それを意識せずに使うためにオペレータのオーバーライドを行っています。
例えば加算処理であれば、以下のようになっています。

CEngine/Rational.cpp
Rational operator+(Rational lhs, Rational const& rhs)
{
    lhs += rhs;
    return lhs;
}
Rational& Rational::operator+=(Rational const& rhs)
{
    PRAT lhsRat = this->ToPRAT();
    PRAT rhsRat = rhs.ToPRAT();

    try
    {
        addrat(&lhsRat, rhsRat, RATIONAL_PRECISION);
        destroyrat(rhsRat);
    }
    catch (DWORD error)
    {
        destroyrat(lhsRat);
        destroyrat(rhsRat);
        throw(error);
    }

    *this = Rational{ lhsRat };
    destroyrat(lhsRat);

    return *this;
}
RatPack/rat.cpp
//-----------------------------------------------------------------------------
//
//    FUNCTION: addrat
//
//    ARGUMENTS: pointer to a rational a second rational.
//
//    RETURN: None, changes first pointer.
//
//    DESCRIPTION: Does the rational equivalent of *pa += b.
//    Assumes base is internal throughout.
//
//-----------------------------------------------------------------------------

void addrat( PRAT *pa, PRAT b, int32_t precision)

{
    PNUMBER bot= nullptr;

    if ( equnum( (*pa)->pq, b->pq ) )
        {
        // Very special case, q's match.,
        // make sure signs are involved in the calculation
        // we have to do this since the optimization here is only
        // working with the top half of the rationals.
        (*pa)->pp->sign *= (*pa)->pq->sign;
        (*pa)->pq->sign = 1;
        b->pp->sign *= b->pq->sign;
        b->pq->sign = 1;
        addnum( &((*pa)->pp), b->pp, BASEX );
        }
    else
        {
        // Usual case q's aren't the same.
        DUPNUM( bot, (*pa)->pq );
        mulnumx( &bot, b->pq );
        mulnumx( &((*pa)->pp), b->pq );
        mulnumx( &((*pa)->pq), b->pp );
        addnum( &((*pa)->pp), (*pa)->pq, BASEX );
        destroynum( (*pa)->pq );
        (*pa)->pq = bot;
        trimit(pa, precision);

        // Get rid of negative zeros here.
        (*pa)->pp->sign *= (*pa)->pq->sign;
        (*pa)->pq->sign = 1;
        }

#ifdef ADDGCD
    gcdrat( pa );
#endif

}
こうして見るとなかなか興味深く、他の処理も解析すると勉強になりそうです。
また、UWPアプリの参考にもいいと思います。
皆さんもお時間があれば是非とも解析してみてはいかがでしょうか。

それではまた~。

0 件のコメント:

コメントを投稿