إصلاح الخلل البسيط في calc.exe

في يوم الأحد ، كنت خاملاً كالعادة ، أتصفح موقع Reddit. التمرير من خلال جرو المرح وروح الفكاهة المبرمجين ، لفتت وظيفة واحدة انتباهي. كان خطأ في calc.exe .


حساب نطاق التاريخ غير صحيح في حاسبة ويندوز

"حسنًا ، يبدو ذلك خطأً غريبًا ، أتساءل ما الذي قد يسببه" ، فكرت في نفسي. عدد الأسابيع ، بطبيعة الحال ، يجعل الخطأ يبدو وكأنه نوع من تجاوز السعة أو خطأ في النطاق ، كما تعلمون ، لأسباب نموذجية. ولكن يمكن أن يكون دائمًا بعض الشيء المقلوب بواسطة بعض أشعة الطاقة العالية من بعض الجيران الكونية الودية.

مهتمًا بالسبب ، فعلت ما تفعله في مثل هذه الحالات: لقد جربته على الجهاز الخاص بي لنشر "كل شيء يناسبني". وتكرار الوضع من آخر "31 يوليو - 31 ديسمبر" على الجهاز الخاص بي أعطى النتيجة الصحيحة "5 أشهر". ولكن بعد الاختبار قليلاً ، وجدت أن "31 يوليو - 30 ديسمبر" في الواقع يسبب خطأ. يتم عرض القيمة غير الصحيحة "5 أشهر ، 613566756 أسابيع ، 3 أيام".

لم أكن قد انتهيت من اهتزاز البرنامج ، ثم تذكرت: "أوه ، أليس الآلة الحاسبة واحدة من تلك الأشياء التي فتحت Microsoft المصدر لها؟" و حقا لا يمكن أن يكون هذا الخطأ معقدًا للغاية ، لذلك اعتقدت أنني سأحاول العثور عليه. كان تنزيل المصادر بسيطًا جدًا ، كما أن إضافة عبء العمل المطلوب لـ UWP إلى Visual Studio لم يجرِ أيضًا أي عوائق.

يعد التنقل بين الأكواد البرمجية التي لا تعرفها شيئًا معتادًا عليه مع مرور الوقت. خاصةً عندما تريد المساهمة في مشاريع مفتوحة المصدر حيث تجد الخطأ. ومع ذلك ، فإن الجهل بـ XAML أو WinRT ، بالطبع ، لا يجعل الأمور أسهل.

لقد فتحت ملف الحل ونظرت في مشروع "الحاسبة" للبحث عن أي ملف يجب أن يكون مرتبطًا بالأخطاء. لقد وجدت DateCalculator.xaml ، ثم يبدو أنه مناسب لاسم DateDiff_FromDate to DateCalculatorViewModel.cpp ، وأخيراً DateCalculator.cpp .

بعد تعيين نقطة توقف والبحث في بعض المتغيرات ، رأيت أن قيمة DateDifference النهائية DateDifference صحيحة بالفعل. بمعنى أنه لم يكن مجرد خطأ في التحويل إلى سلسلة ، ولكنه كان خطأ في الحساب الفعلي.

الحساب الفعلي في رمز زائف مبسط يبدو مثل هذا:

 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 } 

تبدو جيدة. لا توجد مشاكل في المنطق. في الأساس ، تقوم الوظيفة بما يلي:

  • التهم سنوات كاملة من تاريخ البدء
  • من تاريخ آخر سنة كاملة بحساب الأشهر
  • التهم أسابيع من تاريخ آخر شهر كامل
  • من تاريخ الأسبوع الكامل الأخير بحساب الأيام المتبقية

في الواقع ، فإن المشكلة هي افتراض أن بداية متسلسلة

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

يساوي

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

هذا هو عادة نفس الشيء. لكن السؤال الذي يطرح نفسه هو: "إذا وصلت إلى اليوم الحادي والثلاثين من الشهر ، أي في الشهر التالي البالغ 30 يومًا ، فأنت تضيف شهرًا واحدًا ، فأين ستذهب؟"

يبدو أنه بالنسبة لنظام Windows.Globalization.Calendar.AddMonths (Int32) ، ستكون الإجابة "في الثلاثين".

وهذا يعني أن:
"31 يوليو + 4 أشهر = 30 نوفمبر"
"30 نوفمبر + شهر = 30 ديسمبر"
"31 يوليو + 5 أشهر = 31 ديسمبر"

وبالتالي ، فإن عملية AddMonths ليست توزيعية (مع الضرب AddMonth) ، ولا تبادلي ، ولا نقابي. في الواقع ما ينبغي أن يكون تشغيل "الجمع". أليس متعة العمل مع الوقت والتقويمات؟

لماذا في هذه الحالة ، الخطأ في تحديد النطاق يؤدي إلى هذا العدد الهائل من الأسابيع؟ كما كنت قد خمنت ، وهذا يرجع إلى حقيقة أن days_diff هو نوع غير موقع. يتحول هذا إلى -1 أيام إلى كمية ضخمة ، يتم بعد ذلك الانتقال إلى التكرار التالي للدورة مع أسابيع. الذي يحاول بعد ذلك تصحيح الموقف عن طريق تقليل المتغير الحالي لكن دون تقليل المتغير غير الموقع.

حسنًا ، كانت هذه طريقة رائعة لقضاء يوم الأحد. لقد قمت بإنشاء طلب سحب على جيثب مع الحد الأدنى من "الإصلاح". أضع "التصحيح" بين علامات اقتباس ، لأن الحساب يبدو الآن كما يلي:



أعتقد أن هذه هي النتيجة الصحيحة تقنيًا ، إذا افترضنا أن "31 يوليو + 4 أشهر = 30 نوفمبر". على الرغم من أن هذا الخيار لا يتوافق تمامًا مع الحدس البشري حول الفرق في التواريخ. ولكن على أي حال ، هذا أقل خطأ مما كان عليه.

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


All Articles