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

161

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

Par exemple:, MAX * MAX - (MAX - 1) * (MAX + 1) == 1MAX = LLONG_MAX - net n - un nombre naturel.

NGix
la source
17
Quelle est l'importance de la précision?
Anirudh Ramanathan
1
@Cthulhu, excellente question. Il pourrait essayer de créer une fonction équivalente en utilisant un nombre plus petit en les divisant toutes par 10 ou quelque chose, puis en multipliant le résultat.
Chris
4
Les Vars A, B, C, D sont signés. Cela implique A - Cpourrait déborder. Est-ce un problème à considérer ou savez-vous que cela ne se produira pas avec vos données?
William Morris
2
@MooingDuck mais vous pouvez vérifier à l'avance si l'opération débordera stackoverflow.com/a/3224630/158285
bradgonesurfing
1
@Chris: Non, je dis qu'il n'y a pas de moyen portable de vérifier si un débordement signé s'est produit. (Brad a raison de dire que vous pouvez détecter de manière portable que cela se produira). L'utilisation de l'assemblage en ligne est l'une des nombreuses méthodes de vérification non portables.
Mooing Duck

Réponses:

120

Cela semble trop trivial, je suppose. Mais A*Bc'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 faite plus loin .
Comme @Gian l'a souligné, des précautions peuvent être nécessaires lors de l'opération de soustraction si le type est unsigned long long.


Par exemple, avec le cas que vous avez dans la question, cela ne prend qu'une 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
Anirudh Ramanathan
la source
4
@Caleb, appliquez simplement le même algorithme àC*D
Chris
2
Je pense que vous devriez expliquer ce que représente E.
Caleb
7
Les deux long long et double font 64 bits. Puisque double doit allouer quelques bits à l'exposant, il a une plus petite plage de valeurs possibles sans perte de précision.
Jim Garrison
3
@Cthulhu - il me semble que cela ne fonctionnerait que si tout le nombre est très grand ... par exemple, vous auriez toujours un débordement avec {A, B, C, D} = {MAX, MAX, MAX, 2}. L'OP dit "Chaque nombre peut être vraiment grand", mais il n'est pas clair d'après l'énoncé du problème que chaque nombre doit être vraiment grand.
Kevin K
4
Et si certains d'entre eux A,B,C,Dsont négatifs? Ne sera pas Eou Fsera encore plus grand alors?
Supr
68

La solution la plus simple et la plus générale est d'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 en utilisant une structure ou un tableau et en implémentant une sorte de multiplication longue ( 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 pas vraiment besoin de la plupart des bits de R3 et d'aucun de R4

Ofir
la source
8
Le calcul ci-dessus n'est vraiment pas aussi compliqué qu'il en a l'air, c'est vraiment une simple multiplication longue en base 2 ^ 32, et le code en C devrait paraître plus simple. De plus, ce sera une bonne idée de créer des fonctions génériques pour effectuer ce travail dans votre programme.
Ofir
46

Notez que ce n'est pas standard car il repose sur un dépassement de capacité signé. (GCC a des indicateurs de compilateur qui permettent cela.)

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


Voici une solution de contournement qui ne repose que sur le comportement défini par l'implémentation consistant à convertir un entier non signé en entier signé. Mais on peut s'attendre à ce que cela fonctionne 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 unsigned long longlà où le comportement de débordement est garanti pour être enveloppé par la norme. La conversion à 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"

RiaD
la source
+1 Vous êtes le seul à le remarquer. La seule partie délicate consiste à configurer le compilateur pour qu'il effectue un débordement enveloppant et à vérifier si le résultat correct rentre réellement dans un fichier long long.
Mysticial
2
Même la version naïve sans aucune astuce fera ce qu'il faut sur la plupart des implémentations; ce n'est pas garanti par la norme, mais vous devrez trouver une machine à complément de 1 ou un autre appareil assez étrange pour le faire échouer.
hobbs
1
Je pense que c'est une réponse importante. Je conviens que la programmation n'est peut-être pas correcte d'assumer un comportement spécifique à l'implémentation, mais chaque ingénieur doit comprendre l'arithmétique modulo et comment obtenir les bons indicateurs de compilateur pour garantir un comportement cohérent si les performances sont essentielles. Les ingénieurs DSP s'appuient sur ce comportement pour les implémentations de filtres à virgule fixe, pour lesquelles la réponse acceptée aura des performances inacceptables.
Peter M
18

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)
paquetp
la source
1
Merci @bradgonesurfing - pourriez-vous fournir une telle contribution? J'ai mis à jour ma réponse, l'ai exécutée et ça marche (bd et ca valent 0) ...
paquetp
1
Hmmm. Maintenant, j'y pense peut-être pas. Cas dégénéré avec d = 1 et a = 1 et b = maxint et c = maxint cela fonctionne toujours. Cool :)
bradgonesurfing
1
@paquetp: a = 1, b = 0x7fffffffffffffff, c = -0x7fffffffffffffff, d = 1 (la note c est négative). Intelligent cependant, je suis certain que votre code gère correctement tous les nombres positifs.
Mooing Duck
3
@MooingDuck mais la réponse finale pour votre ensemble est également débordée, ce n'est donc pas une configuration valide. Cela ne fonctionne que si chaque côté est du même signe afin que la soustraction résultante soit à portée.
bradgonesurfing
1
Il y a quelque chose d'étrange avec StackOverflow lorsque cette réponse qui est la plus simple et la meilleure a obtenu un score si bas par rapport à la réponse la mieux notée.
bradgonesurfing
9

Vous pouvez envisager de calculer le 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 que le facteur un tel existe, cependant (par exemple, si A, B, Cet Darriver à être premiers entre eux , ils ne seront pas 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 précision numérique.

Gian
la source
1
Le logarithme semble bon s'il long doubleest disponible. Dans ce cas, un niveau de précision acceptable peut être atteint (et le résultat peut être arrondi).
9

Si le résultat tient dans un long long int alors l'expression A * BC * 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 tient 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).

Esteban Crespi
la source
C'est l'exemple le plus pratique. Effacez et donne la bonne réponse (ou lance une exception lorsque les entrées sont mauvaises).
Mark Lakata
1
Sympa et élégant! Vous n'êtes pas tombé dans le piège des autres. Encore une chose: je parie qu'il y a des exemples où le double calcul est inférieur à MAX_LLONG juste en raison d'erreurs d'arrondi. Mon instinct mathématique me dit que vous devriez plutôt calculer la différence du résultat double et du résultat long, et comparer cela à MAX_LLONG / 2 ou quelque chose comme ça. Cette différence sont les erreurs d'arrondi du double calcul et plus le dépassement et devrait normalement être relativement faible, mais dans le cas que j'ai mentionné, elle sera importante. Mais pour le moment, je suis trop paresseux pour le savoir avec certitude. :-)
Hans-Peter Störr
9
E = max(A,B,C,D)
A1 = A -E;
B1 = B -E;
C1 = C -E;
D1 = D -E;

puis

A*B - C*D = (A1+E)*(B1+E)-(C1+E)(D1+E) = (A1+B1-C1-D1)*E + A1*B1 -C1*D1
Landry
la source
7

Vous pouvez écrire chaque nombre dans un tableau, chaque élément étant un chiffre et effectuer les calculs sous forme de 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 123peut être exprimé 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.

Mihai
la source
2
Je ne sais pas ce que c'est mais je vais devoir trouver. mettre :). c'est une solution du dessus de ma tête pendant que je fais du shopping avec ma copine :)
Mihai
vous implémentez des bignums dans un tableau base10. GMP est une bibliothèque bignum de qualité qui utilise la base 4294967296. BEAUCOUP plus rapide. Pas de vote défavorable, car la réponse est correcte et utile.
Mooing Duck
Merci :) . il est utile de savoir que c'est une façon de le faire, mais il existe de meilleures façons, alors ne le faites pas comme ça. du moins pas dans cette situation :)
Mihai
de toute façon ... en utilisant cette solution, vous pourriez calculer un nombre beaucoup plus grand que n'importe quel type primitif pourrait en gras (comme les nombres à 100 chiffres) et conserver le résultat sous forme de tableau. cela mérite un vote à la hausse: p
Mihai
Je ne suis pas certain qu'elle reçoive un vote favorable, car cette méthode (bien qu'efficace et relativement facile à comprendre) est lente et gourmande en mémoire.
Mooing Duck
6

Même si un signed long long intne tiendra pas A*B, deux d'entre eux le feront. Ainsi A*Bpourrait ê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 directe, la sous-action pourrait être effectuée sur chaque paire de AB_iet de CD_imême, en utilisant un bit de report supplémentaire (exactement un entier de 1 bit) pour chacun. Donc, si nous disons E = A * BC * 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 
...

On continue 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 retenue E_11en l'ajoutant avec le bon signe (obtenu à partir de la partie non-retenue) à E_20. Si cela déclenche un débordement, le résultat ne conviendra pas non plus.

E_10a maintenant assez d'espace pour prendre la moitié supérieure de E_00 (décalage, ajouter, effacer) et le bit de report E_01.

E_10peut être plus grand maintenant, nous répétons donc le transfert vers E_20.

À ce stade, E_20doit devenir zéro, sinon le résultat ne correspondra pas. La moitié supérieure deE_10 est également vide suite au transfert.

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

Si l'attente qui E=A*B+C*Dconviendrait aux signed long long intprises, nous avons maintenant

E_20=0
E_10=0
E_00=E
dronus
la source
1
C'est en fait la formule simplifiée que l'on obtiendrait en utilisant la formule de multiplication d'Ofir et en supprimant tous les résultats temporaires inutiles.
dronus
3

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 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, de simples ajustements peuvent être apportés à la routine ConvertToSigned pour cela.

Les utilisations suivantes signed charet unsigned charpour illustrer le code. Pour votre implémentation, modifiez la définition de Signedto typedef signed long long int Signed;et la définition de Unsignedto 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;
}
Eric Postpischil
la source
2

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 encore, vous pouvez les diviser en composants plus petits de manière récursive, puis les recombiner.

bradgonesurf
la source
Cela peut ou peut ne pas être correct, mais est définitivement déroutant. Vous définissez Ket J, pourquoi pas Net M. De plus, je pense que vous enfreignez l'équation en plus gros morceaux. Puisque votre étape 3 est la même que la question du PO, sauf plus compliquée (AK-CJ)->(AB-CD)
Mooing Duck
N n'est simplifié de rien. C'est juste un nombre soustrait de A pour le réduire. En fait, c'est une solution similaire mais inférieure à paquetp. Ici, j'utilise la soustraction au lieu de la division entière pour la rendre plus petite.
bradgonesurfing
2

Je n'ai peut-être pas couvert tous les cas marginaux, ni testé rigoureusement cela, mais cela implémente une technique que je me souviens avoir utilisée dans les années 80 en essayant de faire des calculs entiers 32 bits sur un processeur 16 bits. Essentiellement, vous divisez les 32 bits en deux unités 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());

  }

}

Impressions:

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 que ça marche.

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

VieuxCurmudgeon
la source
1
Je pense que c'est une implémentation de ce que @Ofir a suggéré.
OldCurmudgeon
2

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)
i Code 4 Aliments
la source
1

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Ni B/Cni D/Apeut déborder, alors calculez d' (B/C-D/A)abord. Étant donné que 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*Cquel est le résultat requis.

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

SomeWittyUsername
la source
2
Cela ne fonctionnera pas car la division entière perd des informations (la fraction du résultat)
Ofir
@Ofir c'est correct, mais vous ne pouvez pas manger le gâteau sans le toucher. Vous devez payer soit par précision, soit en utilisant des ressources supplémentaires (comme vous l'avez suggéré dans votre réponse). Ma réponse est de nature mathématique tandis que la vôtre est orientée ordinateur. Chacun peut être correct selon les circonstances.
SomeWittyUsername
2
Vous avez raison - j'aurais dû le formuler ainsi - ne donnera pas un résultat exact plutôt que de ne pas fonctionner, car le calcul est correct. Cependant, notez que dans les cas qui intéressent probablement l'auteur de la question (par exemple, dans l'exemple de la question), l'erreur sera probablement étonnamment grande - beaucoup plus grande que ce qui peut être acceptable pour toute application pratique. En tout cas, c'était une réponse perspicace et je n'aurais pas dû utiliser ce langage.
Ofir
@Ofir Je ne pense pas que votre langage soit inapproprié. Le PO a clairement demandé un calcul "correct", et non un calcul qui perdrait de la précision au nom d'être effectué sous des contraintes de ressources extrêmes.
user4815162342
1

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-Cet B-Dsont de petits nombres.

Amir Saniyan
la source
Comment choisissez-vous K en pratique? De plus, K * (A-C + BD) pourrait encore déborder.
ylc
@ylc: Choisissez K = sqrt (A), ce A-C+B-Dn'est pas un petit nombre. Parce que A, B, C et D sont de grands nombres, AC est donc un petit nombre.
Amir Saniyan
Si vous choisissez K = sqrt (A) , alors (AK) * (BK) risque de déborder à nouveau.
ylc
@ylc: OK! Je le change en A - sqrt(A):)
Amir Saniyan
Alors K * (A-C + BD) peut déborder.
ylc