c# - 如何检查表示数字的对象是否大于另一个?

标签 c# object math

我目前正在编写一个可以表示无限大数(理论上)的类。此类的构造函数从字符串值创建对象,这就是为什么数字可能非常大但大小未知的原因。

我开始编写此类的原因是因为我希望能够编写一个程序,该程序能够对任意大的数字进行数学计算。因此,我开始编写一个类,该类可以很好地处理整数、 float 、 double 、(希望如此)小数等标准范围内的值。

以下是该类的声明和主要构造函数:

/// <summary>
/// Creates a new instance of the LargeDecimal class, which represents either a whole or decimal number.
/// </summary>
/// <param name="number">The string representation of the number.</param>
public LargeDecimal(string value)
{
    string number = value.Replace(" ", "");
    if (number.Contains("-") && (number.IndexOf('-') == 0)) {
        number = number.Replace("-", "");
        IsNegative = true;
    }
    // Determining whether the number is whole or contains a decimal.
    if (number.IndexOf('.') == -1) {
        // Does not contain a decimal.
        for (int i = 0; i < number.Length; i++)
            wholeDigits.Add(int.Parse(number[i].ToString()));
        IsWhole = true;
    }
    else {
        // Still check if number is whole. Add all decimal digits.
        string[] numArray = number.Split('.');
        int sumOfDecimalDigits = 0;
        for (int i = 0; i < numArray[1].ToString().Length; i++)
            sumOfDecimalDigits += int.Parse(numArray[1].ToString()[i].ToString());
        if (sumOfDecimalDigits <= 0) {
            // Is a whole number.
            for (int i = 0; i < numArray[0].ToString().Length; i++)
                wholeDigits.Add(int.Parse(numArray[0].ToString()[i].ToString()));
            IsWhole = true;
        }
        else {
            // Is not a whole number.
            for (int i = 0; i < numArray[0].ToString().Length; i++)
                wholeDigits.Add(int.Parse(numArray[0].ToString()[i].ToString()));
            for (int i = 0; i < numArray[1].ToString().Length; i++)
                decimalDigits.Add(int.Parse(numArray[1].ToString()[i].ToString()));
            IsWhole = false;
        }
    }
}

该类基本上是通过两个 int 类型的列表来表示一个数字,其中一个列表表示构成数字的整个分区的数字,另一个列表表示构成数字的小数部分的数字编号(如果适用)。

我编写了一个 Add 方法,它接受两个 LargeDecimal 对象,将它们的值相加,然后返回一个新的 LargeDecimal 对象,其总和作为其值。虽然不完整,但它确实适用于仅为整数且均为正数或均为负数的 LargeDecimal 对象(图片!)。

我意识到添加比较两个值(大于/小于/等于)的方法在计算中非常有用。但是,我不确定如何检查 LargeDecimal 对象的值是大于还是小于另一个 LargeDecimal 的值。

在某些情况下,我可以只比较 wholeDigits 列表中的项目数量,但只有当两个值的项目数量不同时才会这样。 我不确定如何比较两个数字,例如:15498765423654973246 和 15499111137583924246。

而且我认为如果我尝试比较两个小数会变得更加困难:8573819351.86931 和 8573809999.85999

我不希望将整数计算与位值结合使用(例如,在数字 831 中,数字 8 的值将是 8 * 100,3 的值将是 3 * 10,而 1 的值将是 1 * 1),因为我希望此类能够表示任何给定大小、长度和范围的值(而 int 不能处理最大 2147483647 的值)。

如有任何帮助,我们将不胜感激!谢谢大家!

最佳答案

我将从实现 IComparable 开始:

public class LargeDecimal : IComparable<LargeDecimal>

实现看起来像:

public int CompareTo(LargeDecimal other)
{
    if (other == null) return 1;
    if (ReferenceEquals(this, other)) return 0;

    if (IsNegative != other.IsNegative)
    {
        if (other.IsNegative) return 1;
        return -1;
    }

    int multiplier = (IsNegative) ? -1 : 1;

    if (wholeDigits.Count > other.wholeDigits.Count) return 1 * multiplier;
    if (wholeDigits.Count < other.wholeDigits.Count) return -1 * multiplier;

    for (int i = 0; i < wholeDigits.Count; i++)
    {
        if (wholeDigits[i] > other.wholeDigits[i]) return 1 * multiplier;
        if (wholeDigits[i] < other.wholeDigits[i]) return -1 * multiplier;
    }

    for (int i = 0; i < Math.Min(decimalDigits.Count, other.decimalDigits.Count); i++)
    {
        if (decimalDigits[i] > other.decimalDigits[i]) return 1 * multiplier;
        if (decimalDigits[i] < other.decimalDigits[i]) return -1 * multiplier;
    }

    if (decimalDigits.Count > other.decimalDigits.Count) return 1 * multiplier;
    if (decimalDigits.Count < other.decimalDigits.Count) return -1 * multiplier;

    return 0;
}

更新

今晚吃晚饭时我一直在想这个项目,所以为了好玩我又做了一些。不确定这是否有帮助,但我想我会分享我的想法。

首先,我添加了一些字段来使这个类真正起作用:

public bool IsNegative { get; private set; }
public bool IsWhole { get; private set; }

private List<int> wholeDigits;
private List<int> decimalDigits;

其次,我覆盖了 ToString 方法,因此数字可以很好地显示:

public override string ToString()
{
    return string.Format("{0}{1}{2}{3}",
        (IsNegative) ? "-" : "",
        string.Join("", wholeDigits),
        (IsWhole) ? "" : ".",
        (IsWhole) ? "" : string.Join("", decimalDigits));
}

然后我实现了 Equals 方法,因此它们可以按预期用于数字类型:

public static bool Equals(LargeDecimal first, LargeDecimal second)
{
    return ReferenceEquals(first, null) 
        ? ReferenceEquals(second, null) 
        : first.Equals(second);
}

public override bool Equals(object obj)
{
    return Equals(obj as LargeDecimal);
}

protected bool Equals(LargeDecimal other)
{
    return CompareTo(other) == 0;
}

public override int GetHashCode()
{
    unchecked
    {
        var hashCode = (wholeDigits != null)
            ? wholeDigits.GetHashCode() 
            : 0;
        hashCode = (hashCode * 397) ^ 
            (decimalDigits != null ? decimalDigits.GetHashCode() : 0);
        hashCode = (hashCode * 397) ^ IsNegative.GetHashCode();
        hashCode = (hashCode * 397) ^ IsWhole.GetHashCode();
        return hashCode;
    }
}

接下来,我添加了一些实用方法来帮助完成一些即将到来的任务:

private void ResetToZero()
{
    wholeDigits = new List<int> { 0 };
    decimalDigits = new List<int> { 0 };
    IsWhole = true;
    IsNegative = false;
}

private void NormalizeLists()
{
    RemoveLeadingZeroes(wholeDigits);
    RemoveTrailingZeroes(decimalDigits);
    IsWhole = (decimalDigits.Count == 0 
        || (decimalDigits.Count == 1 && decimalDigits[0] == 0));
}

private void AddLeadingZeroes(List<int> list, int numberOfZeroes)
{
    if (list == null) return;

    for (int i = 0; i < numberOfZeroes; i++)
    {
        list.Insert(0, 0);
    }
}

private void AddTrailingZeroes(List<int> list, int numberOfZeroes)
{
    if (list == null) return;

    for (int i = 0; i < numberOfZeroes; i++)
    {
        list.Add(0);
    }
}

private void RemoveLeadingZeroes(List<int> list, bool leaveOneIfEmpty = true)
{
    if (list == null) return;

    var temp = list;

    for (int i = 0; i < temp.Count; i++)
    {
        if (temp[i] == 0)
        {
            list.RemoveAt(i);
        }
        else
        {
            break;
        }
    }

    if (leaveOneIfEmpty && !list.Any()) list.Add(0);
}

private void RemoveTrailingZeroes(List<int> list, bool leaveOneIfEmpty = true)
{
    if (list == null) return;

    var temp = list;

    for (int i = temp.Count -1; i >= 0; i--)
    {
        if (temp[i] == 0)
        {
            list.RemoveAt(i);
        }
        else
        {
            break;
        }
    }

    if (leaveOneIfEmpty && !list.Any()) list.Add(0);
}

接下来,我添加了一些构造函数。将数字设置为“0”的默认设置,一个解析字符串,另一个从另一个 LargeDecimal 复制值:

public LargeDecimal() : this("0") { }

public LargeDecimal(string value)
{
    if (value == null) throw new ArgumentNullException("value");

    string number = value.Replace(" ", ""); // remove spaces
    number = number.TrimStart('0'); // remove leading zeroes
    IsNegative = (number.IndexOf('-') == 0); // check for negative
    number = number.Replace("-", ""); // remove dashes
    // add a zero if there were no numbers before a decimal point
    if (number.IndexOf('.') == 0) number = "0" + number; 

    // Initialize lists
    wholeDigits = new List<int>();
    decimalDigits = new List<int>();

    // Get whole and decimal parts of the number
    var numberParts = number.Split(new[] {'.'}, 
        StringSplitOptions.RemoveEmptyEntries);

    IsWhole = numberParts.Length == 1;

    // Add whole digits to the list
    wholeDigits.AddRange(numberParts[0].Select(n => int.Parse(n.ToString())));

    // Add decimal digits to the list (if there are any)
    if (numberParts.Length > 1 && 
        numberParts[1].Sum(n => int.Parse(n.ToString())) > 0)
    {
        numberParts[1] = numberParts[1].TrimEnd('0');
        decimalDigits.AddRange(numberParts[1].Select(n => int.Parse(n.ToString())));
    }

    NormalizeLists();
}

public LargeDecimal(LargeDecimal initializeFrom)
{
    wholeDigits = initializeFrom.wholeDigits
        .GetRange(0, initializeFrom.wholeDigits.Count);
    decimalDigits = initializeFrom.decimalDigits
        .GetRange(0, initializeFrom.decimalDigits.Count);
    IsWhole = initializeFrom.IsWhole;
    IsNegative = initializeFrom.IsNegative;
    NormalizeLists();
}

然后我实现了 Add 和 Subtract 方法

public void Add(LargeDecimal other)
{
    if (other == null) return;

    if (IsNegative != other.IsNegative)
    {
        // Get the absolue values of the two operands
        var absThis = new LargeDecimal(this) {IsNegative = false};
        var absOther = new LargeDecimal(other) {IsNegative = false};

        // If the signs are different and the values are the same, reset to 0.
        if (absThis == absOther)
        {
            ResetToZero();
            return;
        }

        // Since the signs are different, we will retain the sign of the larger number
        IsNegative = absThis < absOther ? other.IsNegative : IsNegative;

        // Assign the difference of the two absolute values
        absThis.Subtract(absOther);
        wholeDigits = absThis.wholeDigits.GetRange(0, absThis.wholeDigits.Count);
        decimalDigits = absThis.decimalDigits.GetRange(0, absThis.decimalDigits.Count);
        NormalizeLists();
        return;
    }

    // start with the larger decimal digits list
    var newDecimalDigits = new List<int>();
    newDecimalDigits = decimalDigits.Count > other.decimalDigits.Count
        ? decimalDigits.GetRange(0, decimalDigits.Count)
        : other.decimalDigits.GetRange(0, other.decimalDigits.Count);

    // and add the smaller one to it
    int carry = 0; // Represents the value of the 'tens' digit to carry over
    for (int i = Math.Min(decimalDigits.Count, other.decimalDigits.Count) - 1; i >= 0; i--)
    {
        var result = decimalDigits[i] + other.decimalDigits[i] + carry;
        carry = Convert.ToInt32(Math.Floor((decimal) result / 10));
        result = result % 10;
        newDecimalDigits[i] = result;
    }

    var newWholeDigits = new List<int>();
    newWholeDigits = wholeDigits.Count > other.wholeDigits.Count
        ? wholeDigits.GetRange(0, wholeDigits.Count)
        : other.wholeDigits.GetRange(0, other.wholeDigits.Count);

    for (int i = Math.Min(wholeDigits.Count, other.wholeDigits.Count) - 1; i >= 0; i--)
    {
        var result = wholeDigits[i] + other.wholeDigits[i] + carry;
        carry = Convert.ToInt32(Math.Floor((decimal)result / 10));
        result = result % 10;
        newWholeDigits[i] = result;
    }

    if (carry > 0) newWholeDigits.Insert(0, carry);

    wholeDigits = newWholeDigits.GetRange(0, newWholeDigits.Count);
    decimalDigits = newDecimalDigits.GetRange(0, newDecimalDigits.Count);
    NormalizeLists();
}

public void Subtract(LargeDecimal other)
{
    if (other == null) return;

    // If the other value is the same as this one, then the difference is zero
    if (Equals(other))
    {
        ResetToZero();
        return;
    }

    // Absolute values will be used to determine how we subtract
    var absThis = new LargeDecimal(this) {IsNegative = false};
    var absOther = new LargeDecimal(other) {IsNegative = false};

    // If the signs are different, then the difference will be the sum
    if (IsNegative != other.IsNegative)
    {
        absThis.Add(absOther);
        wholeDigits = absThis.wholeDigits.GetRange(0, absThis.wholeDigits.Count);
        decimalDigits = absThis.decimalDigits.GetRange(0, absThis.decimalDigits.Count);
        NormalizeLists();
        return;
    }

    // Subtract smallNumber from bigNumber to get the difference
    LargeDecimal bigNumber;
    LargeDecimal smallNumber;

    if (absThis < absOther)
    {
        bigNumber = new LargeDecimal(absOther);
        smallNumber = new LargeDecimal(absThis);
    }
    else
    {
        bigNumber = new LargeDecimal(absThis);
        smallNumber = new LargeDecimal(absOther);
    }

    // Pad the whole number and decimal number lists where necessary so that both
    // LargeDecimal objects have the same count of whole and decimal numbers.
    AddTrailingZeroes(
        bigNumber.decimalDigits.Count < smallNumber.decimalDigits.Count
            ? bigNumber.decimalDigits
            : smallNumber.decimalDigits,
        Math.Abs(bigNumber.decimalDigits.Count - smallNumber.decimalDigits.Count));

    AddLeadingZeroes(smallNumber.wholeDigits,
        Math.Abs(bigNumber.wholeDigits.Count - smallNumber.wholeDigits.Count));

    var newWholeDigits = new List<int>();
    var newDecimalDigits = new List<int>();

    bool borrowed = false; // True if we borrowed 1 from next number
    for (int i = bigNumber.decimalDigits.Count - 1; i >= 0; i--)
    {
        if (borrowed)
        {
            bigNumber.decimalDigits[i] -= 1; // We borrowed one from this number last time
            borrowed = false;
        }

        if (bigNumber.decimalDigits[i] < smallNumber.decimalDigits[i])
        {
            bigNumber.decimalDigits[i] += 10; // Borrow from next number and add to this one
            borrowed = true;
        }

        // Since we're working from the back of the list, always add to the front
        newDecimalDigits.Insert(0, bigNumber.decimalDigits[i] - smallNumber.decimalDigits[i]);
    }

    for (int i = bigNumber.wholeDigits.Count - 1; i >= 0; i--)
    {
        if (borrowed)
        {
            bigNumber.wholeDigits[i] -= 1;
            borrowed = false;
        }

        if (bigNumber.wholeDigits[i] < smallNumber.wholeDigits[i])
        {
            bigNumber.wholeDigits[i] += 10;
            borrowed = true;
        }

        newWholeDigits.Insert(0, bigNumber.wholeDigits[i] - smallNumber.wholeDigits[i]);
    }

    if (absThis < absOther) IsNegative = !IsNegative;
    wholeDigits = newWholeDigits.GetRange(0, newWholeDigits.Count);
    decimalDigits = newDecimalDigits.GetRange(0, newDecimalDigits.Count);
    NormalizeLists();
}

最后覆盖数字运算符:

public static LargeDecimal operator +(LargeDecimal first, LargeDecimal second)
{
    if (first == null) return second;
    if (second == null) return first;

    var result = new LargeDecimal(first);
    result.Add(second);
    return result;
}

public static LargeDecimal operator -(LargeDecimal first, LargeDecimal second)
{
    if (first == null) return second;
    if (second == null) return first;

    var result = new LargeDecimal(first);
    result.Subtract(second);
    return result;
}

public static bool operator >(LargeDecimal first, LargeDecimal second)
{
    if (first == null) return false;
    return first.CompareTo(second) > 0;
}

public static bool operator <(LargeDecimal first, LargeDecimal second)
{
    if (second == null) return false;
    return second.CompareTo(first) > 0;
}

public static bool operator >=(LargeDecimal first, LargeDecimal second)
{
    if (first == null) return false;
    return first.CompareTo(second) >= 0;
}
public static bool operator <=(LargeDecimal first, LargeDecimal second)
{
    if (second == null) return false;
    return second.CompareTo(first) >= 0;
}
public static bool operator ==(LargeDecimal first, LargeDecimal second)
{
    return Equals(first, second);
}

public static bool operator !=(LargeDecimal first, LargeDecimal second)
{
    return !Equals(first, second);
}

感谢有趣的挑战!!

关于c# - 如何检查表示数字的对象是否大于另一个?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27748276/

相关文章:

c# - 从 Web Api 获取完整的 GenericPrincipal MVC

C# LINQ 自动生成数字或索引

java - Java 中的自省(introspection)

oop - 术语 "Namespace"的澄清

c# - 在 C# 中更改列表值也会更改您的复制元素

c# - 从大小为 n 的 r 个元素生成幂多重集

java - Math.cos() & Math.sin() 如何使用?

c# - 单击按钮时显示气球提示

javascript - C# JSON 反序列化为复杂类

C#,通过其他点的位置和距离值计算点的位置