Bagaimana menghindari kelebihan dalam expr. A * B - C * D


161

Saya perlu menghitung ekspresi yang terlihat seperti:, di A*B - C*Dmana tipenya: signed long long int A, B, C, D; Setiap angka bisa sangat besar (tidak meluap tipenya). Walaupun A*Bbisa menyebabkan overflow, pada saat yang sama ekspresi A*B - C*Dbisa sangat kecil. Bagaimana saya bisa menghitungnya dengan benar?

Sebagai contoh:, di MAX * MAX - (MAX - 1) * (MAX + 1) == 1mana MAX = LLONG_MAX - ndan n - beberapa bilangan alami.


17
Seberapa penting akurasi?
Anirudh Ramanathan

1
@ Cthulhu, pertanyaan bagus. Dia bisa mencoba membuat fungsi yang setara menggunakan angka yang lebih kecil dengan membagi semuanya dengan 10 atau sesuatu, kemudian mengalikan hasilnya.
Chris

4
Vars A, B, C, D ditandatangani. Ini berarti A - Cbisa meluap. Apakah ini masalah untuk dipertimbangkan atau Anda tahu bahwa ini tidak akan terjadi dengan data Anda?
William Morris

2
@ MoooDuck, tetapi Anda dapat memeriksa sebelumnya apakah operasi akan meluap stackoverflow.com/a/3224630/158285
bradgonesurfing

1
@ Chris: Tidak, saya katakan bahwa tidak ada cara portabel untuk memeriksa apakah ada luapan yang masuk telah terjadi. (Brad benar bahwa Anda dapat dengan mudah mendeteksi bahwa itu akan terjadi). Menggunakan perakitan inline adalah salah satu dari banyak cara non-portabel untuk memeriksa.
Mooing Duck

Jawaban:


120

Sepertinya ini terlalu sepele. Tetapi A*Bapakah itu yang bisa meluap.

Anda bisa melakukan yang berikut, tanpa kehilangan presisi

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)

Dekomposisi ini dapat dilakukan lebih lanjut .
Seperti yang ditunjukkan @Gian, kehati-hatian mungkin perlu dilakukan selama operasi pengurangan jika jenisnya tidak ditandai lama.


Misalnya, dengan kasus yang Anda miliki dalam pertanyaan, hanya perlu satu iterasi,

 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, cukup terapkan algoritma yang sama untukC*D
Chris

2
Saya pikir Anda harus menjelaskan apa yang E wakili.
Caleb

7
Baik panjang dan ganda adalah 64 bit. Sejak ganda harus mengalokasikan beberapa bit untuk eksponen, ia memiliki lebih kecil rentang nilai yang mungkin tanpa kehilangan presisi.
Jim Garrison

3
@ Cthulhu - bagi saya sepertinya ini hanya akan berfungsi jika semua angkanya sangat besar ... misalnya, Anda masih akan mengalami overflow dengan {A, B, C, D} = {MAX, MAX, MAX, 2}. OP mengatakan "Setiap angka bisa sangat besar", tetapi tidak jelas dari pernyataan masalah bahwa setiap angka harus sangat besar.
Kevin K

4
Bagaimana jika ada A,B,C,Dyang negatif? Bukankah Eatau Fakan lebih besar?
Supr

68

Solusi paling sederhana dan paling umum adalah dengan menggunakan representasi yang tidak bisa meluap, baik dengan menggunakan perpustakaan integer panjang (misalnya http://gmplib.org/ ) atau mewakili menggunakan struct atau array dan menerapkan semacam perkalian panjang ( yaitu memisahkan setiap angka menjadi dua bagian 32bit dan melakukan perkalian seperti di bawah ini:

(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

Dengan asumsi hasil akhirnya cocok dalam 64 bit Anda sebenarnya tidak benar-benar membutuhkan sebagian besar bit R3 dan tidak ada R4


8
Perhitungan di atas benar-benar tidak serumit kelihatannya, itu benar-benar perkalian panjang sederhana dalam basis 2 ^ 32, dan kode dalam C akan terlihat lebih sederhana. Juga, itu akan menjadi ide yang baik untuk membuat fungsi generik untuk melakukan pekerjaan ini di program Anda.
Ofir

46

Perhatikan bahwa ini bukan standar karena bergantung pada wrap-around signed-overflow. (GCC memiliki flag compiler yang memungkinkan ini.)

Tetapi jika Anda hanya melakukan semua perhitungan long long, hasil penerapan rumus secara langsung:
(A * B - C * D)akan akurat selama hasil yang benar sesuai dengan a long long.


Berikut ini penyelesaian yang hanya bergantung pada perilaku yang ditentukan implementasi dari casting integer yang tidak ditandatangani ke integer yang ditandatangani. Tetapi ini dapat diharapkan bekerja pada hampir setiap sistem saat ini.

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

Ini melemparkan input ke unsigned long longtempat perilaku overflow dijamin akan dibungkus oleh standar. Mengembalikan ke integer yang ditandatangani di bagian akhir adalah bagian yang ditentukan implementasi, tetapi akan bekerja di hampir semua lingkungan saat ini.


Jika Anda membutuhkan solusi yang lebih bertele-tele, saya pikir Anda harus menggunakan "aritmatika panjang"


+1 Anda satu-satunya yang memperhatikan hal ini. Satu-satunya bagian yang sulit adalah mengatur compiler untuk melakukan wrap-around overflow dan memeriksa apakah hasil yang benar sesuai dengan a long long.
Mysticial

2
Bahkan versi naif tanpa trik sama sekali akan melakukan hal yang benar pada sebagian besar implementasi; itu tidak dijamin oleh standar, tetapi Anda harus menemukan mesin komplemen 1's atau perangkat lain yang cukup aneh untuk membuatnya gagal.
hobbs

1
Saya pikir ini adalah jawaban yang penting. Saya setuju itu mungkin bukan pemrograman yang benar untuk mengasumsikan perilaku implementasi spesifik, tetapi setiap insinyur harus memahami modulo aritmatika dan bagaimana mendapatkan flag compiler yang tepat untuk memastikan perilaku yang konsisten jika kinerja sangat penting. Insinyur DSP mengandalkan perilaku ini untuk implementasi filter titik tetap, yang jawabannya diterima akan memiliki kinerja yang tidak dapat diterima.
Peter M

18

Ini seharusnya bekerja (saya pikir):

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

Inilah derivasi saya:

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
Terima kasih @bradgonesurfing - dapatkah Anda memberikan masukan seperti itu? Saya telah memperbarui jawaban saya, menjalankannya dan berfungsi (bd dan ca adalah 0) ...
paquetp

1
Hmmm. Sekarang saya pikir mungkin tidak. Kasus degenerasi dengan d = 1 dan a = 1 dan b = maxint dan c = maxint masih berfungsi. Keren :)
bradgonesurfing

1
@paquetp: a = 1, b = 0x7fffffffffffffff, c = -0x7ffffffffffffffff, d = 1 (catatan c negatif). Tapi pintar, saya yakin kode Anda menangani semua angka positif dengan benar.
Mooing Duck

3
@ MoooDuck, tetapi jawaban terakhir untuk set Anda juga meluap sehingga bukan setup yang valid. Ini hanya berfungsi jika setiap sisi memiliki tanda yang sama sehingga pengurangan yang dihasilkan berada dalam kisaran.
bradgonesurfing

1
Ada sesuatu yang aneh dengan StackOverflow ketika jawaban ini yang paling sederhana dan yang terbaik mendapat skor rendah dibandingkan dengan jawaban skor teratas.
bradgonesurfing

9

Anda dapat mempertimbangkan menghitung faktor umum terbesar untuk semua nilai Anda, dan kemudian membaginya dengan faktor itu sebelum melakukan operasi aritmatika Anda, lalu mengalikannya lagi. Ini mengasumsikan bahwa faktor tersebut ada, namun (misalnya, jika A, B, Cdan Dkebetulan relatif prima, mereka tidak akan memiliki faktor umum).

Demikian pula, Anda dapat mempertimbangkan mengerjakan skala log, tetapi ini akan sedikit menakutkan, tergantung pada ketepatan numerik.


1
Logaritma sepertinya bagus jika long doubletersedia. Dalam hal ini, tingkat presisi yang dapat diterima dapat dicapai (dan hasilnya dapat dibulatkan).

9

Jika hasilnya cocok dengan int panjang yang panjang maka ekspresi A * BC * D tidak apa-apa karena melakukan mod aritmatika 2 ^ 64, dan akan memberikan hasil yang benar. Masalahnya adalah untuk mengetahui apakah hasilnya cocok dengan int panjang. Untuk mendeteksi ini, Anda dapat menggunakan trik berikut ini menggunakan ganda:

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

Masalah dengan pendekatan ini adalah bahwa Anda dibatasi oleh ketelitian mantissa dari ganda (54bits?) Sehingga Anda perlu membatasi produk A * B dan C * D hingga 63 + 54 bit (atau mungkin sedikit kurang).


Ini adalah contoh paling praktis. Hapus dan berikan jawaban yang benar (atau lemparkan Pengecualian saat inputnya buruk).
Mark Lakata

1
Bagus dan elegan! Anda tidak jatuh untuk perangkap yang orang lain jatuh cinta. Hanya satu hal lagi: Saya berani bertaruh ada beberapa contoh di mana perhitungan ganda di bawah MAX_LLONG hanya karena kesalahan pembulatan. Insting matematika saya memberi tahu Anda bahwa Anda harus menghitung perbedaan hasil ganda dan panjang, dan membandingkannya dengan MAX_LLONG / 2 atau sesuatu. Perbedaan ini adalah kesalahan pembulatan dari penghitungan ganda dan ditambah dengan luapan dan biasanya harus relatif rendah, tetapi dalam kasus yang saya sebutkan itu akan menjadi besar. Tapi saat ini aku terlalu malas untuk mencari tahu. :-)
Hans-Peter Störr

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

kemudian

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

7

Anda bisa menulis setiap angka dalam array, setiap elemen menjadi digit dan melakukan perhitungan sebagai polinomial . Ambil polinomial yang dihasilkan, yang merupakan array, dan hitung hasilnya dengan mengalikan masing-masing elemen array dengan 10 pangkat posisi dalam array (posisi pertama menjadi yang terbesar dan yang terakhir menjadi nol).

Jumlahnya 123dapat dinyatakan sebagai:

123 = 100 * 1 + 10 * 2 + 3

yang baru saja Anda buat array [1 2 3].

Anda melakukan ini untuk semua angka A, B, C dan D, dan kemudian Anda mengalikannya sebagai polinomial. Setelah Anda memiliki polinomial yang dihasilkan, Anda hanya merekonstruksi nomor dari itu.


2
tidak tahu apa itu, tetapi saya harus menemukan. taruh :) . ini adalah solusi dari bagian atas kepala saya saat saya berbelanja dengan pacar saya :)
Mihai

Anda menerapkan bignum dalam array base10. GMP adalah perpustakaan bignum berkualitas yang menggunakan basis 4294967296. JAUH lebih cepat. Tidak ada downvote, karena jawabannya benar, dan bermanfaat.
Mooing Duck

terima kasih :) berguna untuk mengetahui bahwa ini cara melakukannya tetapi ada cara yang lebih baik jadi jangan lakukan seperti ini. setidaknya tidak dalam situasi ini :)
Mihai

lagi pula ... menggunakan solusi ini Anda dapat membuat angka yang jauh lebih besar daripada tipe primitif mana pun komputer (seperti angka 100 digit) dan menyimpan hasilnya sebagai sebuah array. ini layak dipilih: p
Mihai

Saya tidak yakin itu mendapat upvote, karena metode ini (meskipun efektif dan relatif mudah dimengerti) adalah memori yang lapar dan lambat.
Mooing Duck

6

Meskipun signed long long inttidak akan bertahan A*B, dua dari mereka akan melakukannya. Jadi A*Bbisa didekomposisi menjadi pohon istilah eksponen yang berbeda, ada yang pas satu 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;

Sama untuk C*D.

Mengikuti dengan cara yang lurus, subraksi dapat dilakukan untuk setiap pasangan AB_idan CD_ijuga, menggunakan carry bit tambahan (akurat integer 1-bit) untuk masing-masing. Jadi jika kita mengatakan E = A * BC * D Anda mendapatkan sesuatu seperti:

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

Kami melanjutkan dengan mentransfer bagian atas E_10ke E_20(bergeser sebesar 32 dan menambah, lalu menghapus bagian atas E_10).

Sekarang Anda dapat menyingkirkan bit carry E_11dengan menambahkannya dengan tanda yang benar (diperoleh dari bagian non-carry) ke E_20. Jika ini memicu overflow, hasilnya tidak akan cocok.

E_10sekarang memiliki cukup 'ruang' untuk mengambil bagian atas dari E_00 (shift, tambah, hapus) dan carry bit E_01.

E_10mungkin lebih besar sekarang lagi, jadi kami ulangi transfer ke E_20.

Pada titik ini, E_20harus menjadi nol, jika tidak hasilnya tidak akan sesuai. Bagian atas E_10kosong karena transfer juga.

Langkah terakhir adalah mentransfer bagian bawah E_20ke dalam E_10lagi.

Jika harapan yang E=A*B+C*Dsesuai dengan signed long long intpalka, kita sekarang miliki

E_20=0
E_10=0
E_00=E

1
Ini sebenarnya adalah formula sederhana yang akan didapat jika menggunakan rumus multiplikasi Ofir dan menghapus setiap hasil sementara yang tidak berguna.
dronus

3

Jika Anda tahu hasil akhir diwakili dalam tipe integer Anda, Anda dapat melakukan perhitungan ini dengan cepat menggunakan kode di bawah ini. Karena standar C menentukan bahwa aritmatika yang tidak ditandatangani adalah modulo aritmatika dan tidak meluap, Anda dapat menggunakan tipe yang tidak ditandatangani untuk melakukan perhitungan.

Kode berikut mengasumsikan ada tipe unsigned dengan lebar yang sama dan tipe yang ditandatangani menggunakan semua pola bit untuk mewakili nilai (tidak ada representasi trap, minimum dari tipe yang ditandatangani adalah negatif dari setengah modulus dari tipe unsigned). Jika ini tidak berlaku dalam implementasi C, penyesuaian sederhana dapat dilakukan untuk rutin ConvertToSigned untuk itu.

Berikut ini menggunakan signed chardan unsigned charuntuk menunjukkan kode. Untuk implementasi Anda, ubah definisi Signedmenjadi typedef signed long long int Signed;dan definisi Unsignedmenjadi 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

Anda bisa mencoba memecah persamaan menjadi komponen yang lebih kecil yang tidak meluap.

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

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

    where K = B - N
          J = D - M

Jika komponen masih meluap Anda dapat memecahnya menjadi komponen yang lebih kecil secara rekursif dan kemudian bergabung kembali.


Ini mungkin atau mungkin tidak benar, tetapi jelas membingungkan. Anda mendefinisikan Kdan J, mengapa tidak Ndan M. Juga, saya pikir Anda memecah persamaan menjadi potongan-potongan yang lebih besar . Karena langkah 3 Anda sama dengan pertanyaan OP, kecuali lebih rumit (AK-CJ)->(AB-CD)
Mooing Duck

N tidak disederhanakan dari apa pun. Itu hanya angka yang dikurangi dari A untuk membuatnya lebih kecil. Sebenarnya itu adalah solusi yang mirip tetapi lebih rendah untuk paquetp. Di sini saya menggunakan pengurangan daripada pembagian integer untuk membuatnya lebih kecil.
bradgonesurfing

2

Saya mungkin tidak membahas semua kasus tepi, saya juga belum menguji ini dengan keras tetapi ini menerapkan teknik yang saya ingat menggunakan di tahun 80-an ketika mencoba melakukan matematika integer 32-bit pada cpu 16-bit. Pada dasarnya Anda membagi 32 bit menjadi dua unit 16-bit dan bekerja dengannya secara terpisah.

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

  }

}

Cetakan:

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

yang menurut saya itu berfungsi.

Saya yakin saya telah melewatkan beberapa seluk-beluk seperti menonton tanda overflow dll. Tapi saya pikir intinya ada


1
Saya pikir ini adalah implementasi dari apa yang disarankan @Ofir.
OldCurmudgeon

2

Demi kelengkapan, karena tidak ada yang menyebutkannya, beberapa kompiler (misalnya GCC) benar-benar memberi Anda integer 128 bit saat ini.

Dengan demikian solusi yang mudah adalah:

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

1

AB-CD = (AB-CD) * AC / AC = (B/C-D/A)*A*C. Baik B/Catau D/Abisa meluap, sehingga menghitung (B/C-D/A)pertama. Karena hasil akhir tidak akan meluap sesuai dengan definisi Anda, Anda dapat dengan aman melakukan penggandaan yang tersisa dan menghitung (B/C-D/A)*A*Cmana yang merupakan hasil yang diperlukan.

Catatan, jika input Anda bisa sangat kecil juga, B/Catau D/Abisa meluap. Jika memungkinkan, manipulasi yang lebih kompleks mungkin diperlukan sesuai dengan inspeksi input.


2
Itu tidak akan berfungsi karena divisi integer kehilangan informasi (sebagian kecil dari hasilnya)
Ofir

@Ofir itu benar, namun Anda tidak bisa makan kue dan membiarkannya tidak tersentuh. Anda harus membayar dengan presisi atau dengan menggunakan sumber daya tambahan (seperti yang Anda sarankan dalam jawaban Anda). Jawaban saya bersifat matematis sedangkan jawaban Anda berorientasi komputer. Masing-masing bisa benar tergantung pada keadaan.
SomeWittyUsername

2
Anda benar - saya seharusnya menyebutnya sebagai - tidak akan memberikan hasil yang tepat daripada tidak akan berhasil, karena matematika sudah benar. Namun, perhatikan dalam kasus-kasus yang mungkin menarik minat pengirim pertanyaan (misalnya dalam contoh dalam pertanyaan), kesalahan mungkin akan sangat besar - jauh lebih besar daripada yang dapat diterima untuk aplikasi praktis. Bagaimanapun juga - itu adalah jawaban yang mendalam dan saya seharusnya tidak menggunakan bahasa itu.
Ofir

@Ofir Saya rasa bahasa Anda tidak pantas. OP jelas meminta perhitungan "benar", bukan perhitungan yang akan kehilangan presisi demi dilakukan di bawah kendala sumber daya yang ekstrem.
user4815162342

1

Pilih K = a big number(mis. K = A - sqrt(A))

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

Mengapa?

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

Perhatikan bahwa Karena A, B, C dan D adalah angka besar, maka A-Cdan B-Dangka kecil.


Bagaimana Anda memilih K secara praktis? Selain itu, K * (A-C + BD) mungkin masih meluap.
ylc

@ylc: Pilih K = sqrt (A), Bukan itu A-C+B-Dadalah angka kecil. Karena A, B, C dan D adalah angka besar, maka AC adalah angka kecil.
Amir Saniyan

Jika Anda memilih K = sqrt (A) , maka (AK) * (BK) mungkin meluap lagi.
ylc

@ylc: Oke! Saya ubah ke A - sqrt(A):)
Amir Saniyan

Maka K * (A-C + BD) dapat meluap.
ylc
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.