İfadede taşma nasıl önlenir. A * B - C * D


161

Ben A*B - C*Donların türü : gibi görünüyor bir ifade hesaplamak gerekir : signed long long int A, B, C, D; Her sayı (tür taşan değil) gerçekten büyük olabilir. Taşmaya A*Bneden olabilirken, aynı zamanda ifade A*B - C*Dgerçekten küçük olabilir. Nasıl doğru hesaplayabilirim?

Örneğin:, MAX * MAX - (MAX - 1) * (MAX + 1) == 1nerede MAX = LLONG_MAX - nve n - bazı doğal sayılar.


17
Doğruluk ne kadar önemlidir?
Anirudh Ramanathan

1
@Cthulhu, harika bir soru. Hepsini 10'a veya başka bir şeye böldükten sonra sonucu çoğaltarak daha küçük bir sayı kullanarak eşdeğer bir işlev yapmaya çalışabilirdi.
Chris

4
Vars A, B, C, D imzalanır. Bu A - Ctaşabilir anlamına gelir . Dikkate alınması gereken bir konu ya da bunun verilerinizde olmayacağını biliyor musunuz?
William Morris

2
@MooingDuck, ancak işlemin taşma olup olmadığını önceden kontrol edebilirsiniz stackoverflow.com/a/3224630/158285
bradgonesurfing

1
@Chris: Hayır, imzalı bir taşma olup olmadığını kontrol etmenin taşınabilir bir yolu olmadığını söylüyorum. (Brad Eğer portably o algılayabilir bu doğru olacaktır gerçekleşmesi). Satır içi montajı kullanmak, kontrol etmenin birçok taşınabilir olmayan yolundan biridir.
Mooing Duck

Yanıtlar:


120

Sanırım bu çok önemsiz görünüyor. Ama A*Btaşabilecek olan.

Hassasiyeti kaybetmeden aşağıdakileri yapabilirsiniz

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)

Bu ayrışma ayrıca yapılabilir .
@Gian'ın belirttiği gibi, tür uzun süre imzasız ise çıkarma işlemi sırasında dikkatli olunması gerekebilir.


Örneğin, soruda bulunduğunuz durumda, yalnızca bir yineleme gerekir,

 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

4
@Caleb, sadece aynı algoritmayı uygulayınC*D
Chris

2
Bence E'nin neyi temsil ettiğini açıklamalısın.
Caleb

7
Hem uzun hem de çift 64 bittir. Çifte üs için bazı bitler ayırması gerektiğinden, hassasiyet kaybı olmadan daha küçük olası değerler aralığına sahiptir .
Jim Garrison

3
@Cthulhu - bana öyle geliyor ki bu sadece tüm rakamlar çok büyükse işe yarayacaktır ... örneğin, hala {A, B, C, D} = {MAX, MAX, MAX, 2} ile taşacaksınız. OP "Her sayı gerçekten büyük olabilir" der, ancak sorun ifadesinden her sayının gerçekten büyük olması gerektiği açık değildir .
Kevin K

4
Ya bunlardan herhangi biri A,B,C,Dnegatifse? Olmaz Eya Fsonra daha da büyük olacak?
Supr

68

En basit ve en genel çözüm, uzun bir tamsayı kütüphanesi (örn. Http://gmplib.org/ ) kullanarak taşan bir temsili kullanmak veya bir yapı veya dizi kullanarak temsil etmek ve bir tür uzun çarpma ( yani her sayıyı iki 32 bit yarıya ayırmak ve çarpma işlemini aşağıdaki gibi yapmak:

(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

Nihai sonucun 64 bite uyduğunu varsayarsak, R3'ün çoğu bitine ve R4'ün hiçbirine gerçekten ihtiyacınız yoktur


8
Yukarıdaki hesaplama gerçekten göründüğü kadar karmaşık değil, taban 2 ^ 32'de basit uzun çarpma ve C'deki kod daha basit görünmelidir. Ayrıca, programınızda bu işi yapmak için genel işlevler oluşturmak iyi bir fikir olacaktır.
Ofir

46

Sarma imzalı taşmaya bağlı olduğu için bunun standart olmadığını unutmayın. (GCC'nin bunu sağlayan derleyici bayrakları vardır.)

Ancak, sadece tüm hesaplamaları yaparsanız long long, formülü doğrudan uygulamanın sonucu:
(A * B - C * D)doğru sonuç a'ya uyduğu sürece doğru olacaktır long long.


Burada, yalnızca imzasız tamsayıyı işaretli tamsayıya dönüştürmenin uygulama tanımlı davranışına dayanan bir çözüm vardır. Ancak bunun bugün hemen hemen her sistem üzerinde çalışması beklenebilir.

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

Bu unsigned long long, taşma davranışının standart tarafından sarıldığı garanti edilen girdileri verir . Sonunda imzalı bir tam sayıya geri dönme, uygulama tarafından tanımlanan bölümdür, ancak bugün neredeyse tüm ortamlarda çalışacaktır.


Eğer daha bilgiç bir çözüm gerekiyorsa, bence "uzun aritmetik"


+1 Bunu fark eden tek kişi sensin. Zor olan tek nokta, derleyiciyi etrafa saran taşma yapacak şekilde ayarlamak ve doğru sonucun gerçekten a'ya uyup uymadığını kontrol etmektir long long.
Mysticial

2
Hiç hilesi olmayan naif versiyon bile çoğu uygulamada doğru şeyi yapacak ; standart tarafından garanti edilmez, ancak başarısız olması için 1'in tamamlayıcı bir makine veya başka oldukça garip bir cihaz bulmanız gerekir.
Ocak

1
Bunun önemli bir cevap olduğunu düşünüyorum. Uygulamaya özgü davranışı üstlenmenin doğru programlama olmayabileceğini kabul ediyorum, ancak her mühendis modulo aritmetiği ve performans gerekliyse tutarlı davranışı sağlamak için doğru derleyici bayraklarını nasıl edineceğini anlamalıdır. DSP mühendisleri, kabul edilen cevabın kabul edilemez performansa sahip olacağı sabit nokta filtre uygulamaları için bu davranışa güvenmektedir.
Peter M

18

Bu işe yaramalı (sanırım):

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

İşte benim türetim:

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)

1
Teşekkürler @bradgonesurfing - böyle bir girdi sağlayabilir misiniz? Cevabımı güncelledim,
yürüttüm

1
Hmmm. Şimdi düşünüyorum belki değil. D = 1 ve a = 1 ve b = maxint ve c = maxint ile dejenere vaka hala çalışıyor. Serin :)
bradgonesurfing

1
@paquetp: a = 1, b = 0x7fffffffffffffff, c = -0x7fffffffffffffff, d = 1 (not c negatif). Akıllı olsa da, kodunuzun tüm pozitif sayıları doğru bir şekilde işlediğinden eminim.
Mooing Duck

3
@MooingDuck ama setiniz için son cevap da taştı, bu yüzden geçerli bir kurulum değil. Yalnızca her iki tarafın da aynı işarete sahip olması durumunda çalışır, böylece ortaya çıkan çıkarma menzil dahilindedir.
bradgonesurfing

1
En basit ve en iyi olan bu cevap en yüksek puanlı cevaba kıyasla bu kadar düşük bir puan aldığında StackOverflow ile garip bir şey var.
bradgonesurfing

9

Tüm değerleriniz için en büyük ortak faktörü hesaplamayı ve ardından aritmetik işlemlerinizi yapmadan önce bunları bu faktöre bölmeyi ve sonra tekrar çarpmayı düşünebilirsiniz. Bu tür bir faktör ancak varolduğunu varsayar (eğer örneğin A, B, Cve Daralarında asal olmak olur, bunlar ortak bir faktör olmaz).

Benzer şekilde, log-skalalar üzerinde çalışmayı düşünebilirsiniz, ancak bu, sayısal hassasiyete bağlı olarak biraz korkutucu olacaktır.


1
Logaritma varsa iyi görünüyor long double. Bu durumda, kabul edilebilir bir hassasiyet seviyesi elde edilebilir (ve sonuç yuvarlanabilir).

9

Sonuç uzun bir uzunluğa uyuyorsa, 2 * 64 aritmetik modunu gerçekleştirdiği ve doğru sonucu vereceği için A * BC * D ifadesi uygundur. Sorun, sonucun uzun bir uzun int'e uyup uymadığını bilmek. Bunu tespit etmek için, çiftleri kullanarak aşağıdaki numarayı kullanabilirsiniz:

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

Bu yaklaşımdaki sorun, çiftlerin mantisinin doğruluğu ile sınırlı olmanızdır (54 bit?), Bu nedenle A * B ve C * D ürünlerini 63 + 54 bit (veya muhtemelen biraz daha az) ile sınırlamanız gerekir.


Bu en pratik örnektir. Temizle ve doğru cevabı verir (veya girişler kötü olduğunda bir İstisna atar).
Mark Lakata

1
Güzel ve zarif! Diğerlerinin düştüğü tuzağa düşmedin. Sadece bir şey daha: Bahse girerim çift hesaplama sadece yuvarlama hataları nedeniyle MAX_LLONG altında bazı örnekler vardır. Matematiksel içgüdüm, bunun yerine çift ve uzun sonucun farkını hesaplamanız gerektiğini ve bunu MAX_LLONG / 2 veya başka bir şeyle karşılaştırmanız gerektiğini söylüyor. Bu fark, çift hesaplamanın yuvarlama hataları ve artı taşmadır ve normalde nispeten düşük olmalıdır, ancak bahsettiğim durumda büyük olacaktır. Ama şu anda emin olamayacağım kadar tembelim. :-)
Hans-Peter Störr

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

sonra

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

7

Her sayıyı bir diziye yazabilirsiniz, her eleman bir rakamdır ve hesaplamaları polinom olarak yapabilirsiniz . Bir dizi olan elde edilen polinomu alın ve dizinin her bir elemanını 10 ile dizideki konumun gücüne çarparak sonucu hesaplayın (ilk konum en büyük ve son konum sıfırdır).

Sayı 123şu şekilde ifade edilebilir:

123 = 100 * 1 + 10 * 2 + 3

bunun için bir dizi oluşturursunuz [1 2 3].

Bunu tüm A, B, C ve D sayıları için yaparsınız ve sonra bunları polinom olarak çarparsınız. Elde edilen polinomu elde ettikten sonra, sayıyı ondan yeniden yapılandırırsınız.


2
bunun ne olduğunu bilmiyorum ama bulmak zorundayım. koymak :) . kız arkadaşımla alışveriş yaparken bu başımın tepesi bir çözüm :)
Mihai

base10 dizisinde bignum uyguluyorsunuz. GMP, temel 4294967296 kullanan kaliteli bir bignum kütüphanesidir. ÇOK daha hızlı. Olumlu oy yok, çünkü cevap doğru ve kullanışlı.
Mooing Duck

Teşekkürler :) . Bunu yapmanın bir yolunu bilmek faydalıdır, ancak daha iyi yollar vardır, bu yüzden böyle yapma. en azından bu durumda değil :)
Mihai

her neyse ... bu çözümü kullanarak, herhangi bir ilkel türün (100 basamaklı sayı gibi) koyulabileceğinden çok daha büyük bir sayı yapabilir ve sonucu bir dizi olarak tutabilirsiniz. Bu bir yukarı oy hak ediyor: p
Mihai

Bu yöntem (etkili ve anlaşılması kolay olsa da) bellek aç ve yavaş olduğu için bir oylama aldığından emin değilim.
Mooing Duck

6

Bir signed long long intirade tutmazken A*B, ikisi tutmayacak . Bu nedenle A*B, herhangi bir tanesine uyan farklı üssün ağaç terimlerine ayrıştırılabilir 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;

Aynı C*D.

Düz yoldan bakıldığında, çıkarma her bir çift için AB_ive her biri CD_iiçin ek bir taşıma biti (doğru olarak 1 bit tam sayı) kullanılarak yapılabilir. E = A * BC * D dersek şöyle bir şey elde edersiniz:

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 
...

Biz üst yarısını aktararak devam E_10etmek E_20(o üst yarısını silmek 32 tarafından kayması ve ekleyin E_10).

Artık taşıma bitinden E_11doğru işaretiyle (taşımayan kısımdan elde edilen) ekleyerek taşıma bitinden kurtulabilirsiniz E_20. Bu bir taşmayı tetiklerse, sonuç da sığmaz.

E_10artık üst yarıyı E_00 (kaydırma, ekleme, silme) ve taşıma bitini alacak kadar 'boşluk' var E_01.

E_10şimdi daha büyük olabilir, bu yüzden aktarma işlemini tekrarlıyoruz E_20.

Bu noktada, E_20sıfır olmalıdır, aksi takdirde sonuç uymaz. Üst yarısıE_10Transfer sonucu da boş.

Son adım, alt yarısına transfer etmektir E_20içineE_10 tekrar .

E=A*B+C*DBekletmelere uygun beklenti, signed long long intşimdi

E_20=0
E_10=0
E_00=E

1
Bu aslında Ofir'in çarpım formülünü kullanır ve her geçici sonucu kaldırırsa alacağı basitleştirilmiş formüldür.
dronus

3

Eğer nihai sonuç, tamsayı türü sunulabilen biliyorum, hızlı aşağıdaki kodu kullanarak bu hesaplamayı yapabilir. C standardı işaretsiz aritmetiğin modulo aritmetiği olduğunu ve taşmadığını belirttiğinden, hesaplamayı gerçekleştirmek için işaretsiz bir tür kullanabilirsiniz.

Aşağıdaki kod, aynı genişlikte imzasız bir tür olduğunu ve imzalı türün değerleri temsil etmek için tüm bit kalıplarını kullandığını varsayar (bindirme temsili yok, işaretli türün minimum değeri, imzasız türün modülünün yarısının negatifidir). Bu, bir C uygulamasında yer almıyorsa, bunun için ConvertToSigned yordamında basit ayarlamalar yapılabilir.

Aşağıdaki signed charve unsigned charkodu göstermek için kullanır . Uygulamanız Signediçin typedef signed long long int Signed;ve tanımını ve tanımını Unsigneddeğiştirin 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;
}

2

Denklemi taşmayan küçük bileşenlere bölmeyi deneyebilirsiniz.

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

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

    where K = B - N
          J = D - M

Bileşenler hala taşarsa, bunları tekrarlayan küçük parçalara bölebilir ve sonra yeniden birleştirebilirsiniz.


Bu doğru olabilir veya olmayabilir, ancak kesinlikle kafa karıştırıcıdır. Sen tanımlamak Kve J, neden olmasın Nve M. Ayrıca, denklemi daha büyük parçalara ayırdığınızı düşünüyorum . 3. adımınız OP'nin sorusuyla aynı olduğundan, daha karmaşık olanlar hariç (AK-CJ)->(AB-CD)
Mooing Duck

N hiçbir şeyden basitleştirilmemiştir. Daha küçük yapmak için A'dan çıkarılan bir sayı. Aslında, paquetp'e benzer ama daha düşük bir çözümdür. Burada küçültmek için tamsayı bölme yerine çıkarma kullanıyorum.
bradgonesurfing

2

Tüm kenar vakalarını kapsamamış olabilirim veya bunu titizlikle test etmedim, ancak bu, 16 bitlik bir CPU'da 32 bit tam sayı matematik yapmaya çalışırken 80'lerde kullandığım bir tekniği uyguluyor. Esasen 32 biti iki 16 bitlik birime böler ve onlarla ayrı ayrı çalışırsınız.

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

  }

}

Baskılar:

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

Bana çalışıyormuş gibi geliyor.

Bahse girerim işaret taşması gibi bazı incelikleri kaçırdım ama özüm orada olduğunu düşünüyorum.


1
Bence bu @Ofir'in önerdiği bir uygulama.
OldCurmudgeon

2

Tamlık uğruna, hiç kimse bundan bahsetmediği için, bazı derleyiciler (örn. GCC) aslında bugünlerde 128 bit tam sayı sağlar.

Böylece kolay bir çözüm şöyle olabilir:

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

1

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Ne taşabilir B/Cne D/Ade taşamaz, bu yüzden (B/C-D/A)önce hesaplayın . Nihai sonuç tanımınıza göre taşmayacağından, kalan çarpmaları güvenle gerçekleştirebilir (B/C-D/A)*A*Cve gerekli sonucun hangisi olduğunu hesaplayabilirsiniz .

Girişiniz de çok küçükse , B/Cveya D/Ataşabilir. Mümkünse, giriş denetimine göre daha karmaşık manipülasyonlar gerekebilir.


2
Tamsayı bölümü bilgi kaybettiğinden (sonucun bir kısmı) işe yaramaz
Ofir

@ Doğru bu, ancak pastayı yiyip el değmeden bırakamazsınız. Kesin bir şekilde veya ek kaynaklar kullanarak (cevabınızda önerdiğiniz gibi) ödeme yapmanız gerekir. Benim cevabım matematiksel nitelikte, sizinkiler ise bilgisayar odaklı. Her biri koşullara bağlı olarak doğru olabilir.
SomeWittyUsername

2
Haklısın - olarak ifade etmeliydim - matematik doğru olduğu için işe yaramaz yerine kesin bir sonuç vermeyecek. Bununla birlikte, soru göndericiyi ilgilendirmesi muhtemel durumlarda (örneğin, sorudaki örnekte), hata muhtemelen şaşırtıcı derecede büyük olacaktır - herhangi bir pratik uygulama için kabul edilenden çok daha büyük olacaktır. Her durumda - bu anlayışlı bir cevaptı ve bu dili kullanmamalıydım.
Ofir

@Ofir Dilinizin uygunsuz olduğunu düşünmüyorum. OP açık bir şekilde “doğru” bir hesaplama talep etti, aşırı kaynak kısıtlamaları altında gerçekleştirilmek uğruna kesinliği kaybetmeyecek bir hesaplama değil.
user4815162342

1

Seçin K = a big number(örn. K = A - sqrt(A))

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

Neden?

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

Not A, B, C ve D bu nedenle, büyük sayı olduğu için bu A-Cve B-Dküçük sayılardır.


Pratikte K'yi nasıl seçersiniz ? Ayrıca, K * (A-C + BD) yine de taşabilir.
ylc

@ylc: K = sqrt (A) öğesini seçin, bu A-C+B-Dküçük bir sayı değil. A, B, C ve D büyük sayı olduğu için AC küçük sayıdır.
Amir Saniyan

K = sqrt (A) seçerseniz , (AK) * (BK) tekrar taşabilir.
ylc

@ylc: Tamam! Ben değiştirmek A - sqrt(A):)
Amir Saniyan

Sonra K * (A-C + BD) taşabilir.
ylc
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.