Memperbaiki bug kecil di calc.exe

Pada hari Minggu, saya diam seperti biasa, browsing melalui Reddit. Menggulir melalui kesenangan anak anjing dan humor buruk programmer, satu posting tertentu menarik perhatian saya. Itu adalah bug di calc.exe .


Penghitungan rentang tanggal salah dalam Windows Calculator

"Yah, itu kedengarannya seperti kesalahan yang aneh, aku ingin tahu apa yang menyebabkannya," aku berpikir dalam hati. Jumlah minggu, tentu saja, membuat bug terlihat seperti semacam kesalahan overflow atau range, Anda tahu, alasan khas. Tapi itu selalu bisa sedikit dibalik oleh sinar berenergi tinggi dari tetangga kosmik yang ramah.

Karena tertarik pada alasannya, saya melakukan apa yang Anda lakukan dalam kasus-kasus seperti itu: Saya mencobanya di komputer saya untuk memposting "Semuanya bekerja untuk saya." Dan pengulangan situasi dari pos "31 Juli - 31 Desember" di komputer saya memberikan hasil yang benar dari "5 bulan". Tetapi setelah menguji sedikit, saya menemukan bahwa "31 Juli - 30 Desember" sebenarnya menyebabkan kesalahan. Nilai yang salah "5 bulan, 613566756 minggu, 3 hari" ditampilkan.

Saya belum selesai mengguncang program, dan kemudian saya ingat: "Oh, bukankah kalkulator adalah salah satu dari hal-hal yang Microsoft telah membuka sumbernya?" Dan sungguh . Kesalahan ini tidak bisa terlalu rumit, jadi saya pikir saya akan berusaha menemukannya. Mengunduh sumbernya cukup sederhana, dan menambahkan beban kerja UWP yang diperlukan ke Visual Studio juga berjalan tanpa hambatan.

Menavigasi basis kode yang tidak Anda kenal adalah sesuatu yang terbiasa dengan waktu. Terutama ketika Anda ingin berkontribusi pada proyek open source di mana Anda menemukan bug. Namun, ketidaktahuan XAML atau WinRT, tentu saja, tidak membuat segalanya menjadi lebih mudah.

Saya membuka file solusi dan melihat ke dalam proyek "Kalkulator" untuk mencari file yang terkait dengan bug. Saya menemukan DateCalculator.xaml , maka sepertinya cocok untuk nama DateDiff_FromDate to DateCalculatorViewModel.cpp dan, akhirnya, DateCalculator.cpp .

Setelah menetapkan breakpoint dan melihat beberapa variabel, saya melihat bahwa nilai DateDifference akhir sudah salah. Artinya, itu bukan hanya kesalahan konversi menjadi string, tetapi kesalahan perhitungan yang sebenarnya.

Perhitungan aktual dalam pseudo-code sederhana terlihat seperti ini:

 DateDifference calculate_difference(start_date, end_date) { uint[] diff_types = [year, month, week, day] uint[] typical_days_in_type = [365, 31, 7, 1] uint[] calculated_difference = [0, 0, 0, 0] date temp_pivot_date date pivot_date = start_date uint days_diff = calculate_days_difference(start_date, end_date) for(type in differenceTypes) { temp_pivot_date = pivot_date uint current_guess = days_diff /typicalDaysInType[type] if(current_guess !=0) pivot_date = advance_date_by(pivot_date, type, current_guess) int diff_remaining bool best_guess_hit = false do{ diff_remaining = calculate_days_difference(pivot_date, end_date) if(diff_remaining < 0) { // pivotDate has gone over the end date; start from the beginning of this unit current_guess = current_guess - 1 pivot_date = temp_pivot_date pivot_date = advance_date_by(pivot_date, type, current_guess) best_guess_hit = true } else if(diff_remaining > 0) { // pivot_date is still below the end date if(best_guess_hit) break; current_guess = current_guess + 1 pivot_date = advance_date_by(pivot_date, type, 1) } } while(diff_remaining!=0) temp_pivot_date = advance_date_by(temp_pivot_date, type, current_guess) pivot_date = temp_pivot_date calculated_difference[type] = current_guess days_diff = calculate_days_difference(pivot_date, end_date) } calculcated_difference[day] = days_diff return calculcated_difference } 

Itu terlihat baik. Tidak ada masalah dalam logika. Pada dasarnya, fungsi melakukan hal berikut:

  • dihitung tahun penuh sejak tanggal mulai
  • dari tanggal tahun penuh terakhir dihitung bulan
  • dihitung minggu dari tanggal bulan penuh terakhir
  • dari tanggal minggu penuh terakhir menghitung hari yang tersisa

Sebenarnya, masalahnya adalah asumsi bahwa mulai berurutan

 date = advance_date_by(date, month, somenumber) date = advance_date_by(date, month, 1) 

sama dengan

 date = advance_date_by(date, month, somenumber + 1) 

Ini biasanya hal yang sama. Tetapi muncul pertanyaan: "Jika Anda mencapai hari ke-31 bulan itu, bulan berikutnya 30 hari, Anda menambahkan satu bulan, lalu ke mana Anda akan pergi?"

Sepertinya untuk Windows.Globalization.Calendar.AddMonths (Int32) jawabannya adalah "pada tanggal 30".

Dan ini berarti:
โ€œ31 Juli + 4 bulan = 30 Novemberโ€
"30 November + 1 bulan = 30 Desember"
โ€œ31 Juli + 5 bulan = 31 Desemberโ€

Dengan demikian, operasi AddMonths tidak distributif (dengan AddMonth-multiplikasi), atau komutatif , atau asosiatif . Apa yang sebenarnya harus menjadi operasi "penambahan". Bukankah menyenangkan bekerja dengan waktu dan kalender?

Mengapa dalam kasus ini, kesalahan pengaturan rentang menyebabkan sejumlah besar minggu? Seperti yang mungkin sudah Anda duga, ini disebabkan oleh fakta bahwa days_diff adalah tipe yang tidak ditandatangani. Ini mengubah -1 hari menjadi jumlah yang sangat besar, yang kemudian diteruskan ke iterasi siklus berikutnya dengan minggu. Yang kemudian mencoba untuk memperbaiki situasi dengan mengurangi current_guess tetapi tidak mengurangi variabel yang tidak ditandatangani.

Ya, itu cara yang menarik untuk menghabiskan hari Minggu. Saya membuat permintaan tarik di Github dengan "perbaikan" minimal. Saya meletakkan "koreksi" dalam tanda kutip, karena sekarang perhitungannya terlihat seperti ini:



Saya pikir secara teknis ini adalah hasil yang benar, jika kita asumsikan bahwa "31 Juli + 4 bulan = 30 November." Meskipun opsi ini tidak sepenuhnya konsisten dengan intuisi manusia tentang perbedaan tanggal. Tetapi bagaimanapun juga, ini tidak salah seperti sebelumnya.

Source: https://habr.com/ru/post/id456684/


All Articles