C++の多倍長整数をリファクタリングした話

どうもっ!!!!最近SCPにハマってるポテト君です。

いや、今回の話にSCPの話はでてこないです…w

ところで、SCP-010-JPである「Mirai PC」というSCiPを知ってますか?C言語のようなプログラムを扱えるのですが、未来の状態を返す関数というものg((((((((((((

SCPの話しないとか言いながら普通に言ってしまいました、ちゃんと本題に入ります。

半年ほど前に、このプログラムを作りました。
(ここで説明したものと変化しない部分は特に解説しないので、わからなかったらこっちも見てください。とはいってもリファクタリング後はプログラム自体にコメント結構書いてます。)

学校の実験で多倍長整数をやって、思い出してこのプログラムを見てみました。

それで…もっといい書き方があるんじゃないか?と思いました。
そしたらリファクタリングするしかないじゃああああああああああああああああん

まあ、半年も経ってますし、4ヶ月ほど前から毎日GitHub更新してるので、だいぶ変わってきますよね。特に読みやすさに関して()

実験のC言語の多倍長整数を拡張させて、リスト化したり色々したものもありますが、リストを作るところからなので説明がなっっっっっっがいために今回は飛ばします。あとで紹介するかも。

ということで、リファクタリングしたものを紹介していきます。

ソースコード全体はGitHubに上げてあります。

まずは、全体に関わるところを言っておくと
newintクラスのprivateメンバについて
valをvalue
miをminus
に改名しました。

コンストラクタ

リファクタリング前

   newint() {}
   newint(newint *a)
   {
       val = a->val;
       mi = a->mi;
   }
   newint(string newv)
   {
       if (newv[0] == '-')
       {
           val = newv.substr(1);
           mi = true;
       }
       else
       {
           val = newv;
       }
       reverse(val.begin(), val.end() );
   }

リファクタリング後

    /**
     * @brief 値が空のnewintインスタンスを作成します。
     */
    newint() {}

    /**
     * @brief 値渡しでコピーしたnewintインスタンスを作成します。
     * @param newint_inst newintインスタンス
     */
    newint(newint *newint_inst)
    {
        value = newint_inst->value;
        minus = newint_inst->minus;
    }

    /**
     * @brief 渡した値を表すnewintインスタンスを作成します。
     * @param newValue 値を表す文字列
     */
    newint(string newValue)
    {
        this->set(newValue); // setメソッドは下に記述してある
    }

3つめのコンストラクタ(文字列から値を決めるもの)は、set関数に処理を移しました。

set関数

リファクタリング前

   void set(string newv)
   {
       newint a(newv);
       val = a.val;
       mi = a.mi;
   }

リファクタリング後

    /**
     * @brief 渡した値でインスタンスが表す値を設定します。
     * @param newValue 値を表す文字列
     */
    void set(string newValue)
    {
        if (newValue == "-0")
        {
            value = "0";
            minus = false;
        }
        else if (newValue[0] == '-')
        {
            value = newValue.substr(1);
            minus = true;
        }
        else
        {
            value = newValue;
            minus = false;
        }
        reverse(value.begin(), value.end());
    }

値が"-0"にならないようにしました。
新しい値が正の数のときにminusがtrueのままになるので、else部にminus=false;を追加しました。(リファクタリングというかデバッグ)

str関数

 リファクタリング前

   string str(bool z = false)
   {
       string v(val);
       reverse(v.begin(), v.end());
       while ((v[0] == '0') && (v.size() != 1))
       {
           v = v.substr(1);
       }
       if (mi && !z)
       {
           return "-" + v;
       }
       return v;
    }

リファクタリング後

    /**
     * @brief インスタンスが表す値を文字列で返します。
     * @param abs trueにすると絶対値を返します。
     * @return string インスタンスが表す値
     */
    string str(bool abs = false)
    {
        string copy_val(value);
        reverse(copy_val.begin(), copy_val.end()); // 逆順に保存していたのを戻す
        while ((copy_val[0] == '0') && (copy_val.size() != 1))
        {                                  // ゼロ埋めがなくなるまで、桁数が0にならない限り
            copy_val = copy_val.substr(1); // 先頭の文字(ゼロ埋め)を消す
        }
        if (minus && !abs) // マイナスなら(返り値が絶対値でなければ)
        {
            return "-" + copy_val; // マイナスを付けて返す
        }
        return copy_val;
    }

変数名を分かりやすくしたりコメント書いたりしただけです。

keta関数

リファクタリング前

   void keta(int i)
   {
       int cou = i - val.size();
       for (int j = 0; j < cou + 1; j++)
       {
           val += "0";
       }
   }

リファクタリング後

    /**
     * @brief valueをi桁に0埋めします。
     * @param i
     */
    void keta(int i)
    {
        value += string(i - value.size(), '0'); // 差の分だけ0を追加
    }

処理本体は1行だけになりました。
string(int,char)関数は、intの数だけcharを繋げた文字列を返す関数です。
リファクタリング前でfor文の繰り返し回数はi-val.size()回だったので、結果は同じになります。

==

リファクタリング前

bool newint::operator==(newint a)
{
    newint b(this);
    if (b.str() == a.str()) return true;
    return false;
}

リファクタリング後

bool newint::operator==(newint a)
{ // b==a
    newint b(this);
    return (b.str() == a.str());
}

大して変わらないですが、返り値は (b.str()==a.str()) と同じなので、そのまま返すようにしました。

!=

リファクタリング前

bool newint::operator!=(newint a) 
{
    newint b(this);
    if (b.str() == a.str()) return false;
    return true;
}

リファクタリング後

bool newint::operator!=(newint a)
{ // b!=a
    newint b(this);
    return !(b == a);
}

b==aの否定を返すようにしました、!(b.str()==a.str())の方が良かったかもですが大して変わらないです。

<

リファクタリング前

bool newint::operator<(newint a) 
{
    newint b(this);
    if (a.str()=="0"||a.str()=="-0"&&b.str()=="0"||b.str()=="-0") return false;
    if (b.mi && !a.mi) return true;
    if (!b.mi && a.mi) return false;
    string ast, bst;
    ast = a.str();
    bst = b.str();
    if (!b.mi && !a.mi)
    {
        if (ast.size() > bst.size()) return true;
        if (ast.size() < bst.size()) return false;
        if (ast == bst) return false;
        for (int i = 0; i < ast.size(); i++)
        {
            int ai = int(ast[i] - '0');
            int bi = int(bst[i] - '0');
            if (ai > bi) return true;
            if (ai < bi) return false;
        }
    }
    if (b.mi && a.mi) return -a < -b;
    return false;
}

リファクタリング後

bool newint::operator<(newint a)
{ // b<a
    newint b(this);
    if (b.minus == !a.minus) // 異符号なら大小は明らか
        return b.minus;
    string a_str, b_str;
    a_str = a.str();
    b_str = b.str();
    if (!b.minus && !a.minus) // 正の数なら
    {
        if (a_str.size() > b_str.size()) // 文字数で比べる
            return true;
        if (a_str.size() < b_str.size())
            return false;
        for (int i = 0; i < a_str.size(); i++) // 各桁を比べる(valueではないので先頭の方が大きい桁)
        {
            if (a_str[i] > b_str[i])
                return true;
            if (a_str[i] < b_str[i])
                return false;
        }
    }
    if (b.minus && a.minus) // 負同士なら、正の結果の逆になる
        return -a < -b;     // b<a = -b>-a = -a<-b
    return false;
}

4行目にあった
if (a.str()=="0"…
とかいうやつはよくわからないです。
要らないので捨てました。
異符号の判定はb.minus == !a.minusでできます。
変数ai,biに関して、文字をわざわざ数字にしなくても文字コードの大きさで判定できるのでこれにあたる変数は作らないようにしました。

-(単項演算子)

リファクタリング前

newint newint::operator-()
{ 
    newint ret(this);
    (ret.mi) ? (ret.mi = false) : (ret.mi = true);
    return ret;
}

リファクタリング後

newint newint::operator-()
{ // 単項演算子の- (-xの-)
    newint ret(this);
    ret.minus = !ret.minus; // not演算子で反転
    return ret;
}

大して変わらないですが、わざわざ3項演算子を使うこともないです。

<=

リファクタリング前

bool newint::operator<=(newint a)
 {
    newint b(this);
    if (b == a) return true;
    return b < a;
}

リファクタリング後

bool newint::operator<=(newint a)
{ // b<=a
    newint b(this);
    return (b == a || b < a);
}

これもほぼ同じですが、==の場合をわざわざ分けることもないです。

>と>=は変えてないです。

+

リファクタリング前

newint newint::operator+(newint a)
{
    newint ret, b(this);
    int c = 0, v, max;
    (b.val.size() < a.val.size()) ? (max = a.val.size()) : (max = b.val.size());
    max++;
    a.keta(max);
    b.keta(max);
    if (!b.mi && !a.mi || b.mi && a.mi)
    {
        ret.mi = b.mi;
        for (int i = 0; i < max; i++)
        {
            v = int(b.val[i] - '0') + int(a.val[i] - '0') + c;
            c = 0;
            if (v >= 10)
            {
                c = 1;
                v -= 10;
            }
            ret.val += to_string(v);
        }
    }
    if (!b.mi && a.mi) ret = b - (-a);
    if (b.mi && !a.mi) ret = a - (-b);
    return ret;
}

リファクタリング後

newint newint::operator+(newint a)
{                   // b + a (自身をbとする)
    newint ret;     // 返り値
    newint b(this); // 自身
    int carry = 0;  // キャリー
    (b.value.size() < a.value.size())
        ? (b.keta(a.value.size()))
        : (a.keta(b.value.size())); // 3項演算子、ゼロ埋めで桁数をa,b両方同じにする
    if ((b.minus) == (a.minus))
    {                        // 同符号での演算なら
        ret.minus = b.minus; // 符号は変化しない
        for (int i = 0; i < (a.value.size()); i++)
        {                                                                    //小さい桁から足していく
            int tmp = int(b.value[i] - '0') + int(a.value[i] - '0') + carry; // 単純に足した値
            carry = tmp / 10;                                                // 多い分をキャリーに
            tmp = tmp % 10;                                                  // キャリーの分をなくす
            ret.value += to_string(tmp);                                     // 結果
        }
        ret.value += to_string(carry); // 最上位桁へのキャリーを追加
    }
    else
    { // 異符号での演算なら
        (a.minus)
            ? (ret = b - -a)
            : (ret = a - -b);
        // 3項演算子、b+aを符号により b-(-a)(a<0のとき) もしくは a-(-b)(b<0のとき) に変更する
        // つまり、同符号での減算に変える
    }
    return ret;
}

前の5~8行目のように両方keta(max)で合わせることはせず、3項演算子で桁数が小さい方だけ桁合わせをするようにしました。
あと、最上位桁へのキャリー用に1桁多く桁合わせしていたのを、最上位桁へのキャリーは計算の最後で追加するようにしました。
9行目の同符号の判定は(b.minus) == (a.minus)でできます。
for文においてcはcarry、vはtmpになってます。
キャリーとはその桁が10を超えたときに次の桁へ繰り越される値のことです。
ここで言っておきますが、18/10=1.8にはなりません。int型なので18/10=1となります。
tmpはその桁の結果、つまりキャリーを引いた分の値となります。
for文後に最上位桁へのキャリーを追加します。最上位桁はvalueの末尾です。
同符号をif文で判定したなら、else部は異符号の場合になるはずです。
そのelse部で3項演算子によって場合分けして式を変形しています。

-

リファクタリング前

newint newint::operator-(newint a)
{
    newint ret, b(this);
    int c = 0, v, max;
    (b.val.size() < a.val.size()) ? (max = a.val.size()) : (max = b.val.size());
    max++;
    a.keta(max);
    b.keta(max);
    if (!b.mi && a.mi || b.mi && !a.mi)  ret = b + -a;
    if (!b.mi && !a.mi)
    {
        if (a < b)
        {
            ret.mi = false;
            for (int i = 0; i < max; i++)
            {
                v = int(b.val[i] - '0') - int(a.val[i] - '0') - c;
                c = 0;
                if (v < 0)
                {
                    c = 1;
                    v += 10;
                }
                ret.val += to_string(v);
            }
        }
        else if (a.str() == b.str()) ret.set("0");
        else
        {
            ret = a - b;
            ret.mi = true;
        }
    }
    if (mi && a.mi) ret = -a - (-b);
    return ret;
}

リファクタリング後

newint newint::operator-(newint a)
{                   // b - a (自身をbとする)
    newint ret;     // 返り値
    newint b(this); // 自身
    int carry = 0;  // キャリー
    (b.value.size() < a.value.size())
        ? (b.keta(a.value.size()))
        : (a.keta(b.value.size())); // 3項演算子、ゼロ埋めで桁数をa,b両方同じにする
    if (!(b.minus) && !(a.minus))
    { // 正同士での演算なら
        if (a < b)
        { // 大きい方から小さい方を引くとき(符号は+のままになる)
            for (int i = 0; i < a.value.size(); i++)
            {                                                                    // 小さい桁から計算する
                int tmp = int(b.value[i] - '0') - int(a.value[i] - '0') - carry; // 単純に引いた値
                if (tmp < 0)
                {              // tmpが負なら
                    carry = 1; // キャリーを追加し
                    tmp += 10; // 正に直す
                }
                else
                    carry = 0; // 負でなければキャリーを0に戻す
                ret.value += to_string(tmp);
            }
        }
        else if (a == b)
            ret.set("0"); // a==bなら結果は0
        else
            ret = -(a - b); // b>aのときは b-a = -(a-b) として計算
    }
    else if((b.minus) && (a.minus))
    { // 負同士での演算なら
        ret = -(-b - -a); // 正同士での演算に変換
    }
    else
    {                 //異符号なら
        ret = b + -a; //同符号での加法に変換
    }
    return ret;
}

桁合わせに関しては加法と同じ改良をしました
順番は正同士→負同士→異符号としました、処理内容は大して変化してないです。

*

リファクタリング前

newint newint::operator*(newint a)
{
    set(str());
    a.set(a.str());
    newint ret, b(this);
    int bs = b.val.size(), as = a.val.size(), v = 0;
    int n[as + bs];
    for (int i = 0; i < as + bs; i++) n[i] = 0;
    for (int i = 0; i < as; i++)
    {
        for (int j = 0; j < bs; j++)
        {
            v = int(a.val[i] - '0') * int(b.val[j] - '0');
            n[i + j] += v;
        }
    }
    int c = 0, ni = 0;
    for (int i = 0; i < as + bs; i++)
    {
        n[i] += c;
        ni = n[i];
        n[i] = (n[i]) % 10;
        c = ni / 10;
    }
    for (int i = 0; i < as + bs; i++) ret.val += to_string(n[i]);
    if (!a.mi && !b.mi || a.mi && b.mi) ret.mi = false;
    if (!a.mi && b.mi || a.mi && !b.mi) ret.mi = true;
    return ret;
}

リファクタリング後

newint newint::operator*(newint a)
{                              // b*a (自身をbとする)
    this->set(this->str());    // 無駄な0埋めを消す(計算量削減)
    a.set(a.str());            // 同じく
    newint ret = new newint(); // 返り値
    newint b(this);            // 自身
    for (int i = 0; i < a.value.size(); i++)
    {                              // i:小さい桁から(aの桁数まで)
        newint sum = new newint(); // b*a[i]
        int carry = 0;             // キャリー
        for (int j = 0; j < b.value.size(); j++)
        {                                                              // j:小さい桁から(bの桁数まで)
            int tmp = (b.value[j] - '0') * (a.value[i] - '0') + carry; // その桁のかけた値(キャリーあり)
            carry = tmp / 10;                                          // 多い分をキャリーに
            tmp = tmp % 10;                                            // キャリーの分を消す
            sum.value += to_string(tmp);
        }
        sum.value += to_string(carry); // 最上位桁へのキャリー
        for (int j = 0; j < i; j++)
            sum.set(sum.str() + "0"); // 足すためにb*a[i]の桁を直す
        ret = ret + sum;              // 結果にb*a[i]を足す
    }
    ret.minus = (a.minus) ^ (b.minus); // 同じならFalse,違うならTrue → xor
    return ret;
}

asってなんですかbsってなんですか何を表してるか分かりずらいのよ(((((
ちなみにリファクタリング前のものですがそもそも動かないと思います(記事に書く自分のプログラムの動作確認ぐらいしろ)
配列の宣言には定数しか使えないからです。
なのでリファクタリング前と比べてとかいうことはせずにリファクタリング後だけ説明します。
計算方法は筆算と同じです。
b*aにおいて、aから1桁ずつ取ってbと掛け、その総和を結果とします。
※総和と言ってもただ足すのではなく、2桁目は10倍、3桁目は100倍…というように足します。
各桁での計算をするとき(b*a[x]+carryとする)、a[x]は必ず1桁で、bから1桁ずつ取って計算します。ここの計算は加法と似てます。
キャリーについて、9*9+9=90になるので何があってもキャリーが9を超えることはないです。
(もっと言うとキャリーが8を超えることはないです。最下位桁のキャリーは必ず0であり、9*9+0=81で9*9+8=89なので。)
結果の符号について、これはxorで判定できます。xorとは、真(true)と偽(false)の組み合わせのときのみ真(両方同じときは偽)になる論理演算のことです。異符号のときは負が真となるので、ちょうど乗法の符号判定にピッタリです。

/

リファクタリング前

newint newint::operator/(newint a)
{
    set(str());
    a.set(a.str());
    newint ret, b(this);
    if (!a.mi && !b.mi || a.mi && b.mi) ret.mi = false;
    if (!a.mi && b.mi || a.mi && !b.mi) ret.mi = true;
    b.mi=false;
    a.mi=false;
    int bs = b.val.size(), as = a.val.size();
    auto f = [](newint a, newint x) { return a * x; };
    newint ati[11];
    for (int i = 0; i < 11; i++) ati[i] = f(a, new newint(to_string(i)));
    if (as > bs)
    {
        ret.set("0");
        return ret;
    }
    for (int i = 0; i <= bs - as; i++)
    {
        string s = b.str();
        while (s.size() <= str(true).size()) s = "0" + s;
        newint r(s.substr(i, as + 1));
        for (int j = 1; j < 11; j++)
        {
            if (r < ati[j])
            {
                ret.val += to_string(j - 1);
                newint m;
                m = ati[j - 1];
                for (int p = 0; p < (bs - as - i); p++) m = m * new newint("10");
                b = b - m;
                break;
            }
        }
    }
    reverse(ret.val.begin(), ret.val.end());
    return ret;
}

リファクタリング後

newint newint::operator/(newint a)
{                              // b/a
    this->set(this->str());    // 無駄な0埋めを消す(計算量削減)
    a.set(a.str());            // 同じく
    newint ret = new newint(); // 返り値
    newint b(this);            // 自身
    newint rem;                // 余り
    bool a_minus = a.minus;    // a.minusを保存しておく
    a.minus = false;
    for (int i = b.value.length() - 1; i >= 0; i--)
    {
        newint sum;                  // aを足した和
        rem.value = "0" + rem.value; //余りを10倍
        for (int j = 0; j < 10; j++)
        {
            newint b_i(to_string(b.value[i] - '0')); // b[i]
            sum = sum + a;
            if ((rem + b_i) < sum)
            {                                         // bを足していってその桁の数(rem+b_i)を超えたら
                ret.value = to_string(j) + ret.value; // 繰り返し回数-1であるjを足す
                rem = (rem + b_i) - (sum - a);        // 余りとして(rem*b[i])%aに等価な値を設定
                break;
            }
        }
    }
    a.minus = a_minus;
    ret.minus = (a.minus) ^ (b.minus); // 同じならFalse,違うならTrue → xor
    return ret;
}

除法が一番ムズイです。
とりあえず、b/aのaが負だと困るので符号を保存して一時的に正にしてます。結果の符号を判定するときに保存しておいた符号の情報が必要になるので取っておきます。
例えば320/10は以下のように計算します。

結果=""
余り=0
0+3<10である、結果+="0" 
※0+3は余り+割られる数の3桁目、10は割る数*j ( j=1)、0はj-1
余り=( (0+3)-(10-10) )*10 = 30
  ※ 余り=( (余り+今の桁)-(上で割る数に掛けた値-割る数) )*10
30+2<10ではない
30+2<20ではない ※割る数*2、プログラムでは前の数+=割る数となる
30+2<30ではない
30+2<40である、結果+="3"
余り=( (30+2)-(40-10) )*10 = 20
20+0<10ではない
20+0<20ではない
20+0<30である、結果+="2"
結果は"032"

分からんって人もいると思います。
説明難しいのでコード見て理解してください(((((((

結果の符号の判定は乗法と同じ方法です。

%

リファクタリング前

newint newint::operator%(newint a)
{
    newint b(this), v, ret;
    v = b / a;
    v = v * a;
    ret = b - v;
    return ret;
} 

リファクタリング後

newint newint::operator%(newint a)
{ // b/a
    // やってることは割り算と同じ、最後の余りだけを取り出す
    this->set(this->str());    // 無駄な0埋めを消す(計算量削減)
    a.set(a.str());            // 同じく
    newint ret = new newint(); // 返り値
    newint b(this);            // 自身
    newint rem;                // 余り
    bool a_minus = a.minus;    // a.minusを保存しておく
    a.minus = false;
    for (int i = b.value.length() - 1; i >= 0; i--)
    {
        newint sum;                  // aを足した和
        rem.value = "0" + rem.value; //余りを10倍
        for (int j = 0; j < 10; j++)
        {
            newint b_i(to_string(b.value[i] - '0')); // b[i]
            sum = sum + a;
            if ((rem + b_i) < sum)
            {                                         // bを足していってその桁の数(rem+b_i)を超えたら
                ret.value = to_string(j) + ret.value; // 繰り返し回数-1であるjを足す
                rem = (rem + b_i) - (sum - a);        // 余りとして(rem*b[i])%aに等価な値を設定
                break;
            }
        }
    }
    a.minus = a_minus;
    if (rem != (new newint("0")))
        rem.minus = (a.minus) ^ (b.minus); // 同じならFalse,違うならTrue → xor
    return rem;
}

リファクタリング前より無駄な処理をなくすため、除法と同じことをして最後に残った余りを取りだすという方法にしました。

bin関数

リファクタリング前

    string bin()
    {
        string v;
        newint b(str(true)), a("0"), w("2"), c;
        while (b > a)
        {
            c = b % w;
            v += c.str();
            b = b / w;
        }
        reverse(v.begin(), v.end());
        if (mi)  return "-" + v;
        return v;
    }

リファクタリング後

    /**
     * @brief インスタンスが表す値を2進数に変換して返します。
     * 値が負数の時は"-"をつけて返します。
     * @return string
     */
    string bin()
    {
        string ret;                     // 返り値
        newint copied(this->str(true)); // 自身のコピー
        newint kisu("2");               // 基数
        while (copied.str() != "0")
        {                                 // 0になるまで、2進数の筆算と同じ方法
            ret += (copied % kisu).str(); // 基数で割った余りを追加
            copied = copied / kisu;       // 基数で割る
        }
        reverse(ret.begin(), ret.end()); // 2進数の筆算は逆順に読む
        if (minus)
            return "-" + ret;
        return ret;
    }

ほぼ同じです。

oct関数は基数を変えるだけです。

hex関数

リファクタリング前

    string hex()
    {
        string v;
        newint b(str(true)), a("0"), w("16"), c;
        while (b > a)
        {
            c = b % w;
            switch (stoi(c.str()))
            {
            case 10:
                c.val = "A";
                break;
            case 11:
                c.val = "B";
                break;
            case 12:
                c.val = "C";
                break;
            case 13:
                c.val = "D";
                break;
            case 14:
                c.val = "E";
                break;
            case 15:
                c.val = "F";
                break;
            default:
                break;
            }
            v += c.str();
            b = b / w;
        }
        reverse(v.begin(), v.end());
        if (mi) return "-" + v;
        return v;
    }

リファクタリング後

    /**
     * @brief インスタンスが表す値を16進数に変換して返します。
     * 値が負数の時は"-"をつけて返します。
     * @return string
     */
    string hex()
    {
        string ret;                     // 返り値
        newint copied(this->str(true)); // 自身のコピー(絶対値)
        newint kisu("16");              // 基数
        while (copied.str() != "0")
        { // 0になるまで、16進数の筆算と同じ方法
            string amari = (copied % kisu).str();
            if (amari.length() == 1)         // 余りが1桁(つまり9以下)なら
                ret += amari;                // そのまま追加
            else                             // それ以外(つまり10以上)なら
                ret += 'A' + amari[1] - '0'; // 文字コードを利用しA~Fに変換
            copied = copied / kisu;          // 基数で割る
        }
        reverse(ret.begin(), ret.end()); // 筆算の結果は逆に読む
        if (minus)
            return "-" + ret;
        return ret;
    }

基本は同じですが、10~15をA~Fに変換するのをswitch文ではなく文字コードを使います。
文字コードは'A'~'Z'でアルファベット順に並んでるので、10以上の場合1桁目の部分を'A'に足せば変換できます。

++(前置)

リファクタリング前

newint newint::operator++()
{
    newint b(this);
    b = b + new newint("1");
    set(b.str());
    return *this;
}

リファクタリング後

newint newint::operator++()
{ // 単項演算子の++ (++x)
    newint b(this);
    if (b.minus)
    { // 絶対値で演算を行うため
        b.minus=false; // 符号が負のままだと無限にループする
        --b;
        this->set((-b).str());
        return *this;
    }
    b.value += "0"; // 桁数が1つ大きくなる可能性があるため、桁設定
    for (int i = 0; i < b.value.size(); i++)
    {
        if (b.value[i] != '9')
        { // 9が続いたらそれまでの桁の数は0になる
            b.value[i] += 1;
            b.value = string(i, '0') + b.value.substr(i);
            break;
        }
    }
    this->set(b.str());
    return *this;
}

せっかくなので計算量が(多分)少なくなるようにしました。
小さい桁から見て最初の9でない桁を+1するだけです。それ以下の桁はすべて0になります。(例:107+1=108、2199+1=2200)
これは正の数でのみ言えることなので、負の数は絶対値をデクリメントしたものに符号を付けることとします。
前置なので、インクリメント後の値を返します。

--(前置)

リファクタリング前

newint newint::operator--()
{
    newint b(this);
    b = b - new newint("1");
    set(b.str());
    return *this;
}

リファクタリング後

newint newint::operator--()
{ // 単項演算子の-- (--x)
    newint b(this);
    if (b.str() == "0")
    {
        b.set("-1"); // ハグるため
        this->set(b.str());
        return *this;
    }
    if (b.minus)
    { // 絶対値で演算を行うため
        b.minus=false; // 符号が負のままだと無限にループする
        ++b;
        this->set((-b).str());
        return *this;
    }
    for (int i = 0; i < b.value.size(); i++)
    {
        if (b.value[i] != '0')
        { // 9が続いたらそれまでの桁の数は0になる
            b.value[i] -= 1;
            b.value = string(i, '9') + b.value.substr(i);
            this->set(b.str());
            return *this;
        }
    }
    this->set("0"); // 処理が完了しないー>0
    return *this;
}

インクリメントと同じ考えです。今度は引くので0以外の値を引きます。それ以下は9になります。(例:302-1=301、200-1=199)

これはリファクタリングではなく新機能ですが、後置のインクリメント・デクリメント演算子も追加しました。変更は元々の値を保存してから計算して、保存しておいた方の値を渡すだけです。
あと[]も追加しました、これはコード見せた方が早いと思うので以下に貼ります。

int newint::operator[](int n)
{ // a[n]、値の絶対値におけるn番目の数値を返す
    return (this->str(true))[n] - '0';
}

以上で、リファクタリングの説明が全て終わりました。

コメントと変数名は大事だね()

この記事が気に入ったらサポートをしてみませんか?