web-dev-qa-db-fra.com

Comment éviter le débordement dans expr. A B C D

J'ai besoin de calculer une expression qui ressemble à: A*B - C*D, Où leurs types sont: signed long long int A, B, C, D; Chaque nombre peut être vraiment grand (sans déborder son type). Bien que A*B Puisse provoquer un débordement, l'expression A*B - C*D Peut en même temps être très petite. Comment puis-je le calculer correctement?

Par exemple: MAX * MAX - (MAX - 1) * (MAX + 1) == 1, où MAX = LLONG_MAX - n Et n - un certain nombre naturel.

161
NGix

Cela semble trop trivial, je suppose. Mais A*B est celui qui pourrait déborder.

Vous pouvez faire ce qui suit, sans perdre en précision

A*B - C*D = A(D+E) - (A+F)D
          = AD + AE - AD - DF
          = AE - DF
             ^smaller quantities E & F

E = B - D (hence, far smaller than B)
F = C - A (hence, far smaller than C)

Cette décomposition peut être fait plus loin.
Comme l'a souligné @Gian, il peut être nécessaire de prendre des précautions lors de l'opération de soustraction si le type n'est pas signé longtemps.


Par exemple, avec le cas que vous avez dans la question, cela ne prend qu'une seule itération,

 MAX * MAX - (MAX - 1) * (MAX + 1)
  A     B       C           D

E = B - D = -1
F = C - A = -1

AE - DF = {MAX * -1} - {(MAX + 1) * -1} = -MAX + MAX + 1 = 1
120

La solution la plus simple et la plus générale consiste à utiliser une représentation qui ne peut pas déborder, soit en utilisant une bibliothèque d'entiers longs (par exemple http://gmplib.org/ ) soit en représentant à l'aide d'une structure ou d'un tableau et mettre en œuvre une sorte de longue multiplication (c'est-à-dire séparer chaque nombre en deux moitiés de 32 bits et effectuer la multiplication comme ci-dessous:

(R1 + R2 * 2^32 + R3 * 2^64 + R4 * 2^96) = R = A*B = (A1 + A2 * 2^32) * (B1 + B2 * 2^32) 
R1 = (A1*B1) % 2^32
R2 = ((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) % 2^32
R3 = (((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) %2^32
R4 = ((((A1*B1) / 2^32 + (A1*B2) % 2^32 + (A2*B1) % 2^32) / 2^32 + (A1*B2) / 2^32 + (A2*B1) / 2^32 + (A2*B2) % 2^32) / 2^32) + (A2*B2) / 2^32

En supposant que le résultat final tient dans 64 bits, vous n'avez en fait pas vraiment besoin de la plupart des bits de R3 et aucun de R4

68
Ofir

Notez que ce n'est pas standard car il repose sur un débordement signé enveloppant. (GCC a des drapeaux de compilation qui permettent cela.)

Mais si vous faites simplement tous les calculs dans long long, résultat de l'application directe de la formule:
(A * B - C * D) sera précis tant que le résultat correct s'inscrit dans un long long.


Voici une solution de contournement qui ne repose que sur le comportement défini par l'implémentation de la conversion d'un entier non signé en entier signé. Mais cela devrait fonctionner sur presque tous les systèmes aujourd'hui.

(long long)((unsigned long long)A * B - (unsigned long long)C * D)

Cela convertit les entrées en unsigned long long où le comportement de débordement est garanti d'être bouclé par la norme. La restitution à un entier signé à la fin est la partie définie par l'implémentation, mais fonctionnera sur presque tous les environnements aujourd'hui.


Si vous avez besoin d'une solution plus pédante, je pense que vous devez utiliser "l'arithmétique longue"

46
RiaD

Cela devrait fonctionner (je pense):

signed long long int a = 0x7ffffffffffffffd;
signed long long int b = 0x7ffffffffffffffd;
signed long long int c = 0x7ffffffffffffffc;
signed long long int d = 0x7ffffffffffffffe;
signed long long int bd = b / d;
signed long long int bdmod = b % d;
signed long long int ca = c / a;
signed long long int camod = c % a;
signed long long int x = (bd - ca) * a * d - (camod * d - bdmod * a);

Voici ma dérivation:

x = a * b - c * d
x / (a * d) = (a * b - c * d) / (a * d)
x / (a * d) = b / d - c / a

now, the integer/mod stuff:
x / (a * d) = (b / d + ( b % d ) / d) - (c / a + ( c % a ) / a )
x / (a * d) = (b / d - c / a) - ( ( c % a ) / a - ( b % d ) / d)
x = (b / d - c / a) * a * d - ( ( c % a ) * d - ( b % d ) * a)
18
paquetp

Vous pouvez envisager de calculer un plus grand facteur commun pour toutes vos valeurs, puis de les diviser par ce facteur avant d'effectuer vos opérations arithmétiques, puis de multiplier à nouveau. Cela suppose cependant qu'un tel facteur existe (par exemple, si A, B, C et D se trouvent être relativement premiers, ils gagneront ' t ont un facteur commun).

De même, vous pourriez envisager de travailler sur des échelles logarithmiques, mais cela va être un peu effrayant, sous réserve de la précision numérique.

9
Gian
E = max(A,B,C,D)
A1 = A -E;
B1 = B -E;
C1 = C -E;
D1 = D -E;

ensuite

A*B - C*D = (A1+E)*(B1+E)-(C1+E)(D1+E) = (A1+B1-C1-D1)*E + A1*B1 -C1*D1
9
landry

Si le résultat tient dans un long long int alors l'expression A * B-C * D est correcte car elle exécute le mod arithmétique 2 ^ 64, et donnera le résultat correct. Le problème est de savoir si le résultat s'inscrit dans un long long int. Pour détecter cela, vous pouvez utiliser l'astuce suivante en utilisant des doubles:

if( abs( (double)A*B - (double)C*D ) > MAX_LLONG ) 
    Overflow
else 
    return A*B-C*D;

Le problème avec cette approche est que vous êtes limité par la précision de la mantisse des doubles (54bits?) Donc vous devez limiter les produits A * B et C * D à 63 + 54 bits (ou probablement un peu moins).

9
Esteban Crespi

Vous pouvez écrire chaque nombre dans un tableau, chaque élément étant un chiffre et faire les calculs sous la forme polynômes . Prenez le polynôme résultant, qui est un tableau, et calculez le résultat en multipliant chaque élément du tableau par 10 à la puissance de la position dans le tableau (la première position étant la plus grande et la dernière étant zéro).

Le nombre 123 peut s'exprimer comme suit:

123 = 100 * 1 + 10 * 2 + 3

pour lequel vous venez de créer un tableau [1 2 3].

Vous faites cela pour tous les nombres A, B, C et D, puis vous les multipliez sous forme de polynômes. Une fois que vous avez le polynôme résultant, vous en reconstruisez simplement le nombre.

7
Mihai

Alors qu'un signed long long int ne tiendra pas A*B, deux d'entre eux le feront. Donc A*B pourrait être décomposé en termes d'arbre d'exposant différent, chacun d'entre eux convenant à un signed long long int.

A1=A>>32;
A0=A & 0xffffffff;
B1=B>>32;
B0=B & 0xffffffff;

AB_0=A0*B0;
AB_1=A0*B1+A1*B0;
AB_2=A1*B1;

Pareil pour C*D.

En suivant la voie droite, la sous-traction pourrait être effectuée pour chaque paire de AB_i et CD_i de même, en utilisant un bit de retenue supplémentaire (avec précision un entier de 1 bit) pour chacun. Donc, si nous disons E = A * B-C * D, vous obtenez quelque chose comme:

E_00=AB_0-CD_0 
E_01=(AB_0 > CD_0) == (AB_0 - CD_0 < 0) ? 0 : 1  // carry bit if overflow
E_10=AB_1-CD_1 
...

Nous continuons en transférant la moitié supérieure de E_10 à E_20 (décaler de 32 et ajouter, puis effacer la moitié supérieure de E_10).

Vous pouvez maintenant vous débarrasser du bit de transport E_11 en l'ajoutant avec le bon signe (obtenu à partir de la partie non portée) à E_20. Si cela déclenche un débordement, le résultat ne conviendrait pas non plus.

E_10 dispose désormais de suffisamment d'espace pour prendre la moitié supérieure de E_00 (décalage, ajout, effacement) et le bit de retenue E_01.

E_10 peut être à nouveau plus grand, nous répétons donc le transfert vers E_20.

À ce point, E_20 doit devenir nul, sinon le résultat ne rentrera pas. La moitié supérieure de E_10 est également vide à la suite du transfert.

La dernière étape consiste à transférer la moitié inférieure de E_20 en E_10 encore.

Si l'attente que E=A*B+C*D correspondrait au signed long long int tient, nous avons maintenant

E_20=0
E_10=0
E_00=E
6
dronus

Si vous savez que le résultat final est représentable dans votre type entier, vous pouvez effectuer ce calcul rapidement en utilisant le code ci-dessous. Étant donné que la norme C spécifie que l'arithmétique non signée est une arithmétique modulo et ne déborde pas, vous pouvez utiliser un type non signé pour effectuer le calcul.

Le code suivant suppose qu'il existe un type non signé de la même largeur et que le type signé utilise tous les modèles de bits pour représenter les valeurs (pas de représentation d'interruption, le minimum du type signé est le négatif de la moitié du module du type non signé). Si cela ne tient pas dans une implémentation C, des ajustements simples peuvent être apportés à la routine ConvertToSigned pour cela.

Les utilisations suivantes signed char et unsigned char pour illustrer le code. Pour votre implémentation, remplacez la définition de Signed par typedef signed long long int Signed; et la définition de Unsigned à typedef unsigned long long int Unsigned;.

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>


//  Define the signed and unsigned types we wish to use.
typedef signed char   Signed;
typedef unsigned char Unsigned;

//  uHalfModulus is half the modulus of the unsigned type.
static const Unsigned uHalfModulus = UCHAR_MAX/2+1;

//  sHalfModulus is the negation of half the modulus of the unsigned type.
static const Signed   sHalfModulus = -1 - (Signed) (UCHAR_MAX/2);


/*  Map the unsigned value to the signed value that is the same modulo the
    modulus of the unsigned type.  If the input x maps to a positive value, we
    simply return x.  If it maps to a negative value, we return x minus the
    modulus of the unsigned type.

    In most C implementations, this routine could simply be "return x;".
    However, this version uses several steps to convert x to a negative value
    so that overflow is avoided.
*/
static Signed ConvertToSigned(Unsigned x)
{
    /*  If x is representable in the signed type, return it.  (In some
        implementations, 
    */
    if (x < uHalfModulus)
        return x;

    /*  Otherwise, return x minus the modulus of the unsigned type, taking
        care not to overflow the signed type.
    */
    return (Signed) (x - uHalfModulus) - sHalfModulus;
}


/*  Calculate A*B - C*D given that the result is representable as a Signed
    value.
*/
static signed char Calculate(Signed A, Signed B, Signed C, Signed D)
{
    /*  Map signed values to unsigned values.  Positive values are unaltered.
        Negative values have the modulus of the unsigned type added.  Because
        we do modulo arithmetic below, adding the modulus does not change the
        final result.
    */
    Unsigned a = A;
    Unsigned b = B;
    Unsigned c = C;
    Unsigned d = D;

    //  Calculate with modulo arithmetic.
    Unsigned t = a*b - c*d;

    //  Map the unsigned value to the corresponding signed value.
    return ConvertToSigned(t);
}


int main()
{
    //  Test every combination of inputs for signed char.
    for (int A = SCHAR_MIN; A <= SCHAR_MAX; ++A)
    for (int B = SCHAR_MIN; B <= SCHAR_MAX; ++B)
    for (int C = SCHAR_MIN; C <= SCHAR_MAX; ++C)
    for (int D = SCHAR_MIN; D <= SCHAR_MAX; ++D)
    {
        //  Use int to calculate the expected result.
        int t0 = A*B - C*D;

        //  If the result is not representable in signed char, skip this case.
        if (t0 < SCHAR_MIN || SCHAR_MAX < t0)
            continue;

        //  Calculate the result with the sample code.
        int t1 = Calculate(A, B, C, D);

        //  Test the result for errors.
        if (t0 != t1)
        {
            printf("%d*%d - %d*%d = %d, but %d was returned.\n",
                A, B, C, D, t0, t1);
            exit(EXIT_FAILURE);
        }
    }
    return 0;
}
3
Eric Postpischil

Vous pouvez essayer de diviser l'équation en composants plus petits qui ne débordent pas.

AB - CD
= [ A(B - N) - C( D - M )] + [AN - CM]

= ( AK - CJ ) + ( AN - CM)

    where K = B - N
          J = D - M

Si les composants débordent toujours, vous pouvez les diviser récursivement en composants plus petits puis les recombiner.

2
bradgonesurfing

Par souci d'exhaustivité, puisque personne ne l'a mentionné, certains compilateurs (par exemple GCC) vous fournissent actuellement un entier de 128 bits.

Ainsi, une solution simple pourrait être:

(long long)((__int128)A * B - (__int128)C * D)
2
i Code 4 Food

Je n'ai peut-être pas couvert tous les cas Edge, ni testé rigoureusement cela, mais cela met en œuvre une technique dont je me souviens avoir utilisé dans les années 80 lorsque j'essayais de faire des mathématiques entières sur 32 bits sur un processeur 16 bits. Essentiellement, vous divisez les 32 bits en deux unités de 16 bits et travaillez avec eux séparément.

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

  }

}

Tirages:

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

qui me semble fonctionner.

Je parie que j'ai raté certaines des subtilités telles que la surveillance du débordement des signes, etc., mais je pense que l'essence est là.

2
OldCurmudgeon

Choisissez K = a big number (Par exemple. K = A - sqrt(A))

A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A-C+B-D); // Avoid overflow.

Pourquoi?

(A-K)*(B-K) = A*B - K*(A+B) + K^2
(C-K)*(D-K) = C*D - K*(C+D) + K^2

=>
(A-K)*(B-K) - (C-K)*(D-K) = A*B - K*(A+B) + K^2 - {C*D - K*(C+D) + K^2}
(A-K)*(B-K) - (C-K)*(D-K) = A*B - C*D - K*(A+B) + K*(C+D) + K^2 - K^2
(A-K)*(B-K) - (C-K)*(D-K) = A*B - C*D - K*(A+B-C-D)

=>
A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A+B-C-D)

=>
A*B - C*D = (A-K)*(B-K) - (C-K)*(D-K) + K*(A-C+B-D)

Notez que Parce que A, B, C et D sont de grands nombres, donc A-C Et B-D Sont de petits nombres.

1
Amir Saniyan

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Ni B/C ni D/A peut déborder, alors calculez (B/C-D/A) premier. Comme le résultat final ne débordera pas selon votre définition, vous pouvez effectuer en toute sécurité les multiplications restantes et calculer (B/C-D/A)*A*C qui est le résultat requis.

Notez que si votre entrée peut également être extrêmement petite, le B/C ou D/A peut déborder. Si cela est possible, des manipulations plus complexes peuvent être nécessaires en fonction de l'inspection des entrées.

1