這次做的案子金額數字需要沒有上限,這個就不能只用一般常用的數字結構(int, float...),因為這些值的儲存範圍是有上限的,破上限或下限都會產生error,這時候就會需要C#的BigInteger的幫助

BigInteger內置在NET4.0的System.Numerics裡,他就是為了處理大數字誕生的,不過因為BigInteger是對整數做處理,我這個案子會需要有小數的運算,而金錢的小數處理上,因為float和double的精度問題會產生金額上的錯誤,不適宜做金錢的運算,decimal就是解套的方案

由於float和double都是二進制的方式儲存,因此會有精度的問題,因此就需要十進制的decimal來解決,而總括上述問題,我需要一個可以存大數的BigDecimal,在網路上我找到了兩個不錯的BigDecimal實作

我是拿第一個來修改研究,他的方式是以C#給的BigInteger作為無限大數的部分+int來記錄小數點的位數,不過畢竟小數處理一定有除不盡的時候,所以小數位數是有一定上限的,在第一個實作裡是小數位數上限是100,其實也夠多了,而各類別的轉型也都有完全的實作出來,正是我所需要的,第二個的部分我沒拿來用過也沒測過,但第二個的可讀性相對比第一個更容易閱讀更好理解,也可以嘗試去做做看,這個地方我還是以第一個來做

有了BigDecimal後,還要在UI上塞進大數,但這時候不是數字太擠,就是會突破外框,所以我們需要單位的存在,所以就來實作Custom的BigDecimal

這裡用了DesignPattern的外觀模式(Facade Pattern)建立了屬於自己的BigDecimal來實作單位的呈現,而關於外觀模式詳細的部分後續有時間再補上,外觀模式下能保有那個類別的功能外,還可以外加自己需要的功能

這裡建立了一個CustomBigDecimal.cs 

裡面放了BigDecimal的值和單位的類別UnitData,UnitData有兩個值,Digits : 位數 Unit : 單位

實作一些值的建構子和Pow次方函式

[Serializable]
public class CustomBigDecimal
{
    public BigDecimal m_Value;

    public CustomBigDecimal()
    {
        m_Value = new BigDecimal(0);
    }

    public CustomBigDecimal(int value)
    {
        m_Value = value;
    }

    public CustomBigDecimal(BigDecimal value)
    {
        m_Value = value;
    }

    public CustomBigDecimal(string value)
    {
        m_Value = new BigDecimal(value);
    }
 
    public class UnitData
    {
        public BigDecimal Digits { get; set; }
        public string Unit { get; set; } = "";

        public UnitData(int digit, string unit)
        {
            Digits = Pow(10, digit).m_Value;
            Unit = unit;
        }
    }
    public static CustomBigDecimal Pow(CustomBigDecimal a, int b)
    {
        BigDecimal v = 1;
        for (int i = 0; i < b; i++)
        {
            v *= a.m_Value;
        }
    
        return new CustomBigDecimal(v);
    }
     
    public static CustomBigDecimal Pow(int a, int b)
    {
        BigDecimal v = 1;
        for (var i = 0; i < b; i++)
        {
            v *= a;
        }
    
        return new CustomBigDecimal(v);
    }
    
    public static CustomBigDecimal Pow(ulong a, ulong b)
    {
        BigDecimal v = 1;
        for (ulong i = 0; i < b; i++)
        {
            v *= a;
        }
    
        return new CustomBigDecimal(v);
    }

 

這裡為了舉例,直接把多國語系設定寫在程式裡,後面為了方便修改也可以拉到ScriptableObject去做設定

//英文
private static readonly UnitData[] UnitListEng = new UnitData[]
{
    new UnitData( 3, "K"),
    new UnitData( 6, "M"),
    new UnitData( 9, "B"),
    new UnitData(12, "T"),
    new UnitData(15, "Q"),
    new UnitData(18, "S"),
    new UnitData(21, "SS"),
    new UnitData(24, "SSS"),
    new UnitData(27, "O"),
    new UnitData(30, "N"),
    new UnitData(33, "D"),
    new UnitData(36, "U"),
    new UnitData(39, "DD"),
    new UnitData(42, "C"),
};
// 繁体
private static readonly UnitData[] UnitListTraditional = new UnitData[]
{
    new UnitData( 4, "萬"),
    new UnitData( 8, "億"),
    new UnitData(12, "兆"),
    new UnitData(16, "京"),
    new UnitData(20, "垓"),
    new UnitData(24, "秭"),
    new UnitData(28, "穰"),
    new UnitData(32, "溝"),
    new UnitData(36, "澗"),
    new UnitData(40, "正"),
    new UnitData(44, "載"),
    new UnitData(48, "無限大"),
};
// 簡体
private static readonly UnitData[] UnitListSimplified = new UnitData[]
{
    new UnitData( 4, "万"),
    new UnitData( 8, "亿"),
    new UnitData(12, "兆"),
    new UnitData(16, "京"),
    new UnitData(20, "垓"),
    new UnitData(24, "秭"),
    new UnitData(28, "穰"),
    new UnitData(32, "沟"),
    new UnitData(36, "涧"),
    new UnitData(40, "正"),
    new UnitData(44, "载"),
    new UnitData(48, "无限大"),
};

private static UnitData[] GetUnitList()
{
    switch (Application.systemLanguage)
    {
        case SystemLanguage.Japanese:
        return UnitList;

        case SystemLanguage.Chinese:
        case SystemLanguage.ChineseSimplified:
        return UnitListSimplified;

        case SystemLanguage.ChineseTraditional:
        return UnitListTraditional;
    }
    return UnitListTraditional;
}

 

再來就是重點了,單位化

這裡首先要先對BigDecimal.cs添加一個保留位數的ToStringByAfterDecimal的函式

digits參數可帶入顯示的小數點位數

public string ToStringByAfterDecimal(int digits)
{
    var s = BigInteger.Abs(_bigIntValue).ToString("R");

    if (_decimalCount > 0)
    {
        s = s.PadLeft(_decimalCount + 1, '0');
        int decimalIndex = s.Length - _decimalCount;
        s = s.Insert(decimalIndex, DecimalSeparator);
        if(s.Length > decimalIndex + 1 + digits)
        {
            s = s.Remove(decimalIndex + 1 + digits);
        }
    }

    if (_bigIntValue.Sign < 0)
        s = "-" + s;

    return s;
}

然後回到CustomBigDecimal.cs,加入ToUnit函式做單位的轉換,這裡使用到System.Linq,,然後用剛剛BigDecimal的ToStringByAfterDecimal顯示小數點位數,這裡我給定小數點後2位

突破了所設定的最大位數,會直接使用最後一個位數的名稱,這個部分可以依自己需求設定

public string ToUnit()
{
    var minus = m_Value < 0 ? "-" : "";
    var abs = m_Value < 0 ? -m_Value : m_Value;
    var data = GetUnitList().Where(unit => abs >= unit.Digits).LastOrDefault();
    if (data == null) return $"{m_Value.ToStringByAfterDecimal(2)}";
    var unitName = data.Unit;
    if (data == GetUnitList().Last()) return $"{minus}{unitName}";
    var positiveNum = abs / data.Digits;
    return $"{positiveNum.ToStringByAfterDecimal(2)}{unitName}";
}

剩下的就是實作CustomBigDecimal的加減乘除,大小比較,大小等於判斷式,各值類別轉換的函式

public static CustomBigDecimal operator +(CustomBigDecimal leftSide, float rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value + rightSide);
}
public static CustomBigDecimal operator +(CustomBigDecimal leftSide, int rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value + rightSide);
}
public static CustomBigDecimal operator +(CustomBigDecimal leftSide, CustomBigDecimal rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value + rightSide.m_Value);
}

public static CustomBigDecimal operator -(CustomBigDecimal v)
{
    return new CustomBigDecimal(-v.m_Value);
}
public static CustomBigDecimal operator -(CustomBigDecimal leftSide, float rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value - rightSide);
}
public static CustomBigDecimal operator -(CustomBigDecimal leftSide, int rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value - rightSide);
}
public static CustomBigDecimal operator -(CustomBigDecimal leftSide, CustomBigDecimal rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value - rightSide.m_Value);
}

public static CustomBigDecimal operator *(CustomBigDecimal leftSide, float rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value * rightSide);
}
public static CustomBigDecimal operator *(CustomBigDecimal leftSide, CustomBigDecimal rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value * rightSide.m_Value);
}

public static CustomBigDecimal operator /(CustomBigDecimal leftSide, CustomBigDecimal rightSide)
{
    return new CustomBigDecimal(leftSide.m_Value / rightSide.m_Value);
}

public static bool operator >(CustomBigDecimal leftSide, float rightSide)
{
    return leftSide.m_Value > rightSide;
}
public static bool operator >(CustomBigDecimal leftSide, CustomBigDecimal rightSide)
{
    return leftSide.m_Value > rightSide.m_Value;
}
public static bool operator >=(CustomBigDecimal leftSide, float rightSide)
{
    return leftSide.m_Value >= (int)rightSide;
}
public static bool operator >=(CustomBigDecimal leftSide, CustomBigDecimal rightSide)
{
    return leftSide.m_Value >= rightSide.m_Value;
}

public static bool operator <(CustomBigDecimal leftSide, float rightSide)
{
    return leftSide.m_Value < (int)rightSide;
}
public static bool operator <(CustomBigDecimal leftSide, CustomBigDecimal rightSide)
{
    return leftSide.m_Value < rightSide.m_Value;
}
public static bool operator <=(CustomBigDecimal leftSide, float rightSide)
{
    return leftSide.m_Value <= (int)rightSide;
}
public static bool operator <=(CustomBigDecimal leftSide, CustomBigDecimal rightSide)
{
    return leftSide.m_Value <= rightSide.m_Value;
}


public static CustomBigDecimal Max(CustomBigDecimal a, CustomBigDecimal b)
{
    if (a >= b) return a;
    return b;
}

public static CustomBigDecimal Min(CustomBigDecimal a, CustomBigDecimal b)
{
    if (a <= b) return a;
    return b;
}
 
public static implicit operator CustomBigDecimal(sbyte value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(ushort value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(uint value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(ulong value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(byte value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(short value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(int value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(long value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(BigDecimal value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(float value)
{
    return new CustomBigDecimal((double)value);
}

public static implicit operator CustomBigDecimal(double value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(decimal value)
{
    return new CustomBigDecimal(value);
}

public static implicit operator CustomBigDecimal(string value)
{
    return new CustomBigDecimal(BigDecimal.Parse(value));
} 

public static explicit operator SByte(CustomBigDecimal value)
{
    return (SByte)value.m_Value;
}

public static explicit operator UInt16(CustomBigDecimal value)
{
    return (UInt16)value.m_Value;
}

public static explicit operator UInt32(CustomBigDecimal value)
{
    return (UInt32)value.m_Value;
}

public static explicit operator UInt64(CustomBigDecimal value)
{
    return (UInt64)value.m_Value;
}

public static explicit operator Byte(CustomBigDecimal value)
{
    return (Byte)value.m_Value;
}

public static explicit operator Int16(CustomBigDecimal value)
{
    return (Int16)value.m_Value;
}

public static explicit operator Int32(CustomBigDecimal value)
{
    return (Int32)value.m_Value;
}

public static explicit operator Int64(CustomBigDecimal value)
{
    return (Int64)value.m_Value;
}

public static explicit operator BigDecimal(CustomBigDecimal value)
{
    return (BigDecimal)value.m_Value;
}

public static explicit operator float(CustomBigDecimal value)
{
    return (float)value.m_Value;
}

public static explicit operator double(CustomBigDecimal value)
{
    return (double)value.m_Value;
}

public static explicit operator decimal(CustomBigDecimal value)
{
    return (decimal)value.m_Value;
}

做一個簡單的測試吧,這裡的數字尾巴的m是decimal的尾碼,表示decimal自動轉型成CustomBigDecimal

public class TryTry : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        CustomBigDecimal a1 = 620332349830954803494.43130m;
        CustomBigDecimal a2 = 334242444244232332323.44420m;
        Debug.Log((a1 * a2).ToUnit());
    }

}

最後呈現,單位“正”為40位數的大數,所以20正 = 41位數,然後只取小數點後方2位數

截圖 2022-10-06 上午10.33.10

從999k變到1M

螢幕錄製_2022-10-06_下午6_43_48_AdobeExpress

 

arrow
arrow

    Kouhei 發表在 痞客邦 留言(0) 人氣()