无精度损失整数运算

Ron*_*dau 0 c# math floating-point

我如何创建一个使用整数的类型,至少支持加法减法除法和乘法以及保证和整数数字IF操作导致整数(否则抛出).

例如,我希望能够做类似的事情:

Precise A = 10;
A.Divide(3);
A.GetNumber(); // This would throw an exception as 10/3 isn't an int.
A.Multiply(6);
int result = A.GetNumber; // I want result to be = to 20, not to a floating point type that would round to 2 or be very close like 1.9999999999999999999999998992
Run Code Online (Sandbox Code Playgroud)

我意识到这是一个奇怪的用例,但我确实有这个需要(执行一系列操作,在浮点可能会被错过,但保证最终成为有效的int).

Jon*_*nna 5

因为我们无法知道10 / 3最终会产生一个精确的整数答案,直到* 6我们不得不推迟到那时为止:

public sealed class Precise
{
  private interface IOperation
  {
    int Calculate(int value);
    IOperation Combine(IOperation next);
  }
  private sealed class NoOp : IOperation
  {
    public static NoOp Instance = new NoOp();
    public int Calculate(int value)
    {
      return value;
    }
    public IOperation Combine(IOperation next)
    {
      return next;
    }
  }
  private sealed class Combo : IOperation
  {
    private readonly IOperation _first;
    private readonly IOperation _second;
    public Combo(IOperation first, IOperation second)
    {
      _first = first;
      _second = second;
    }
    public int Calculate(int value)
    {
      return _second.Calculate(_first.Calculate(value));
    }
    public IOperation Combine(IOperation next)
    {
      return new Combo(_first, _second.Combine(next));
    }
  }
  private sealed class Mult : IOperation
  {
    private readonly int _multiplicand;
    public Mult(int multiplicand)
    {
      _multiplicand = multiplicand;
    }
    public int Calculate(int value)
    {
      return value * _multiplicand;
    }
    public int Multiplicand
    {
      get { return _multiplicand; }
    }
    public IOperation Combine(IOperation next)
    {
      var nextMult = next as Mult;
      if(nextMult != null)
        return new Mult(_multiplicand * nextMult._multiplicand);
      var nextDiv = next as Div;
      if(nextDiv != null)
      {
        int divisor = nextDiv.Divisor;
        if(divisor == _multiplicand)
          return NoOp.Instance;//multiplcation by 1
        if(divisor > _multiplicand)
        {
          if(divisor % _multiplicand == 0)
            return new Div(divisor / _multiplicand);
        }
        if(_multiplicand % divisor == 0)
          return new Mult(_multiplicand / divisor);
      }
      return new Combo(this, next);
    }
  }
  private sealed class Div : IOperation
  {
    private readonly int _divisor;
    public Div(int divisor)
    {
      _divisor = divisor;
    }
    public int Divisor
    {
      get { return _divisor; }
    }
    public int Calculate(int value)
    {
      int ret = value / _divisor;
      if(value != ret * _divisor)
        throw new InvalidOperationException("Imprecise division");
      return ret;
    }
    public IOperation Combine(IOperation next)
    {
      var nextDiv = next as Div;
      if(nextDiv != null)
        return new Div(_divisor * nextDiv._divisor);
      var nextMult = next as Mult;
      if(nextMult != null)
      {
        var multiplicand = nextMult.Multiplicand;
        if(multiplicand == _divisor)
          return NoOp.Instance;
        if(multiplicand > _divisor)
        {
          if(multiplicand % _divisor == 0)
            return new Mult(multiplicand / _divisor);
        }
        else if(_divisor % multiplicand == 0)
          return new Div(multiplicand / _divisor);
      }
      return new Combo(this, next);
    }
  }
  private sealed class Plus : IOperation
  {
    private readonly int _addend;
    public Plus(int addend)
    {
      _addend = addend;
    }
    public int Calculate(int value)
    {
      return value + _addend;
    }
    public IOperation Combine(IOperation next)
    {
      var nextPlus = next as Plus;
      if(nextPlus != null)
      {
        int newAdd = _addend + nextPlus._addend;
        return newAdd == 0 ? (IOperation)NoOp.Instance : new Plus(newAdd);
      }
      return new Combo(this, next);
    }
  }
  private readonly int _value;
  private readonly IOperation _operation;
  public static readonly Precise Zero = new Precise(0);
  private Precise(int value, IOperation operation)
  {
    _value = value;
    _operation = operation;
  }
  public Precise(int value)
    : this(value, NoOp.Instance)
  {
  }
  public int GetNumber()
  {
    return _operation.Calculate(_value);
  }
  public static explicit operator int(Precise value)
  {
    return value.GetNumber();
  }
  public static implicit operator Precise(int value)
  {
    return new Precise(value);
  }
  public override string ToString()
  {
    return GetNumber().ToString();
  }
  public Precise Multiply(int multiplicand)
  {
    if(multiplicand == 0)
      return Zero;
    return new Precise(_value, _operation.Combine(new Mult(multiplicand)));
  }
  public static Precise operator * (Precise precise, int value)
  {
    return precise.Multiply(value);
  }
  public Precise Divide(int divisor)
  {
    return new Precise(_value, _operation.Combine(new Div(divisor)));
  }
  public static Precise operator / (Precise precise, int value)
  {
    return precise.Divide(value);
  }
  public Precise Add(int addend)
  {
    return new Precise(_value, _operation.Combine(new Plus(addend)));
  }
  public Precise Subtract(int minuend)
  {
    return Add(-minuend);
  }
  public static Precise operator + (Precise precise, int value)
  {
    return precise.Add(value);
  }
  public static Precise operator - (Precise precise, int value)
  {
    return precise.Subtract(value);
  }
}
Run Code Online (Sandbox Code Playgroud)

这里每个Precise都有一个整数值和一个将在其上执行的操作.进一步的操作会产生一个新的Precise(做一种可变的事情就像疯了一样),但是如果可能的话,这些操作会合并成一个简单的操作.因此,"除以3然后乘以6"变为"乘以2".

我们可以这样测试:

public static void Main(string[] args)
{
  Precise A = 10;
  A /= 3;
  try
  {
    var test = (int)A;
  }
  catch(InvalidOperationException)
  {
    Console.Error.WriteLine("Invalid operation attempted");
  }
  A *= 6;
  int result = (int)A;
  Console.WriteLine(result);
  // Let's do 10 / 5 * 2 = 4 because it works but can't be pre-combined:
  Console.WriteLine(new Precise(10) / 5 * 2);
  // Let's do 10 / 5 * 2 - 6 + 4 == 2 to mix in addition and subtraction:
  Console.WriteLine(new Precise(10) / 5 * 2 - 6 + 4);
  Console.Read();
}
Run Code Online (Sandbox Code Playgroud)

一个好的解决方案也可以很好地处理LHS是一个整数和RHS a所做的操作Precise,其中两者都是a Precise; 留给读者的练习;)

遗憾的是,我们必须要处理得更复杂(10 / 3 + 1) * 3,必须在Combine实现中进行改进.

编辑:对上述问题进行更深入的研究,以至少抓住大部分边缘情况,我认为它应该从处理两个Precise对象之间的操作开始,因为go int- > Precise很简单,很容易被放到在顶部,但是Precise- > int需要调用计算,可能为时过早.我还要使操作成为关键的操作(让操作存储一个或两个对象,而这些对象又包含一个操作或一个值).然后,如果你开始使用总和的表示(10 / 3) + 5并将其乘以6,则更容易将其转换为(10 * (6 / 3)) + (5 * 6)最终计算时可以给出精确结果50而不是失败因为它达到不精确10 / 3.