9
votes

(a * b) / c MulDiv et gestion du débordement de la multiplication intermédiaire

Je dois faire l'arithmétique suivante:

public static long divide(int[] dividend, long divisor)

Bien que le résultat soit garanti pour tenir dans long , la multiplication ne l'est pas, donc elle peut déborder.

/ p>

J'ai essayé de le faire étape par étape (d'abord multiplier puis diviser) tout en traitant le débordement en divisant le résultat intermédiaire de a * b en un tableau int de taille max 4 (un peu comme le BigInteger utilise sa variable int [] mag ).

Ici, je suis resté coincé avec la division. Je ne peux pas comprendre les changements de bits nécessaires pour effectuer une division précise. Tout ce dont j'ai besoin est le quotient (je n'ai pas besoin du reste).

La méthode hypothétique serait:

long a,b,c;
long result = a*b/c;

De plus, je n'envisage pas d'utiliser BigInteger car cette partie du code doit être rapide (je voudrais m'en tenir à l'utilisation de primitives et de tableaux primitifs).

Toute aide serait très appréciée!

Modifier: Je n'essaye pas d'implémenter tout le BigInteger moi-même. Ce que j'essaie de faire, c'est de résoudre un problème spécifique ( a * b / c , où a * b peut déborder) plus rapidement que d'utiliser le générique BigInteger .

Edit2: Ce serait idéal si cela pouvait être fait de manière astucieuse , en ne débordant pas du tout, quelques astuces ont fait surface dans les commentaires, mais je J'en cherche toujours un qui est correct.

Mise à jour: J'ai essayé de porter le code BigInteger à mes besoins spécifiques, sans création d'objet, et dans la première itération, j'ai obtenu une amélioration d'environ 46% de la vitesse par rapport à l'utilisation de BigInteger (sur mon PC de développement).

Ensuite, j'ai essayé la solution @David Eisenstat un peu modifiée, qui m'a donné ~ 56% (j'ai exécuté 100_000_000_000 entrées aléatoires de Long.MIN_VALUE à Long.MAX_VALUE ) temps d'exécution réduits (plus de 2x ) par rapport à BigInteger (soit ~ 18% par rapport à mon algorithme BigInteger adapté).

Il y aura plus d'itérations sur l'optimisation et les tests, mais à ce stade, je pense que je dois accepter cette réponse comme la meilleure .


17 commentaires

Donc, vous implémentez cela vous-même à la dure en utilisant int [] parce que vous supposez que la création d'objets est lente. Cette hypothèse est fausse, ou du moins non justifiée. De plus, un tableau est également un objet en Java, vous créez donc maintenant un ou plusieurs objets int [] . Au lieu de baser votre code sur des hypothèses, il est préférable de trouver des preuves que votre propre implémentation est plus rapide / plus efficace.


En fait, je ne pense pas qu'il existe un autre moyen que d'utiliser un type de données plus grand, jetez un œil à stackoverflow.com/questions / 849813 / large-numbers-in-java pour les chiffres décimaux, il y a BigDecimal, mais oui, je suppose, cela dégradera un peu les performances par rapport à long.


Vous pouvez utiliser numberOfLeadingZeros (long) et numberOfTrailingZeros (long) pour dire trivial de facile et difficile .


Un instrument spécifique pour une tâche MulDiv64 spécifique devrait fonctionner mieux qu'une solution générique avec des opérations inutiles. Exemple arbitraire


Je ne pense pas que ce soit un double. Le PO ne demande pas une solution polyvalente mais se concentre sur l'opération a * b / c sur les longs et une solution ad-hoc pourrait être meilleure.


Si le résultat est garanti pour tenir dans long alors essayez long result = ((a / c) * b) + (a% c) * b / c;


@Eritrean, cela semble beaucoup plus adapté à mon cas, mais je peux voir que le résultat pour a * b / c est différent du résultat pour b * a / c


@MagicKriss avez-vous remplacé a par b pour calculer b * a / c ? Signification b * a / c = ((b / c) * a) + (b% c) * a / c; Le résultat devrait être le même ou est-ce que je me trompe?


@Eritrean est-ce que l'expression (a% c) * b elle-même est garantie de tenir dans long ?


@meowgoesthedog Je pense que oui. Si a * b / c <= Long.MAX_VALUE alors (a% c) * b <= Long.MAX_VALUE depuis a% c <= c-1 . Ce n'est pas une preuve mathématique, juste un sentiment :-)


@Eritrean, je pense que vous pourriez transformer cela en réponse


@meowgoesthedog, ah, oui, le problème réside ici car nous avons à nouveau affaire à un débordement ... par exemple, si long b = Long.MAX_VALUE; alors n'importe quel (a% c)> 1 entraînera un débordement dans (a% c) * b


@Contre-exemple érythréen: let a = b = 2 ^ 33, c = 2 ^ 34 + 1 ; a% c = 2 ^ 33 donc (a% c) * b déborderait toujours, mais bien sûr le résultat attendu a * b / c = 2 ^ 31 ne le ferait pas.


@meowgoesthedog l'obtenir. Merci pour votre exemple. J'y réfléchirai et publierai une réponse si cela est résolu.


@MagicKriss, vous pouvez trouver ce message utile.


Ce Comment multiplier un entier 64 bits par une fraction est ce que je considérais comme un point de départ sûr.


[en supposant que FP-arithmétique 57 bits ish] résout le problème de précision [en (63 * 63) / 63 bits]. Est-ce que je manque quelque chose? Je pense que oui, sauf si vous divulguez les goûts de derniers 1… 3 bits n'ont pas besoin d'être corrects , une erreur inférieure à 1 ‰ est suffisante , c <2 ** 32 ou a × b <2 ** 65 .


6 Réponses :


-2
votes

Vous supposez ce qui suit:

(a * b) / c = (b / c) * a + (b % c) * a
  • Les 3 opérandes sont de type long
  • Le résultat est de type long
  • a * b peut être plus grand et ne pas convenir au type long

Parole mathématique:

(a * b) / c = (a / c) * b + (a % c) * b
  • a / c est sûrement de type long
  • b / c est sûrement de type long

Tant que votre hypothèse est correcte (le résultat est de type long), vous devez diviser le plus grand de (a) et (b) par (c) et faire la multiplication par la suite pour recevoir un résultat qui n'est pas plus grand le type long.

Mais:

Le type long ne contient pas de décimales. Par conséquent, nous devons également sauvegarder le reste de la division.

(a * b) / c = (a / c) * b = a * (b / c)

Nous supposons que (a% c) * b nous donne une valeur longue claire et non un double- valeur. Alternativement, nous pouvons utiliser:

long a,b,c;
long result = a*b/c;

Nous supposons que (b% c) * a ne contient pas de décimales.

Néanmoins, @Jesper a raison. Tant que vous ne prévoyez pas de faire ce calcul plusieurs millions de fois, vous devriez être d'accord avec les grands types existants.


7 commentaires

(En ignorant le fait qu'il s'agit d'une copie directe des commentaires de @Eritrean ) Nous supposons que (a% c) * b nous donne une valeur longue claire - pourrait prouvez-vous que nous pouvons ?


cela déborde de c = (1L << 63) -1 , a = c-1, b = a-1 , puisque b% c == b et b * a est trop grand.


Sinon, nous pouvons [diviser l'autre facteur] qu'en est-il du fractionnement du facteur avec une valeur absolue plus élevée?


(a * b) / c = (a / c) * b + (a% c) * b ne devrait pas lire (a * b) / c = (a / c ) * b + ((a% c) * b) / c ?


@meowgoesthedog 1.) Ce n'était pas une copie - je ne l'ai pas lu (j'ai écrit ma réponse pendant ses commentaires) et maintenant que je l'ai écrit, je pense que c'est faux comme la suggestion de Greybeard 2.) oui nous devons supposer parce que MagicKriss veut un résultat as long: résultat long = a * b / c; Si ce n'est pas le cas, alors son souhait n'est pas possible de produire.


@ barbe grise: (a * b) / c = (a / c) * b; car a / c nous met une partie devant la virgule et pour celle derrière nous devons la scinder: a / c + a% c; maintenant nous devons encore multiplier les deux parties avec b: (a / c) * b + (a% c) * b; le second / c comme vous l'avez suggéré n'est pas valide car il est déjà dans le calcul


J'ai revérifié et je ne pense toujours pas, voir Commentaire de l'Érythréen également. Ou essayez un exemple: 19 * 3/10 .



-1
votes

Divisez a / c et b / c en parties entières et fractionnaires (restantes), alors vous avez:

long a,b,c;
long bMod = (b % c)
long result = a * (b / c) + (a / c) * bMod + ((a % c) * bMod) / c;

En tant que tel, vous pouvez calculer trivialement les trois premiers facteurs sans débordement. D'après mon expérience, cela suffit souvent pour couvrir les cas de débordement typiques. Cependant, si votre diviseur est trop grand, de sorte que (a% c) * (b% c) déborde, cette méthode échoue toujours. Si c'est un problème typique pour vous, vous voudrez peut-être envisager d'autres approches (par exemple, diviser le plus grand de a et b ainsi que c par 2 jusqu'à ce que vous n'ayez plus de débordements, mais comment le faire sans introduire d'erreur supplémentaire en raison de les biais dans le processus ne sont pas triviaux - vous devrez probablement garder un score courant de l'erreur dans une variable séparée, probablement)

Quoi qu'il en soit, le code ci-dessus:

a*b/c 
= c * a/c * b/c 
= c * (x/c + y/c) * (z/c + w/c)
= xz/c + xw/c + yz/c + yw/c where x and z are multiples of c

Si la vitesse est un gros problème (je suppose que c'est au moins dans une certaine mesure, puisque vous demandez ceci), vous pouvez envisager de stocker a / c code> et b / c dans les variables et calcul du mod par multiplication, par exemple remplacez (a% c) par (a - aDiv * c) - cela vous permet de passer de 4 divisions par appel à 2.

p >


2 commentaires

n'aide pas si a et b


n'aide pas si 2³² ≤ c



1
votes

Vous pouvez utiliser le plus grand diviseur commun (pgcd) pour vous aider.

static long gcd(long a, long b) 
{ 
  if (b == 0) 
    return a; 
  return gcd(b, a % b);  
} 

Edit: L'OP m'a demandé d'expliquer l'équation ci-dessus. En gros, nous avons:

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

a*b/c = (a/x) * (b/y) 
        ------------
           (c/(xy))

Vous pouvez aller plus loin. Soit y = gcd (b, c / x)

a = (a / gcd(a,c)) * gcd(a,c)
c = (c / gcd(a,c)) * gcd(a,c)

Let's say x=gcd(a,c) for brevity, and rewrite this.

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

Next, we cancel

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

Voici le code pour obtenir le gcd.

a * b / c = (a / gcd(a,c)) * (b / (c / gcd(a,c)))


7 commentaires

Merci pour votre réponse. Bien que cela fonctionne la plupart du temps, il y a des cas où cela échoue. Par exemple. a = 13 , b = 2 et c = 12 (la réponse de cet algo est 0 , tandis que le la bonne est 2 ) Je commence à me demander s'il est même possible de ne pas déborder et toujours obtenir des réponses correctes tout le temps, car on perdrait en précision sans débordement.


Je me demande, que se passe-t-il si je modifie un peu cette réponse et que j'obtiens un résultat intermédiaire en double, comme ceci: a * b / c = ((double) a / gcd (a, c)) * (b / (( double) c / gcd (a, c))) Cela fonctionnerait-il? Ou y a-t-il des erreurs sous-jacentes que je ne vois pas?


@MagicKriss Je supposais que a * b est divisible par c. Si ce n'est pas le cas, cela donnera parfois une réponse différente en raison de l'arrondissement.


Je suis curions, comment êtes-vous passé de a * b / c à (a / gcd (a, c)) * (b / (c / gcd (a, c))) ? Pourriez-vous montrer les étapes intermédiaires?


Je me suis trompé dans mes hypothèses, le code avec GDC serait erroné même dans certains cas où il n'y a pas du tout de débordement (même avec une conversion en double )


@MagicKriss Il donne la mauvaise réponse lorsque vous arrondissez en raison d'une division entière à cause de l'ordre dans lequel nous faisons les choses. Si vous calculez le numérateur complet puis divisez (voir les équations ci-dessus), cela devrait toujours donner la bonne réponse, mais dans ce cas, vous risquez un débordement.


Voir comment b / (c / gcd (a, c)) provoque de mauvaises réponses, en utilisant la forme compacte (a / x) * (b / y) / (c / (x) * y)) de votre étape plus loin semble prudent. N'empêche pas le débordement des GCD particulièrement petits ou grands. Oh, attendez, (a / x) * (b / y) / ((c / x) / y) (simplifiez autant que possible, mais…) ne devrait pas souffrir de gros GCD, laissant "le problème du coprime" (non résolu, c'est-à-dire).



3
votes

J'ai bricolé une approche qui (1) multiplie a et b avec l'algorithme scolaire sur les membres 21 bits (2) procède à une division longue par c , avec une représentation inhabituelle du résidu a * b - c * q qui utilise un double pour stocker les bits de poids fort et un long pour stocker les bits de poids faible. Je ne sais pas si cela peut être fait pour être compétitif avec la division longue standard, mais pour votre plaisir,

public class MulDiv {
  public static void main(String[] args) {
    java.util.Random r = new java.util.Random();
    for (long i = 0; true; i++) {
      if (i % 1000000 == 0) {
        System.err.println(i);
      }
      long a = r.nextLong() >> (r.nextInt(8) * 8);
      long b = r.nextLong() >> (r.nextInt(8) * 8);
      long c = r.nextLong() >> (r.nextInt(8) * 8);
      if (c == 0) {
        continue;
      }
      long x = mulDiv(a, b, c);
      java.math.BigInteger aa = java.math.BigInteger.valueOf(a);
      java.math.BigInteger bb = java.math.BigInteger.valueOf(b);
      java.math.BigInteger cc = java.math.BigInteger.valueOf(c);
      java.math.BigInteger xx = aa.multiply(bb).divide(cc);
      if (java.math.BigInteger.valueOf(xx.longValue()).equals(xx) && x != xx.longValue()) {
        System.out.printf("a=%d b=%d c=%d: %d != %s\n", a, b, c, x, xx);
      }
    }
  }

  // Returns truncate(a b/c), subject to the precondition that the result is
  // defined and can be represented as a long.
  private static long mulDiv(long a, long b, long c) {
    // Decompose a.
    long a2 = a >> 42;
    long a10 = a - (a2 << 42);
    long a1 = a10 >> 21;
    long a0 = a10 - (a1 << 21);
    assert a == (((a2 << 21) + a1) << 21) + a0;
    // Decompose b.
    long b2 = b >> 42;
    long b10 = b - (b2 << 42);
    long b1 = b10 >> 21;
    long b0 = b10 - (b1 << 21);
    assert b == (((b2 << 21) + b1) << 21) + b0;
    // Compute a b.
    long ab4 = a2 * b2;
    long ab3 = a2 * b1 + a1 * b2;
    long ab2 = a2 * b0 + a1 * b1 + a0 * b2;
    long ab1 = a1 * b0 + a0 * b1;
    long ab0 = a0 * b0;
    // Compute a b/c.
    DivBy d = new DivBy(c);
    d.shift21Add(ab4);
    d.shift21Add(ab3);
    d.shift21Add(ab2);
    d.shift21Add(ab1);
    d.shift21Add(ab0);
    return d.getQuotient();
  }
}

public strictfp class DivBy {
  // Initializes n <- 0.
  public DivBy(long d) {
    di = d;
    df = (double) d;
    oneOverD = 1.0 / df;
  }

  // Updates n <- 2^21 n + i. Assumes |i| <= 3 (2^42).
  public void shift21Add(long i) {
    // Update the quotient and remainder.
    q <<= 21;
    ri = (ri << 21) + i;
    rf = rf * (double) (1 << 21) + (double) i;
    reduce();
  }

  // Returns truncate(n/d).
  public long getQuotient() {
    while (rf != (double) ri) {
      reduce();
    }
    // Round toward zero.
    if (q > 0) {
      if ((di > 0 && ri < 0) || (di < 0 && ri > 0)) {
        return q - 1;
      }
    } else if (q < 0) {
      if ((di > 0 && ri > 0) || (di < 0 && ri < 0)) {
        return q + 1;
      }
    }
    return q;
  }

  private void reduce() {
    // x is approximately r/d.
    long x = Math.round(rf * oneOverD);
    q += x;
    ri -= di * x;
    rf = repairLowOrderBits(rf - df * (double) x, ri);
  }

  private static double repairLowOrderBits(double f, long i) {
    int e = Math.getExponent(f);
    if (e < 64) {
      return (double) i;
    }
    long rawBits = Double.doubleToRawLongBits(f);
    long lowOrderBits = (rawBits >> 63) ^ (rawBits << (e - 52));
    return f + (double) (i - lowOrderBits);
  }

  private final long di;
  private final double df;
  private final double oneOverD;
  private long q = 0;
  private long ri = 0;
  private double rf = 0;
}


3 commentaires

J'ai bricolé des membres de bits 31,5 - non compatibles avec DivBy.reduce () , j'ai une idée de pourquoi (31,5 bits: base ceil (Math. sqrt (Long.MAX_VALUE)) . Soyez distrait en codant "l'intelligence"…


@greybeard C'est frustrant, n'est-ce pas? Entre long et double, nous pouvons obtenir un produit 64 + ~ 50 bits de quantités 64 bits, ce qui n'est pas tout à fait suffisant ici.


Frustrant en effet: l'essentiel semble être que dans le pire des cas, vous devez implémenter 64 × 64 → 128 bits et 128/64 bits . La seule promesse d'un effort réduit (par rapport à, .g., BigInteger ) est que vous pouvez caser ces quelques membres en particulier et éviter les boucles. Java 1.8 a développé un long divideUnsigned (long dividende, long divisor) et assortis de stayder () - Je suis tout sauf tordu sur le codage 128/64 bits peu importe.



1
votes

David Eisenstat m'a fait réfléchir un peu plus.
Je veux que les cas simples soient rapides: laissez doubler s'occuper de cela. Newton-Raphson peut être un meilleur choix pour le reste.

 /** Multiplies both <code>factor</code>s
  *  and divides by <code>divisor</code>.
  * @return <code>Long.MIN_VALUE</code> if result out of range,<br/>
  *     else <code>factorA * factor1 / divisor</code> */
    public static long
    mulDiv(long factorA, long factor1, long divisor) {
        final double dd = divisor,
            product = (double)factorA * factor1,
            a1_d = product / dd;
        if (a1_d < -TOO_LARGE || TOO_LARGE < a1_d)
            return tooLarge();
        if (-ONE_ < a1_d && a1_d < ONE_)
            return 0;
        if (-EXACT < product && product < EXACT)
            return (long) a1_d;
        long pLo = factorA * factor1, //diff,
            pHi = high64(factorA, factor1);
        if (a1_d < -LONG_MAX_ || LONG_MAX_ < a1_d) {
            long maxdHi = divisor >> 1;
            if (maxdHi < pHi
                || maxdHi == pHi
                   && Long.compareUnsigned((divisor << Long.SIZE-1),
                                           pLo) <= 0)
                return tooLarge();
        }
        final double high_dd = TWO_POWER64/dd;
        long quotient = (long) a1_d,
            loPP = quotient * divisor,
            hiPP = high64(quotient, divisor);
        long remHi = pHi - hiPP, // xxx overflow/carry
            remLo = pLo - loPP;
        if (Long.compareUnsigned(pLo, remLo) < 0)
            remHi -= 1;
        double fudge = remHi * high_dd;
        if (remLo < 0)
            fudge += high_dd;
        fudge += remLo/dd;
        long //fHi = (long)fudge/TWO_POWER64,
            fLo = (long) Math.floor(fudge); //*round
        quotient += fLo;
        loPP = quotient * divisor;
        hiPP = high64(quotient, divisor);
        remHi = pHi - hiPP; // should be 0?!
        remLo = pLo - loPP;
        if (Long.compareUnsigned(pLo, remLo) < 0)
            remHi -= 1;
        if (0 == remHi && 0 <= remLo && remLo < divisor)
            return quotient;

        fudge = remHi * high_dd;
        if (remLo < 0)
            fudge += high_dd;
        fudge += remLo/dd;
        fLo = (long) Math.floor(fudge);
        return quotient + fLo;
    }

 /** max <code>double</code> trusted to represent
  *  a value in the range of <code>long</code> */
    static final double
        LONG_MAX_ = Double.valueOf(Long.MAX_VALUE - 0xFFF);
 /** max <code>double</code> trusted to represent a value below 1 */
    static final double
        ONE_ = Double.longBitsToDouble(
                    Double.doubleToRawLongBits(1) - 4);
 /** max <code>double</code> trusted to represent a value exactly */
    static final double
        EXACT = Long.MAX_VALUE >> 12;
    static final double
        TWO_POWER64 = Double.valueOf(1L<<32)*Double.valueOf(1L<<32);

    static long tooLarge() {
//      throw new RuntimeException("result too large for long");
        return Long.MIN_VALUE;
    }
    static final long   ONES_32 = ~(~0L << 32);

    static long high64(long factorA, long factor1) {
        long loA = factorA & ONES_32,
            hiA = factorA >>> 32,
            lo1 = factor1 & ONES_32,
            hi1 = factor1 >>> 32;
        return ((loA * lo1 >>> 32)
                +loA * hi1 + hiA * lo1 >>> 32)
               + hiA * hi1;
    }

(J'ai réorganisé ce code une partie de l'EDI pour avoir mulDiv () en haut. Étant paresseux, j'ai un wrapper pour la gestion des signes - je pourrais essayer de le faire correctement avant que l'enfer ne gèle.
Pour le timing, un modèle d'entrée est absolument nécessaire:
Que diriez-vous de telle sorte que chaque résultat possible soit également probable ?)


1 commentaires

À partir de Java 9, high64 () doit être remplacé par Math.multiplyHigh (long, long) . (J'ai essayé de mettre à jour mon IDE à 11: j'ai parcouru les pages à la fin de legalse (89 p. Manuel de licence? Kid me.). Je n'ai pas (encore) fait accepter à Eclipse JDT l'openJDK…)



0
votes

Peut-être pas intelligent, mais a un temps de résultat linéaire

#define MUL_DIV_TYPE    unsigned int
#define BITS_PER_TYPE   (sizeof(MUL_DIV_TYPE)*8)
#define TOP_BIT_TYPE    (1<<(BITS_PER_TYPE-1))

//
//    result = ( a * b ) / c, without intermediate overflow.
//
MUL_DIV_TYPE mul_div( MUL_DIV_TYPE a, MUL_DIV_TYPE b, MUL_DIV_TYPE c ) {
    MUL_DIV_TYPE    st, sb;     // product sum top and bottom

    MUL_DIV_TYPE    d, e;       // division result

    MUL_DIV_TYPE    i,      // bit counter
            j;      // overflow check

    st = 0;
    sb = 0;

    d = 0;
    e = 0;

    for( i = 0; i < BITS_PER_TYPE; i++ ) {
        //
        //  Shift sum left to make space
        //  for next partial sum
        //
        st <<= 1;
        if( sb & TOP_BIT_TYPE ) st |= 1;
        sb <<= 1;
        //
        //  Add a to s if top bit on b
        //  is set.
        //
        if( b & TOP_BIT_TYPE ) {
            j = sb;
            sb += a;
            if( sb < j ) st++;
        }
        //
        //  Division.
        //
        d <<= 1;
        if( st >= c ) {
            d |= 1;
            st -= c;
            e++;
        }
        else {
            if( e ) e++;
        }
        //
        //  Shift b up by one bit.
        //
        b <<= 1;
    }
    //
    //  Roll in missing bits.
    //
    for( i = e; i < BITS_PER_TYPE; i++ ) {
        //
        //  Shift across product sum
        //
        st <<= 1;
        if( sb & TOP_BIT_TYPE ) st |= 1;
        sb <<= 1;
        //
        //  Division, continued.
        //
        d <<= 1;
        if( st >= c ) {
            d |= 1;
            st -= c;
        }
    }
    return( d );  // remainder should be in st
}


0 commentaires