うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【C#】ulongを超える値を扱う方法

C# で扱える最大値は ulong の「18,446,744,073,709,551,615」(1,844京)ですが
基本的にはそんな値いかないと思うので int で実装することが多い
※理想は int よりも uint だが uint を使うと int を使うとキャストする必要があるため面倒

int の場合は最大値が「2,147,483,647」(21億)となり
初期のゲームでは小さい値だったがインフレして 21億超えるなんて簡単に起こり得る

また最近遊んでたペンギンの島では 1000毎にアルファベットのAから単位をつけて、Zまで行くとAAからまた加算されている

今回はこの手法を参考に作成してみる

クラス設計

f:id:hacchi_man:20200319134019p:plain:h300

入れ込む値を4桁区切りにして、Listに格納していく 例えば 123456789 の場合

var list = new List<int>
{
    1,
    2345,
    6789
}

と格納しておく

加算

下の桁から順に加算していき、上振れたした分を次に繰り越す

f:id:hacchi_man:20200401231653p:plain

減算

下の桁から順に減算していき、結果がマイナスになった場合は一つ上の桁から 10000を借りてくる

f:id:hacchi_man:20200401231515p:plain

コード

上記を踏まえたコードが下記になる

using System;
using System.Collections.Generic;
using UnityEngine;
 
[Serializable]
public class MultiDigit
{
    [SerializeField]
    private List<ushort> _values;
 
    public int this[int digit] => _values[digit];
    /// <summary>
    /// 桁数
    /// </summary>
    public int Digit => _values.Count;
    /// <summary>
    /// 最大桁の値
    /// </summary>
    public int MaxDigitValue => Digit <= 0 ? 0 : _values[Digit - 1];
  
    public MultiDigit()
    {
        _values = new List<ushort> {0};
    }
 
    public MultiDigit(List<ushort> values)
    {
        _values = new List<ushort>(values);
        _values.Reverse();
    }
    
    public MultiDigit(ushort value, int digit = 0)
    {
        _values = new List<ushort>(digit + 1);
        for (int i = 0; i < digit + 1; i++)
            _values.Add(0);

        _values[digit] = (ushort) Mathf.Clamp(value, 0, 9999);
    }
 
    public static MultiDigit operator +(MultiDigit a, MultiDigit b)
    {
        var n = new MultiDigit
        {
            _values = new List<ushort>(a._values)
        };
        if (n.Digit < b.Digit)
            for (int i = n.Digit; i < b.Digit; i++)
                n._values.Add(0);

        ushort over = 0;
        for (var i = 0; i < b._values.Count; i++)
        {
            n._values[i] += (ushort)(b._values[i] + over);
            if (n._values[i] >= 10000)
            {
                over = (ushort) (n._values[i] / 10000);
                n._values[i] %= 10000;
            }
            else
            {
                over = 0;
            }
        }
        
        while (over > 0)
        {
            var temp = (ushort)(over % 10000);
            n._values.Add(temp);
            over /= 10000;
        }
        
        return n;
    }
    
    public static MultiDigit operator -(MultiDigit a, MultiDigit b)
    {
        var n = new MultiDigit
        {
            _values = new List<ushort>(a._values)
        };
        if (n.Digit < b.Digit || (n.MaxDigitValue < b.MaxDigitValue && n.Digit == b.Digit))
        {
            Debug.LogError("負の値にはできません");
            return n;
        }
 
        short over = 0;
        ushort bVal = 0;
        for (var i = 0; i < n.Digit; i++)
        {
            bVal = (ushort) (i < b.Digit ? b._values[i] : 0);
            over = (short) (n._values[i] - bVal + over);
            if (over > 0)
            {
                n._values[i] = (ushort) over;
                over = 0;
            }
            else if (over < 0)
            {
                n._values[i] = (ushort) (10000 + over);
                over = -1;
            }
            else
            {
                n._values[i] = 0;
            }
        }
        
        // 先頭0は消す
        for (var i = n.Digit - 1; i >= 0; i--)
            if (n[i] <= 0)
                n._values.RemoveAt(i);
 
        return n;
    }
    
    public static bool operator ==(MultiDigit a, MultiDigit b)
    {
        if (a._values.Count != b._values.Count)
            return false;
 
        var count = a._values.Count;
        for (var i = 0; i < count; i++)
        {
            if (a._values[i] != b._values[i])
                return false;
        }
        return true;
    }
 
    public static bool operator !=(MultiDigit a, MultiDigit b)
    {
        return !(a == b);
    }
    
    public override bool Equals(object o)
    {
        if(o == null)
            return false;
 
        var a = o as MultiDigit;
        
        if (a._values.Count != _values.Count)
            return false;
 
        var count = a._values.Count;
        for (var i = 0; i < count; i++)
        {
            if (a._values[i] != _values[i])
                return false;
        }
 
        return true;
    }
  
    public override int GetHashCode()
    {
        return _values[0] ^ _values.Count;
    }
}

サンプル

ulong の最大値に加算できているので、ulong の最大値を超えていても問題無く動作するようにできた

f:id:hacchi_man:20200331230022p:plain