0

How would i create a type that works with integers, supports at least addition substraction division and multiplication and guarantees and integer number IF the operation leads to an integer (otherwise throw).

For example i'd like to be able to do something like:

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

I realise this is a odd use case but i do have this need (performing a sequence of operations, which in floating point could be missrepresented, but are guaranteed to end up as a valid int).

Ronan Thibaudau
  • 3,413
  • 3
  • 29
  • 78
  • Why would the result be 2? I'd expect 18 or 20. But you can just do normal division and then cast to an int – Sayse Apr 21 '15 at 08:53
  • @Sayse Through me, but that `Divide` is a `DivideInto` so it calculates 3/10... (naming is hard). – Richard Apr 21 '15 at 08:54
  • [SO] does not do recommendations. We'll help you if you get stuck will a "big rational" library you find, but we won't find it for you. – Richard Apr 21 '15 at 08:55
  • Actually it's just a typo i changed A without changing my comment after writing (A was originally 1). Changing the text now. – Ronan Thibaudau Apr 21 '15 at 08:55
  • You would have to store the prime factors for numerator and denominator separately. I guess there is one but we don't recommend libraries here any longer.. – TaW Apr 21 '15 at 08:55
  • @Richard I'm not looking for recommandations on a Library, i didn't even know if such libraries existed so i was looking for tips on what do to myself, i don't even know where to start googling but even "big rational" wasn't a term i'd have known to Google. – Ronan Thibaudau Apr 21 '15 at 08:56
  • There's a BigInteger, making a BigRational out of two of them is not exactly hard – harold Apr 21 '15 at 08:57
  • Richard, It sounds like you are looking for [`Math.Ceiling`](https://msdn.microsoft.com/en-us/library/zx4t0t48%28v=vs.110%29.aspx), [Related question](http://stackoverflow.com/questions/4846493/how-to-always-round-up-to-the-next-integer) Edit: Actually you probably want to [round up](https://msdn.microsoft.com/en-us/library/ef48waz8%28v=vs.110%29.aspx) – Sayse Apr 21 '15 at 08:57
  • BigRational doesn't sound like what i want (i do NOT want to store arbitrary precision numbers, i do want to do integer only operations on integers, so basically if anything would result in a floating point result, i want an exception, i want to get a non truncated non rounded int) – Ronan Thibaudau Apr 21 '15 at 08:58
  • contradicting myself: [Rational numbers on codeproject](http://www.codeproject.com/Articles/88980/Rational-Numbers-NET-Version-Rational-Computin), [Math.NET Numerics](http://numerics.mathdotnet.com/#Math-NET-Numerics), [BigRational](http://bcl.codeplex.com/wikipage?title=BigRational) – TaW Apr 21 '15 at 09:00
  • 2
    _I do NOT want to store arbitrary precision numbers_ The way you wrote the question: After the first division how would you store the intermediate result? – TaW Apr 21 '15 at 09:03
  • You don't have to store the intermediate result, only to store the operations and by the time GetNumber is called do the operations in an appropriate order. For example 10/3*6 can be re ordered as 10/6*3 = 10/2 = 5. – Ronan Thibaudau Apr 21 '15 at 09:18
  • Watch your typos, please.. So I repeat: _You have to store the prime factors for numerator and denominator separately._ – TaW Apr 21 '15 at 09:42
  • Then can you post an answer with a sample of what you mean? – Ronan Thibaudau Apr 21 '15 at 11:11
  • Removed the Library request to conform better to the suggestions and added a line that should throw to better convey the expected behavior – Ronan Thibaudau Apr 21 '15 at 11:14
  • Some kind of `Rational` type using `BigIntegers` for nominator/denominator is the best *generic* solution I can think of. At the end you can simplify the fraction and check if the denominator is 1. That's far simpler than storing a sequence of operation and trying to reorder them. For your specific problem there might be a better solution, exploiting the reason that guarantees that the result is an integer, but you didn't include enough context. – CodesInChaos Apr 21 '15 at 11:25
  • @CodesInChaos That sounds like the simplest way yes. I didn't include more context on purpose (i didn't want to have the discution derail on the input and the why, i also can't rely on it so exploiting it is risky, no one is safe from the occasional bug and here it would be hard to find) – Ronan Thibaudau Apr 21 '15 at 11:53
  • if you don't want BigInteger for BigRational you can write a rational class yourself, using only int/long operations without the need of BigInt, but it may not work in all cases. Another solution is using BigInteger and doing all the multiplications first then divisions – phuclv Apr 21 '15 at 11:56
  • If you can't know if something will work yet, don't do it yet; use a promise. – Jon Hanna Apr 21 '15 at 12:31

4 Answers4

5

Because we can't know that 10 / 3 will eventually result in a precise integer answer until after the * 6 we have to defer it until then with a promise:

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);
  }
}

Here each Precise has both an integer value and an operation that will be performed on it. Further operations produce a new Precise (doing this sort of thing as a mutable is crazy) with a new operation but when possible those operations are combined into a single simpler operation. Hence "divide by three then multiply by six" becomes "multiply by two".

We can test this thus:

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();
}

A good solution would also deal well with operations done where the LHS was an integer and the RHS a Precise and where both where a Precise; left as an exercise for the reader ;)

Sadly we have to get much more complicated to handle (10 / 3 + 1) * 3, with the improvement having to be made in the Combine implementations.

Edit: Musing a bit further on the issues of doing the above well enough to catch at least most of the edge cases, I think it should start with only dealing with operations between two Precise objects, because going int -> Precise is trivial and can easily be put on top, but going Precise -> int requires a call to the calculation, perhaps too early. I'd also make the operations the key thing acted upon (have the operation store one or two objects which in turn contain an operation or a value). Then if you started with a representation of the sum (10 / 3) + 5 and multiplied it by 6 it's easier to turn that into (10 * (6 / 3)) + (5 * 6) which upon final calculation can give the precise result 50 rather than fail because it hits the imprecise 10 / 3.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • Exactly what i was looking for thank you (way more detailed than i'd hope actually, thanks for the very well written sample code!). I will start a Bounty when the timer is gone and award it to you – Ronan Thibaudau Apr 21 '15 at 13:41
  • 1
    "way more detailed than i'd hope actually" But do be very careful, because it's still way more simple than a complete job; there are a lot of things to be careful about when bringing the above up to production-ready level. Have a tonne of unit tests going! – Jon Hanna Apr 21 '15 at 13:48
  • Aye i was just looking for a starting point so it's a nice headstart still. – Ronan Thibaudau Apr 21 '15 at 14:33
1

If you don't allow arbitrary precision rationals, it seems that you are asking the impossible without more constraints.

Take 1 and divide it by 65537 twice, then multiply by 65537 twice to retrieve 1: this can't fit in 32 bits integers.

  • Aye i understand that, i'm not asking for it to fit in a specific size however. I don't mind if rationals are used within the type implementation, but i do need to get out either an exception (if it cannot be converted to an int without any data loss) or a single int out. – Ronan Thibaudau Apr 21 '15 at 11:51
  • Though you say "i do NOT want to store arbitrary precision numbers". –  Apr 21 '15 at 12:05
0

Then round final answer using Math.Round().

jdweng
  • 33,250
  • 2
  • 15
  • 20
  • I don't want to round it, at all. Rounding isn't the same as being sure there was no loss, it's hoping for the best, i want for it to never go to floating point even as an intermediary step. – Ronan Thibaudau Apr 21 '15 at 11:09
0

I would use a decimal for the result of operation and on the GetNumber check on the .ToString if there is a "." If yes I throw an Exception, if not I convert it to an int.

Pak
  • 2,639
  • 2
  • 21
  • 27