修复calc.exe中的一个小错误

周日,我像往常一样闲着,浏览Reddit。 滚动浏览小狗的乐趣和程序员的不良幽默,其中一篇特别的文章引起了我的注意。 这是calc.exe中的错误


Windows计算器中错误的日期范围计算

“好吧,这听起来像是一个奇怪的错误,我想知道是什么原因引起的,”我心想。 当然,周数会使该错误看起来像某种溢出或范围错误,这是典型的原因。 但是它总是被一些友好的宇宙邻居发出的高能射线所颠倒。

出于这个原因,我在这种情况下做了您要做的事情:我在计算机上尝试了一下,并发布了“一切对我有用”。 在我的机器上重复“ 7月31日-12月31日”的帖子后,情况得到了正确的结果:“ 5个月”。 但是经过一些测试,我发现“ 7月31日-12月30日”实际上会导致错误。 显示错误的值“ 5个月,613566756周,3天”。

我还没有完成程序的摇动,然后我想起:“哦,计算器不是微软为之打开源代码的那些东西之一吗?” 真的 。 这个错误不可能太复杂,所以我认为我会尝试找到它。 下载源非常简单,并且将所需的UWP工作负载添加到Visual Studio中也毫不费力。

您不习惯浏览不熟悉的代码库。 特别是当您想为发现错误的开源项目做出贡献时。 但是,对XAML或WinRT的无知当然不会使事情变得容易。

我打开了解决方案文件,并在“ Calculator”项目中进行了搜索,以查找与该错误相关的任何文件。 我找到了DateCalculator.xaml ,然后似乎适合将DateDiff_FromDate to DateCalculatorViewModel.cpp命名DateDiff_FromDate to DateCalculatorViewModel.cpp ,最后是DateCalculator.cpp

设置断点并查看一些变量后,我看到最终的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) 

这通常是同一件事。 但是问题出现了: “如果您在每月的第31天,下个月的30天,再加上一个月,那么您将去哪里?”

对于Windows.Globalization.Calendar.AddMonths(Int32),答案似乎是“ 30日”。

这意味着:
“ 7月31日+ 4个月= 11月30日”
“ 11月30日+ 1个月= 12月30日”
“ 7月31日+ 5个月= 12月31日”

因此,AddMonths操作既不是分布式的 (使用AddMonth乘法),也不是可交换的 ,也不是关联的 。 实际上应该是“加法”的运算。 使用时间和日历不是很有趣吗?

为什么在这种情况下,设置范围的错误会导致这么多的星期? 您可能已经猜到了,这是由于days_diff是无符号类型。 这将-1天变成大量,然后用几周的时间传递到周期的下一次迭代。 然后尝试通过减小current_guess而不是减小unsigned变量来纠正这种情况。

好吧,那是度过星期日的有趣方式。 我在Github上创建了一个带有最小“修复”的请求请求 。 我将“更正”放在引号中,因为现在的计算如下:



如果我们假设“ 7月31日+ 4个月= 11月30日”,我认为从技术上讲这是正确的结果。 尽管此选项并不完全符合人们对日期差异的直觉。 但是无论如何,这都没有错。

Source: https://habr.com/ru/post/zh-CN456684/


All Articles