How to avoid overflow in expr. A * B - C * D

后端 未结 15 998
萌比男神i
萌比男神i 2021-01-29 18:18

I need to compute an expression which looks like: A*B - C*D, where their types are: signed long long int A, B, C, D; Each number can be really big (not

相关标签:
15条回答
  • 2021-01-29 19:04

    I may not have covered all of the edge cases, nor have I rigorously tested this but this implements a technique I remember using in the 80s when trying to do 32-bit integer maths on a 16-bit cpu. Essentially you split the 32 bits into two 16-bit units and work with them separately.

    public class DoubleMaths {
      private static class SplitLong {
        // High half (or integral part).
        private final long h;
        // Low half.
        private final long l;
        // Split.
        private static final int SPLIT = (Long.SIZE / 2);
    
        // Make from an existing pair.
        private SplitLong(long h, long l) {
          // Let l overflow into h.
          this.h = h + (l >> SPLIT);
          this.l = l % (1l << SPLIT);
        }
    
        public SplitLong(long v) {
          h = v >> SPLIT;
          l = v % (1l << SPLIT);
        }
    
        public long longValue() {
          return (h << SPLIT) + l;
        }
    
        public SplitLong add ( SplitLong b ) {
          // TODO: Check for overflow.
          return new SplitLong ( longValue() + b.longValue() );
        }
    
        public SplitLong sub ( SplitLong b ) {
          // TODO: Check for overflow.
          return new SplitLong ( longValue() - b.longValue() );
        }
    
        public SplitLong mul ( SplitLong b ) {
          /*
           * e.g. 10 * 15 = 150
           * 
           * Divide 10 and 15 by 5
           * 
           * 2 * 3 = 5
           * 
           * Must therefore multiply up by 5 * 5 = 25
           * 
           * 5 * 25 = 150
           */
          long lbl = l * b.l;
          long hbh = h * b.h;
          long lbh = l * b.h;
          long hbl = h * b.l;
          return new SplitLong ( lbh + hbl, lbl + hbh );
        }
    
        @Override
        public String toString () {
          return Long.toHexString(h)+"|"+Long.toHexString(l);
        }
      }
    
      // I'll use long and int but this can apply just as easily to long-long and long.
      // The aim is to calculate A*B - C*D without overflow.
      static final long A = Long.MAX_VALUE;
      static final long B = Long.MAX_VALUE - 1;
      static final long C = Long.MAX_VALUE;
      static final long D = Long.MAX_VALUE - 2;
    
      public static void main(String[] args) throws InterruptedException {
        // First do it with BigIntegers to get what the result should be.
        BigInteger a = BigInteger.valueOf(A);
        BigInteger b = BigInteger.valueOf(B);
        BigInteger c = BigInteger.valueOf(C);
        BigInteger d = BigInteger.valueOf(D);
        BigInteger answer = a.multiply(b).subtract(c.multiply(d));
        System.out.println("A*B - C*D = "+answer+" = "+answer.toString(16));
    
        // Make one and test its integrity.
        SplitLong sla = new SplitLong(A);
        System.out.println("A="+Long.toHexString(A)+" ("+sla.toString()+") = "+Long.toHexString(sla.longValue()));
    
        // Start small.
        SplitLong sl10 = new SplitLong(10);
        SplitLong sl15 = new SplitLong(15);
        SplitLong sl150 = sl10.mul(sl15);
        System.out.println("10="+sl10.longValue()+"("+sl10.toString()+") * 15="+sl15.longValue()+"("+sl15.toString()+") = "+sl150.longValue() + " ("+sl150.toString()+")");
    
        // The real thing.
        SplitLong slb = new SplitLong(B);
        SplitLong slc = new SplitLong(C);
        SplitLong sld = new SplitLong(D);
        System.out.println("B="+Long.toHexString(B)+" ("+slb.toString()+") = "+Long.toHexString(slb.longValue()));
        System.out.println("C="+Long.toHexString(C)+" ("+slc.toString()+") = "+Long.toHexString(slc.longValue()));
        System.out.println("D="+Long.toHexString(D)+" ("+sld.toString()+") = "+Long.toHexString(sld.longValue()));
        SplitLong sanswer = sla.mul(slb).sub(slc.mul(sld));
        System.out.println("A*B - C*D = "+sanswer+" = "+sanswer.longValue());
    
      }
    
    }
    

    Prints:

    A*B - C*D = 9223372036854775807 = 7fffffffffffffff
    A=7fffffffffffffff (7fffffff|ffffffff) = 7fffffffffffffff
    10=10(0|a) * 15=15(0|f) = 150 (0|96)
    B=7ffffffffffffffe (7fffffff|fffffffe) = 7ffffffffffffffe
    C=7fffffffffffffff (7fffffff|ffffffff) = 7fffffffffffffff
    D=7ffffffffffffffd (7fffffff|fffffffd) = 7ffffffffffffffd
    A*B - C*D = 7fffffff|ffffffff = 9223372036854775807
    

    which looks to me like it's working.

    I bet I've missed some of the subtleties such as watching for sign overflow etc. but I think the essence is there.

    0 讨论(0)
  • 2021-01-29 19:04

    AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Neither B/C nor D/A can overflow, so calculate (B/C-D/A) first. Since the final result won't overflow according to your definition, you can safely perform the remaining multiplications and calculate (B/C-D/A)*A*C which is the required result.

    Note, if your input can be extremely small as well, the B/C or D/A can overflow. If it's possible, more complex manipulations might be required according to input inspection.

    0 讨论(0)
  • 2021-01-29 19:05
    E = max(A,B,C,D)
    A1 = A -E;
    B1 = B -E;
    C1 = C -E;
    D1 = D -E;
    

    then

    A*B - C*D = (A1+E)*(B1+E)-(C1+E)(D1+E) = (A1+B1-C1-D1)*E + A1*B1 -C1*D1
    
    0 讨论(0)
提交回复
热议问题