Apakah saya perlu secara eksplisit menangani angka negatif atau nol ketika menjumlahkan angka kuadrat?

220

Saya baru-baru ini menjalani tes di kelas saya. Salah satu masalah adalah sebagai berikut:

Diberi nomor n , tulis fungsi dalam C / C ++ yang mengembalikan jumlah angka dari kuadrat . (Berikut ini penting). The berbagai dari n adalah [- (10 ^ 7), 10 ^ 7]. Contoh: Jika n = 123, fungsi Anda harus mengembalikan 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).

Ini adalah fungsi yang saya tulis:

int sum_of_digits_squared(int n) 
{
    int s = 0, c;

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

Tampak benar bagiku. Jadi sekarang tes kembali dan saya menemukan bahwa guru tidak memberi saya semua poin karena alasan yang saya tidak mengerti. Menurutnya, agar fungsi saya menjadi lengkap, saya seharusnya menambahkan detail berikut:

int sum_of_digits_squared(int n) 
 {
    int s = 0, c;

    if (n == 0) {      //
        return 0;      //
    }                  //
                       // THIS APPARENTLY SHOULD'VE 
    if (n < 0) {       // BEEN IN THE FUNCTION FOR IT
        n = n * (-1);  // TO BE CORRECT
    }                  //

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

Argumen untuk ini adalah bahwa angka n berada dalam kisaran [- (10 ^ 7), 10 ^ 7], sehingga dapat menjadi angka negatif. Tapi saya tidak melihat di mana versi saya sendiri dari fungsi gagal. Jika saya mengerti benar, makna while(n)adalah while(n != 0), tidak while (n > 0) , sehingga dalam versi saya fungsi jumlah n tidak akan gagal untuk masuk ke loop. Itu akan bekerja sama saja.

Kemudian, saya mencoba kedua versi fungsi di komputer saya di rumah dan saya mendapatkan jawaban yang persis sama untuk semua contoh yang saya coba. Jadi, sum_of_digits_squared(-123)sama dengan sum_of_digits_squared(123)(yang lagi-lagi sama dengan 14) (bahkan tanpa detail yang sepertinya harus saya tambahkan). Memang, jika saya mencoba untuk mencetak pada layar angka-angka dari (dari yang paling penting sampai yang paling penting), dalam 123kasus yang saya dapatkan 3 2 1dan dalam -123kasus yang saya dapatkan -3 -2 -1(yang sebenarnya agak menarik). Tetapi dalam masalah ini tidak masalah karena kita mengkuadratkan angka.

Jadi siapa yang salah?

EDIT : Buruk saya, saya lupa menentukan dan tidak tahu itu penting. Versi C yang digunakan di kelas kami dan tes harus C99 atau lebih baru . Jadi saya kira (dengan membaca komentar) bahwa versi saya akan mendapatkan jawaban yang benar dengan cara apa pun.

pengguna010517720
sumber
120
n = n * (-1)adalah cara konyol untuk menulis n = -n; Hanya seorang akademisi yang akan memikirkannya. Apalagi menambahkan tanda kurung yang berlebihan.
user207421
32
Tulis serangkaian tes unit untuk memeriksa apakah implementasi yang diberikan sesuai dengan spesifikasi. Jika ada masalah (fungsional) dengan sepotong kode, seharusnya dimungkinkan untuk menulis tes yang menunjukkan hasil yang salah dengan input tertentu.
Carl
94
Saya merasa menarik bahwa "jumlah digit dari angka kuadrat" dapat ditafsirkan dalam tiga (3) cara yang sama sekali berbeda. (Jika angkanya 123, kemungkinan interpretasi menghasilkan 18, 14, dan 36.)
Andreas Rejbrand
23
@ilkkachu: "jumlah digit angka kuadrat". Nah, "angka kuadrat" jelas 123 ^ 2 = 15129, jadi "jumlah digit dari angka kuadrat" adalah "jumlah digit 15129", yang jelas adalah 1 + 5 + 1 + 2 + 9 = 18.
Andreas Rejbrand
15
n = n * (-1)? Wut ??? Apa yang dicari profesor Anda adalah ini: `n = -n '. Bahasa C memiliki operator minus unary.
Kaz

Jawaban:

245

Merangkum diskusi yang telah meresap dalam komentar:

  • Tidak ada alasan bagus untuk menguji terlebih dahulu n == 0. The while(n)tes akan menangani kasus itu dengan sempurna.
  • Kemungkinan guru Anda masih terbiasa dengan waktu sebelumnya, ketika hasil %dengan operan negatif didefinisikan secara berbeda. Pada beberapa sistem lama (termasuk, terutama, awal Unix pada PDP-11, di mana Dennis Ritchie awalnya dikembangkan C), hasil a % bitu selalu dalam kisaran [0 .. b-1], yang berarti bahwa -123% 10 adalah 7. Pada sistem seperti itu, tes di muka n < 0akan diperlukan.

Tapi peluru kedua hanya berlaku untuk masa-masa sebelumnya. Dalam versi terkini dari standar C dan C ++, pembagian integer didefinisikan untuk memotong ke 0, sehingga ternyata n % 10dijamin memberi Anda digit terakhir (mungkin negatif) nbahkan ketika nnegatif.

Jadi jawaban untuk pertanyaan "Apa artinya while(n)?" adalah "Persis sama dengan while(n != 0)" , dan jawaban untuk "Apakah kode ini berfungsi dengan baik untuk negatif maupun positif n?" adalah "Ya, di bawah kompiler modern yang memenuhi standar apa pun." Jawaban untuk pertanyaan "Lalu mengapa instruktur menandainya?" mungkin mereka tidak menyadari adanya definisi ulang bahasa yang signifikan yang terjadi pada C pada tahun 1999 dan C ++ pada tahun 2010 atau lebih.

Steve Summit
sumber
39
"Tidak ada alasan bagus untuk menguji di muka untuk n == 0" - secara teknis, itu benar. Tetapi mengingat bahwa kita sedang berbicara tentang seorang profesor di lingkungan pengajaran, mereka mungkin menghargai kejelasan lebih singkat daripada kita. Menambahkan tes tambahan untuk n == 0setidaknya membuatnya segera dan jelas bagi pembaca apa pun yang terjadi dalam kasus itu. Tanpa itu, pembaca harus meyakinkan diri sendiri bahwa loop memang dilewati, dan nilai default yang sdikembalikan adalah yang benar.
ilkkachu
22
Juga, profesor mungkin ingin tahu bahwa siswa sadar dan telah memikirkan mengapa dan bagaimana fungsi berperilaku dengan input nol (yaitu bahwa itu tidak mengembalikan nilai yang benar secara tidak sengaja ). Mereka mungkin telah bertemu siswa yang tidak menyadari apa yang akan terjadi dalam kasus itu, bahwa loop dapat berjalan nol kali, dll. Ketika Anda berurusan dengan pengaturan pengajaran, yang terbaik adalah menjadi lebih jelas tentang asumsi dan kasus sudut. ..
ilkkachu
36
@ilkkachu Jika itu masalahnya, maka guru harus membagikan tugas yang memerlukan tes semacam itu agar bekerja dengan benar.
klutt
38
@ilkkachu Yah, saya mengambil poin Anda dalam kasus umum, karena saya sangat menghargai kejelasan atas singkatnya - dan hampir sepanjang waktu, tidak harus hanya dalam pengaturan pedagogis. Tetapi dengan itu, kadang-kadang singkatnya adalah kejelasan, dan jika Anda dapat mengatur kode utama untuk menangani kasus umum dan kasus tepi, tanpa mengacaukan kode (dan analisis cakupan) dengan kasus khusus untuk kasus tepi , itu hal yang indah! Saya pikir itu sesuatu yang harus dihargai bahkan di tingkat pemula.
Steve Summit
49
@ilkkachu Dengan argumen itu Anda juga harus menambahkan tes untuk n = 1 dan seterusnya. Tidak ada yang spesial dari itu n=0. Memperkenalkan cabang yang tidak perlu dan komplikasi tidak membuat kode lebih mudah, itu membuatnya lebih sulit karena sekarang Anda tidak hanya harus menunjukkan bahwa algoritma umum sudah benar, Anda juga harus memikirkan semua kasus khusus secara terpisah.
Voo
107

Kode Anda baik-baik saja

Anda benar sekali dan guru Anda salah. Sama sekali tidak ada alasan sama sekali untuk menambahkan kompleksitas ekstra, karena itu tidak mempengaruhi hasilnya sama sekali. Bahkan memperkenalkan bug. (Lihat di bawah)

Pertama, pemeriksaan terpisah apakah nnol jelas sama sekali tidak perlu dan ini sangat mudah untuk diwujudkan. Sejujurnya, saya benar-benar mempertanyakan kompetensi guru Anda jika dia keberatan dengan hal ini. Tetapi saya kira setiap orang dapat memiliki otak yang kentut dari waktu ke waktu. Namun, saya TIDAK berpikir itu while(n)harus diubah while(n != 0)karena menambah sedikit kejelasan ekstra bahkan tanpa biaya garis tambahan. Ini hal kecil.

Yang kedua sedikit lebih bisa dimengerti, tetapi dia masih salah.

Inilah yang dikatakan standar C11 6.5.5.p6 :

Jika hasil bagi a / b dapat diwakili, ungkapan (a / b) * b + a% b harus sama dengan a; jika tidak, perilaku a / b dan% b tidak ditentukan.

Catatan kaki mengatakan ini:

Ini sering disebut "pemotongan ke nol".

Pemotongan ke nol berarti bahwa nilai absolut untuk a/bsama dengan nilai absolut (-a)/buntuk semua adan b, yang pada gilirannya berarti bahwa kode Anda baik-baik saja.

Modulo adalah matematika yang mudah, tetapi mungkin berlawanan dengan intuisi

Namun, guru Anda memang memiliki poin bahwa Anda harus berhati-hati, karena fakta bahwa Anda mengkuadratkan hasilnya sebenarnya sangat penting di sini. Menghitung a%bmenurut definisi di atas adalah matematika mudah, tetapi itu mungkin bertentangan dengan intuisi Anda. Untuk perkalian dan pembagian, hasilnya positif jika operan memiliki tanda yang sama. Tetapi ketika datang ke modulo, hasilnya memiliki tanda yang sama dengan operan pertama . Operan kedua tidak mempengaruhi tanda sama sekali. Misalnya, 7%3==1tapi (-7)%(-3)==(-1).

Ini cuplikan yang menunjukkan:

$ cat > main.c 
#include <stdio.h>

void f(int a, int b) 
{
    printf("a: %2d b: %2d a/b: %2d a\%b: %2d (a%b)^2: %2d (a/b)*b+a%b==a: %5s\n",
           a, b ,a/b, a%b, (a%b)*(a%b), (a/b)*b+a%b == a ? "true" : "false");
}

int main(void)
{
    int a=7, b=3;
    f(a,b);
    f(-a,b);
    f(a,-b);
    f(-a,-b);
}

$ gcc main.c -Wall -Wextra -pedantic -std=c99

$ ./a.out
a:  7 b:  3 a/b:  2 a%b:  1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a: -7 b:  3 a/b: -2 a%b: -1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a:  7 b: -3 a/b: -2 a%b:  1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a: -7 b: -3 a/b:  2 a%b: -1 (a%b)^2:  1 (a/b)*b+a%b==a:  true

Jadi, ironisnya, gurumu membuktikan pendapatnya dengan salah.

Kode guru Anda cacat

Ya, sebenarnya. Jika inputnya INT_MINDAN arsitekturnya adalah pelengkap dua DAN pola bit di mana bit tanda adalah 1 dan semua nilai bit adalah 0 BUKAN nilai jebakan (menggunakan pelengkap dua tanpa nilai jebakan sangat umum) maka kode guru Anda akan menghasilkan perilaku yang tidak terdefinisi di telepon n = n * (-1). Kode Anda - jika sedikit - lebih baik dari miliknya. Dan mempertimbangkan memperkenalkan bug kecil dengan membuat kode tidak perlu rumit dan mendapatkan nilai nol, saya akan mengatakan bahwa kode Anda JAUH lebih baik.

Dengan kata lain, dalam kompilasi di mana INT_MIN = -32768 (meskipun fungsi yang dihasilkan tidak dapat menerima input yang <-32768 atau> 32767), input yang valid dari -32768 menyebabkan perilaku tidak terdefinisi, karena hasil dari - (- 32768i16) tidak dapat dinyatakan sebagai bilangan bulat 16-bit. (Sebenarnya, -32768 mungkin tidak akan menyebabkan hasil yang salah, karena - (- 32768i16) biasanya mengevaluasi ke -32768i16, dan program Anda menangani angka negatif dengan benar.) (SHRT_MIN bisa -32768 atau -32767, tergantung pada kompilernya)

Tetapi guru Anda secara eksplisit menyatakan bahwa nbisa berada dalam kisaran [-10 ^ 7; 10 ^ 7]. Bilangan bulat 16-bit terlalu kecil; Anda harus menggunakan [setidaknya] integer 32-bit. Penggunaannya intmungkin membuat kode-nya aman, kecuali itu intbelum tentu integer 32-bit. Jika Anda mengkompilasi untuk arsitektur 16-bit, kedua cuplikan kode Anda cacat. Tetapi kode Anda masih jauh lebih baik karena skenario ini memperkenalkan kembali bug dengan yang INT_MINdisebutkan di atas dengan versinya. Untuk menghindari ini, Anda bisa menulis longalih-alih int, yang merupakan bilangan bulat 32-bit pada kedua arsitektur. A longdijamin dapat memiliki nilai apa pun dalam kisaran [-2147483647; 2147483647]. C11 Standar 5.2.4.2.1 LONG_MIN sering-2147483648tetapi nilai maksimum yang diizinkan (ya, maksimum, ini adalah angka negatif) LONG_MINadalah 2147483647.

Perubahan apa yang akan saya lakukan pada kode Anda?

Kode Anda baik-baik saja, jadi ini bukan keluhan. Lebih seperti itu jika saya benar-benar perlu mengatakan apa pun tentang kode Anda, ada beberapa hal kecil yang dapat membuatnya sedikit lebih jelas.

  • Nama-nama variabel bisa sedikit lebih baik, tetapi itu adalah fungsi pendek yang mudah dimengerti, jadi itu bukan masalah besar.
  • Anda dapat mengubah kondisi dari nmenjadi n!=0. Secara semantik, ini setara 100%, tetapi membuatnya sedikit lebih jelas.
  • Pindahkan deklarasi c(yang saya ganti namanya digit) ke dalam loop sementara karena itu hanya digunakan di sana.
  • Ubah tipe argumen longuntuk memastikannya dapat menangani seluruh rangkaian input.
int sum_of_digits_squared(long n) 
{
    long sum = 0;

    while (n != 0) {
        int digit = n % 10;
        sum += (digit * digit);
        n /= 10;
    }

    return sum;
}

Sebenarnya, ini bisa sedikit menyesatkan karena - seperti yang disebutkan di atas - variabel digitbisa mendapatkan nilai negatif, tetapi digit itu sendiri tidak pernah positif atau negatif. Ada beberapa cara untuk mengatasi hal ini, tetapi ini BENAR-BENAR membingungkan, dan saya tidak akan peduli dengan detail sekecil itu. Terutama fungsi terpisah untuk digit terakhir terlalu jauh. Ironisnya, ini adalah salah satu hal yang dipecahkan oleh kode guru Anda.

  • Ubah sum += (digit * digit)ke sum += ((n%10)*(n%10))dan lewati variabel digitsepenuhnya.
  • Ubah tanda digitjika negatif. Tapi saya akan sangat menyarankan agar kode tidak lebih kompleks hanya untuk membuat nama variabel masuk akal. Itu bau kode yang SANGAT kuat.
  • Buat fungsi terpisah yang mengekstrak digit terakhir. int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }Ini berguna jika Anda ingin menggunakan fungsi itu di tempat lain.
  • Beri nama saja cseperti yang Anda lakukan semula. Nama variabel itu tidak memberikan informasi yang berguna, tetapi di sisi lain, itu juga tidak menyesatkan.

Tetapi jujur ​​saja, pada titik ini Anda harus beralih ke pekerjaan yang lebih penting. :)

klutt
sumber
19
Jika inputnya adalah INT_MINdan arsitekturnya menggunakan komplemen dua (yang sangat umum) maka kode guru Anda akan menghasilkan perilaku yang tidak terdefinisi. Aduh. Itu akan meninggalkan bekas. ;-)
Andrew Henle
5
Perlu disebutkan bahwa di samping itu (a/b)*b + a%b ≡ a, kode OP juga bergantung pada fakta bahwa/ putaran menuju nol, dan itu (-c)*(-c) ≡ c*c. Ini bisa dikatakan bahwa pemeriksaan ekstra dibenarkan meskipun standar menjamin semua itu, karena itu cukup non-jelas. (Tentu saja dapat juga diperdebatkan bahwa seharusnya ada komentar yang menghubungkan bagian standar yang relevan, tetapi pedoman gaya bervariasi.)
leftaroundabout
7
@MartinRosenau Anda mengatakan "mungkin". Apakah Anda yakin ini benar-benar terjadi atau diizinkan oleh standar atau sesuatu atau Anda hanya berspekulasi?
klutt
6
@ MartinRosenau: Oke, tapi menggunakan sakelar itu akan membuatnya tidak lagi C. GCC / dentang tidak memiliki sakelar yang memecah divisi integer pada ISA apa pun yang saya ketahui. Meskipun mengabaikan bit tanda mungkin bisa memberikan percepatan menggunakan inversi multiplikatif normal untuk pembagi konstan. (Tetapi semua ISA yang saya kenal yang memiliki instruksi divisi perangkat keras mengimplementasikannya dengan cara C99, memotong ke nol, sehingga C %dan /operator dapat dikompilasi menjadi hanya idivpada x86, atau sdivpada ARM atau apa pun. Namun, itu tidak terkait dengan banyak kode-gen yang lebih cepat untuk pembagi waktu kompilasi-konstan)
Peter Cordes
5
@TonyK AFIK, begitulah biasanya diselesaikan, tetapi menurut standarnya adalah UB.
klutt
20

Saya tidak sepenuhnya menyukai versi Anda atau versi guru Anda. Versi guru Anda tidak perlu melakukan tes tambahan yang Anda tunjukkan dengan benar. Operator mod C bukan mod matematika yang tepat: angka negatif mod 10 akan menghasilkan hasil negatif (modulus matematika yang tepat selalu non-negatif). Tetapi karena Anda tetap mengkuadratkannya, tidak ada perbedaan.

Tapi ini jauh dari jelas, jadi saya akan menambahkan kode Anda bukan cek guru Anda, tetapi komentar besar yang menjelaskan mengapa itu berhasil. Misalnya:

/ * CATATAN: Ini berfungsi untuk nilai negatif, karena modulus menjadi kuadrat * /

Lee Daniel Crocker
sumber
9
C %paling baik disebut sebagai sisa , karena memang itu, bahkan untuk tipe yang ditandatangani.
Peter Cordes
15
Kuadrat itu penting, tapi saya pikir itulah bagian yang jelas. Apa yang harus ditunjukkan adalah bahwa (misalnya) -7 % 10 sebenarnya akan-7 lebih daripada 3.
Yakub Raihle
5
"Modulus matematika yang tepat" tidak berarti apa-apa. Istilah yang benar adalah "Euclidean modulo" (pikirkan akhiran!) Dan memang itulah yang %bukan operator C.
Jan Hudec
Saya suka jawaban ini karena menjawab pertanyaan beberapa cara untuk menafsirkan modulo. Jangan pernah biarkan hal seperti itu terjadi secara kebetulan / interpretasi. Ini bukan kode golf.
Harper - Reinstate Monica
1
"Modulus matematika yang tepat selalu non-negatif" - Tidak juga. Hasil dari operasi modulo adalah kelas ekivalensi , tetapi itu umum untuk memperlakukan hasilnya sebagai angka non-negatif terkecil milik kelas itu.
klutt
10

CATATAN: SEBAGAI saya menulis jawaban ini, Anda mengklarifikasi bahwa Anda menggunakan C. Mayoritas jawaban saya adalah tentang C ++. Namun, karena judul Anda masih memiliki C ++ dan pertanyaannya masih ditandai C ++, saya tetap memilih untuk menjawab kalau-kalau ini masih berguna bagi orang lain, terutama karena sebagian besar jawaban yang saya lihat sampai sekarang sebagian besar tidak memuaskan.

Di zaman modern C + + (Catatan: Saya tidak benar-benar tahu di mana C berdiri pada ini), profesor Anda tampaknya salah dalam kedua hal.

Pertama adalah bagian ini di sini:

if (n == 0) {
        return 0;
}

Dalam C ++, ini pada dasarnya sama dengan :

if (!n) {
        return 0;
}

Itu berarti saat Anda setara dengan sesuatu seperti ini:

while(n != 0) {
    // some implementation
}

Itu berarti karena Anda hanya keluar di if Anda ketika sementara tidak akan mengeksekusi pula, sebenarnya tidak ada alasan untuk menempatkan ini jika di sini, karena apa yang Anda lakukan setelah loop dan di jika tetap sama. Meskipun saya harus mengatakan bahwa karena alasan tertentu ini berbeda, Anda harus memilikinya jika.

Jadi sungguh, pernyataan if ini tidak terlalu berguna kecuali saya salah.

Bagian kedua adalah di mana semuanya menjadi berbulu:

if (n < 0) {
    n = n * (-1);
}  

Inti dari masalah ini adalah apa output dari modulus dari output angka negatif.

Dalam C ++ modern, ini tampaknya sebagian besar didefinisikan dengan baik :

Biner / operator menghasilkan hasil bagi, dan operator biner% menghasilkan sisanya dari pembagian ekspresi pertama oleh yang kedua. Jika operan kedua dari / atau% adalah nol perilaku tidak terdefinisi. Untuk operan integral operator / menghasilkan hasil bagi aljabar dengan setiap bagian pecahan dibuang; jika hasil bagi a / b dapat direpresentasikan dalam tipe hasil, (a / b) * b + a% b sama dengan a.

Dan kemudian:

Jika kedua operan tidak negatif maka sisanya adalah non-negatif; jika tidak, tanda sisanya ditentukan implementasi.

Seperti yang ditunjukkan oleh poster jawaban yang dikutip dengan benar, bagian penting dari persamaan ini di sini:

(a / b) * b + a% b

Mengambil contoh dari kasus Anda, Anda akan mendapatkan sesuatu seperti ini:

-13/ 10 = -1 (integer truncation)
-1 * 10 = -10
-13 - (-10) = -13 + 10 = -3 

Satu-satunya tangkapan adalah baris terakhir:

Jika kedua operan tidak negatif maka sisanya adalah non-negatif; jika tidak, tanda sisanya ditentukan implementasi.

Itu artinya dalam kasus seperti ini, hanya pertanda tampaknya sudah ditentukan implementasi. Itu seharusnya tidak menjadi masalah dalam kasus Anda karena, karena Anda tetap mengkuadratkan nilai ini.

Karena itu, perlu diingat bahwa ini tidak selalu berlaku untuk versi C ++ yang lebih lama, atau C99. Jika itu yang digunakan profesor Anda, itu bisa menjadi alasannya.


EDIT: Tidak, saya salah. Ini tampaknya juga berlaku untuk C99 atau lebih baru :

C99 mensyaratkan bahwa ketika a / b diwakili:

(a / b) * b + a% b harus sama dengan a

Dan tempat lain :

Ketika bilangan bulat dibagi dan pembagiannya tidak tepat, jika kedua operan positif hasil / operator adalah bilangan bulat terbesar kurang dari hasil aljabar dan hasil dari% operator positif. Jika salah satu operan negatif, apakah hasil dari operator / adalah bilangan bulat terbesar kurang dari hasil bagi aljabar atau bilangan bulat terkecil yang lebih besar dari hasil aljabar yang ditentukan-implementasi, seperti tanda hasil dari% operator. Jika hasil bagi a / b dapat diwakili, ungkapan (a / b) * b + a% b harus sama dengan a.

Apakah ANSI C atau ISO C menentukan -5% 10 seharusnya?

Jadi ya. Bahkan di C99, ini sepertinya tidak mempengaruhi Anda. Persamaannya sama.

Chipster
sumber
1
Bagian yang Anda kutip tidak mendukung jawaban ini. "tanda sisanya adalah implementasi yang ditentukan" tidak berarti (-1)%10dapat menghasilkan -1atau 1; itu berarti dapat menghasilkan -1atau 9, dan dalam kasus terakhir (-1)/10akan menghasilkan -1dan kode OP tidak akan pernah berakhir.
stewbasic
Bisakah Anda menunjuk ke sumber untuk ini? Saya memiliki waktu yang sulit untuk percaya (-1) / 10 adalah -1. Itu harus 0. Juga, (-1)% 10 = 9 tampaknya melanggar persamaan yang mengatur.
Chipster
1
@Chipster, mulailah dengan (a/b)*b + a%b == a, lalu biarkan a=-1; b=10, memberi (-1/10)*10 + (-1)%10 == -1. Sekarang, jika -1/10memang dibulatkan (ke -inf), maka kita miliki (-1/10)*10 == -10, dan Anda harus memiliki (-1)%10 == 9persamaan pertama yang cocok. Seperti negara jawaban lainnya , ini bukan cara kerjanya dalam standar saat ini, tetapi ini adalah cara kerjanya. Ini tidak benar-benar tentang tanda sisa seperti itu, tapi bagaimana putaran divisi dan apa sisanya kemudian memiliki untuk menjadi untuk memenuhi persamaan.
ilkkachu
1
@Chipster Sumbernya adalah cuplikan yang Anda kutip. Perhatikan bahwa (-1)*10+9=-1, jadi pilihan (-1)/10=-1dan (-1)%10=9tidak melanggar persamaan yang mengatur. Di sisi lain pilihan itu (-1)%10=1tidak dapat memenuhi persamaan yang mengatur tidak peduli bagaimana (-1)/10dipilih; tidak ada bilangan bulat qseperti itu q*10+1=-1.
stewbasic
8

Seperti yang telah ditunjukkan orang lain, perlakuan khusus untuk n == 0 adalah omong kosong, karena untuk setiap programmer C yang serius jelas bahwa "while (n)" melakukan pekerjaan.

Perilaku untuk n <0 tidak begitu jelas, itu sebabnya saya lebih suka melihat 2 baris kode:

if (n < 0) 
    n = -n;

atau setidaknya komentar:

// don't worry, works for n < 0 as well

Jujur, pada jam berapa Anda mulai mempertimbangkan bahwa n mungkin negatif? Saat menulis kode atau ketika membaca komentar guru Anda?

CB
sumber
1
Terlepas dari apakah N negatif, N kuadrat akan positif. Jadi, mengapa menghapus tanda itu di tempat pertama? -3 * -3 = 9; 3 * 3 = 9. Atau apakah matematika berubah dalam 30-aneh tahun sejak saya belajar ini?
Merovex
2
@ CB Agar adil, saya bahkan tidak melihat bahwa n bisa negatif ketika saya sedang menulis tes, tetapi ketika kembali saya hanya merasa bahwa loop sementara tidak akan dilewati, bahkan jika angkanya negatif. Saya melakukan beberapa tes di komputer saya dan itu mengkonfirmasi keraguan saya. Setelah itu, saya memposting pertanyaan ini. Jadi tidak, saya tidak berpikir terlalu dalam saat menulis kode.
user010517720
5

Ini mengingatkan saya pada tugas yang gagal

Kembali di tahun 90-an. Dosen telah bertunas tentang loop dan, singkatnya, tugas kami adalah untuk menulis fungsi yang akan mengembalikan jumlah digit untuk setiap bilangan bulat yang diberikan> 0.

Jadi, misalnya, jumlah digit 321akan menjadi 3.

Meskipun tugas hanya mengatakan untuk menulis fungsi yang mengembalikan jumlah digit, harapannya adalah bahwa kita akan menggunakan loop yang membaginya dengan 10 sampai ... Anda mendapatkannya, seperti yang dicakup oleh kuliah .

Tetapi menggunakan loop tidak secara eksplisit dinyatakan jadi saya: took the log, stripped away the decimals, added 1dan kemudian dicerca di depan seluruh kelas.

Intinya, tujuan dari tugas ini adalah untuk menguji pemahaman kita tentang apa yang telah kita pelajari selama kuliah . Dari kuliah yang saya terima, saya tahu bahwa guru komputer itu sedikit brengsek (tapi mungkin brengsek dengan rencana?)


Dalam situasi Anda:

tulis fungsi dalam C / C ++ yang mengembalikan jumlah digit angka kuadrat

Saya pasti akan memberikan dua jawaban:

  • jawaban yang benar (kuadratkan angka pertama), dan
  • jawaban yang salah sesuai dengan contoh, hanya untuk membuatnya senang ;-)
SlowLearner
sumber
5
Dan juga yang ketiga mengkuadratkan jumlah digit?
Kriss
@ Kriss - yeah, aku tidak sepintar itu :-(
SlowLearner
1
Saya juga mendapat bagian dari tugas yang terlalu samar dalam waktu siswa saya. Seorang guru ingin perpustakaan manipulasi sedikit tetapi dia terkejut dengan kode saya dan mengatakan itu tidak berfungsi. Saya harus menunjukkan kepadanya bahwa dia tidak pernah mendefinisikan endianness dalam tugasnya dan dia mengambil bit lebih rendah sebagai bit 0 sementara saya membuat pilihan lain. Satu-satunya bagian yang menjengkelkan adalah bahwa dia seharusnya bisa mengetahui di mana perbedaannya tanpa saya katakan padanya.
Kriss
1

Umumnya dalam penugasan tidak semua tanda diberikan hanya karena kode berfungsi. Anda juga mendapatkan nilai untuk membuat solusi mudah dibaca, efisien dan elegan. Hal-hal ini tidak selalu saling eksklusif.

Satu saya tidak bisa strees cukup adalah "menggunakan nama variabel yang bermakna" .

Dalam contoh Anda itu tidak membuat banyak perbedaan, tetapi jika Anda mengerjakan proyek dengan jutaan baris pembacaan kode menjadi sangat penting.

Hal lain yang cenderung saya lihat dengan kode C adalah orang yang mencoba terlihat pintar. Daripada menggunakan while (n! = 0) saya akan menunjukkan kepada semua orang betapa pandainya saya dengan menulis while (n) karena itu artinya hal yang sama. Baik itu dalam kompiler yang Anda miliki tetapi seperti yang Anda sarankan versi lama guru Anda belum mengimplementasikannya dengan cara yang sama.

Contoh umum adalah mereferensikan indeks dalam array sambil menambahkannya pada saat yang bersamaan; Angka [i ++] = iPrime;

Sekarang, programmer berikutnya yang bekerja pada kode harus tahu apakah saya bertambah sebelum atau setelah penugasan, supaya seseorang bisa pamer.

Satu megabyte ruang disk lebih murah dari pada gulungan kertas toilet, lebih jelas daripada mencoba menghemat ruang, sesama programmer Anda akan lebih bahagia.

Paul McCarthy
sumber
2
Saya telah memprogram dalam C hanya beberapa kali dan saya tahu bahwa ++ikenaikan sebelum evaluasi dan i++kenaikan sesudahnya. while(n)juga merupakan fitur bahasa yang umum. Berdasarkan logika seperti ini, saya telah melihat banyak kode seperti if (foo == TRUE). Saya setuju kembali: nama variabel.
alan ocallaghan
3
Umumnya itu bukan saran yang buruk, tetapi menghindari fitur bahasa dasar (bahwa orang pasti akan menemukan) untuk tujuan pemrograman defensif berlebihan. Kode pendek, jelas sering lebih mudah dibaca. Kami tidak berbicara tentang perl gila atau bash one-liners di sini, hanya fitur bahasa yang sangat dasar.
alan ocallaghan
1
Tidak yakin, untuk apa downvotes jawaban ini diberikan. Bagi saya semua yang dinyatakan di sini adalah benar dan penting dan saran yang baik untuk setiap pengembang. Terutama bagian pemrograman yang pintar, meskipun while(n)bukan contoh terburuk untuk itu (saya "lebih suka" if(strcmp(one, two))lebih)
Kai Huppmann
1
Dan selain itu, Anda benar-benar tidak boleh membiarkan siapa pun yang tidak tahu perbedaan antara i++dan ++imemodifikasi kode C yang harus digunakan dalam produksi.
klutt
2
@ PaulMcCarthy Kami berdua untuk keterbacaan kode. Tetapi kami tidak setuju tentang apa artinya itu. Juga, itu tidak objektif. Apa yang mudah dibaca untuk satu orang mungkin sulit bagi orang lain. Pengetahuan kepribadian dan latar belakang sangat memengaruhi hal ini. Poin utama saya adalah bahwa Anda tidak mendapatkan keterbacaan maksimum dengan secara membabi buta mengikuti beberapa aturan.
klutt
0

Saya tidak akan berdebat tentang apakah definisi asli atau modern tentang '%' lebih baik tetapi siapa pun yang menulis dua pernyataan kembali ke dalam fungsi singkat seperti itu seharusnya tidak mengajarkan pemrograman C sama sekali. Pengembalian ekstra adalah pernyataan goto dan kami tidak menggunakan goto dalam C. Selanjutnya kode tanpa cek nol akan memiliki hasil yang sama, pengembalian ekstra membuatnya lebih sulit untuk dibaca.

Peter Krassoi
sumber
4
"Pengembalian ekstra adalah pernyataan goto dan kami tidak menggunakan goto dalam C." - Itu adalah kombinasi dari generalisasi yang sangat luas dan rentang yang sangat jauh.
SS Anne
1
"Kami" pasti menggunakan goto di C. Tidak ada yang salah dengan itu.
klutt
1
Juga, tidak ada yang salah dengan fungsi sepertiint findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
klutt
1
@ PeterKrassoi Saya tidak menganjurkan kode yang sulit dibaca, tetapi saya telah melihat banyak contoh di mana kode tersebut terlihat seperti kekacauan lengkap hanya untuk menghindari kebohongan sederhana atau pernyataan pengembalian ekstra. Berikut adalah beberapa kode dengan penggunaan goto yang tepat. Saya menantang Anda untuk menghapus pernyataan goto sementara pada saat yang sama membuat kode lebih mudah dibaca dan mantain: pastebin.com/aNaGb66Q
klutt
1
@PeterKrassoi Tolong juga tunjukkan versi Anda ini: pastebin.com/qBmR78KA
klutt
0

Pernyataan masalah membingungkan, tetapi contoh numerik mengklarifikasi arti dari jumlah digit dari angka kuadrat . Ini versi yang ditingkatkan:

Menulis fungsi di bagian umum dari C dan C ++ yang mengambil integer ndalam rentang [-10 7 , 10 7 ] dan kembali dengan jumlah kuadrat dari angka perwakilannya di basis 10. Contoh: jika nadalah 123, fungsi Anda harus kembali 14(1 2 + 2 2 + 3 2 = 14).

Fungsi yang Anda tulis baik-baik saja kecuali untuk 2 detail:

  • Argumen harus memiliki tipe longuntuk mengakomodasi semua nilai dalam rentang yang ditentukan karena tipe longdijamin oleh Standar C untuk memiliki setidaknya 31 bit nilai, karenanya rentang yang cukup untuk mewakili semua nilai dalam [-10 7 , 10 7 ] . (Perhatikan bahwa jenis inttersebut cukup untuk jenis pengembalian, yang nilainya maksimum 568.)
  • Perilaku % operan negatif adalah non-intuitif dan spesifikasinya bervariasi antara Standar C99 dan edisi sebelumnya. Anda harus mendokumentasikan mengapa pendekatan Anda valid bahkan untuk input negatif.

Ini adalah versi yang dimodifikasi:

int sum_of_digits_squared(long n) {
    int s = 0;

    while (n != 0) {
        /* Since integer division is defined to truncate toward 0 in C99 and C++98 and later,
           the remainder of this division is positive for positive `n`
           and negative for negative `n`, and its absolute value is the last digit
           of the representation of `n` in base 10.
           Squaring this value yields the expected result for both positive and negative `c`.
           dividing `n` by 10 effectively drops the last digit in both cases.
           The loop will not be entered for `n == 0`, producing the correct result `s = 0`.
         */
        int c = n % 10;
        s += c * c;
        n /= 10;
    }
    return s;
}

Jawaban guru memiliki banyak kelemahan:

  • Tipe int mungkin memiliki kisaran nilai yang tidak memadai.
  • tidak perlu khusus nilai kasus 0 .
  • meniadakan nilai negatif tidak perlu dan mungkin memiliki perilaku tidak terdefinisi untuk n = INT_MIN.

Mengingat kendala tambahan dalam pernyataan masalah (C99 dan rentang nilai untuk n), hanya kelemahan pertama yang menjadi masalah. Kode tambahan masih menghasilkan jawaban yang benar.

Anda harus mendapatkan nilai bagus dalam tes ini, tetapi penjelasan diperlukan dalam tes tertulis untuk menunjukkan pemahaman Anda tentang masalah negatif n, jika tidak guru mungkin berasumsi bahwa Anda tidak sadar dan hanya beruntung. Dalam ujian lisan, Anda akan mendapat pertanyaan dan jawaban Anda akan berhasil.

chqrlie
sumber