No domingo, eu estava ocioso como sempre, navegando no Reddit. Percorrendo a diversão de cachorros e o mau humor dos programadores, um post em particular chamou minha atenção. Foi um
erro no calc.exe .
Cálculo incorreto do período na Calculadora do Windows"Bem, isso parece um erro curioso, eu me pergunto o que pode causar isso", pensei comigo mesma. O número de semanas, é claro, faz com que o bug pareça algum tipo de erro de estouro ou alcance, você sabe, razões típicas. Mas sempre pode ser um pouco invertido por algum raio de alta energia de algum vizinho cósmico amigável.
Por estar interessado no motivo, fiz o que você faz nesses casos: tentei na minha máquina publicar "Tudo funciona para mim". E a repetição da situação do post “31 de julho a 31 de dezembro” na minha máquina deu o resultado correto de “5 meses”. Mas, depois de testar um pouco, descobri que "31 de julho a 30 de dezembro" realmente causa um erro. O valor incorreto "5 meses, 613566756 semanas, 3 dias" é exibido.
Não terminei de agitar o programa e lembrei-me: "Oh, a calculadora não é uma daquelas coisas pelas quais a Microsoft abriu a fonte?"
E realmente . Esse erro não poderia ser muito complicado, então pensei em tentar encontrá-lo. O download das fontes foi bastante simples e a adição da carga de trabalho UWP necessária ao Visual Studio também ocorreu sem problemas.
Navegar nas bases de código com as quais você não está familiarizado é algo com o qual você se acostuma. Especialmente quando você deseja contribuir para projetos de código aberto onde você encontra o erro. No entanto, a ignorância de XAML ou WinRT, é claro, não facilita as coisas.
Abri o arquivo da solução e procurei no projeto “Calculadora” em busca de qualquer arquivo que deveria estar relacionado ao bug. Encontrei
DateCalculator.xaml
, então parece ser adequado para o nome
DateDiff_FromDate to DateCalculatorViewModel.cpp
e, finalmente,
DateCalculator.cpp
.
Depois de definir um ponto de interrupção e examinar algumas variáveis, vi que o valor final
DateDifference
já
DateDifference
incorreto. Ou seja, não foi apenas um erro de conversão em uma sequência, mas um erro do cálculo real.
O cálculo real em um pseudocódigo simplificado é mais ou menos assim:
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) {
Parece bom. Não há problemas na lógica. Essencialmente, a função faz o seguinte:
- conta anos completos a partir da data de início
- a partir da data do último ano inteiro conta meses
- conta semanas a partir da data do último mês completo
- a partir da data da última semana completa conta os dias restantes
Na verdade, o problema é a suposição de que o início seqüencial
date = advance_date_by(date, month, somenumber) date = advance_date_by(date, month, 1)
é igual a
date = advance_date_by(date, month, somenumber + 1)
Geralmente é a mesma coisa. Mas surge a pergunta:
"Se você atingir o 31º dia do mês, o próximo mês 30 dias, você adiciona um mês, então para onde irá?"Parece que para
Windows.Globalization.Calendar.AddMonths (Int32) a resposta será "no dia 30".
E isso significa que:
"31 de julho + 4 meses = 30 de novembro"
"30 de novembro + 1 mês = 30 de dezembro"
"31 de julho + 5 meses = 31 de dezembro"
Portanto, a operação AddMonths não é
distributiva (com multiplicação AddMonth), nem
comutativa , nem
associativa . O que realmente deve ser a operação de "adição". Não é divertido trabalhar com horários e calendários?
Por que, neste caso, o erro de definir o intervalo leva a um número tão grande de semanas? Como você deve ter adivinhado, isso se deve ao fato de
days_diff
ser um tipo não assinado. Isso transforma -1 dias em uma quantidade enorme, que é passada para a próxima iteração do ciclo com semanas. O qual tenta corrigir a situação diminuindo o
current_guess
mas não a variável não assinada.
Bem, essa foi uma maneira interessante de passar o domingo. Criei uma
solicitação de recebimento no Github com uma "correção" mínima. Coloquei a “correção” entre aspas, porque agora o cálculo se parece com o seguinte:

Eu acho que tecnicamente esse é o resultado correto, se assumirmos que "31 de julho + 4 meses = 30 de novembro". Embora essa opção não seja totalmente consistente com a intuição humana sobre a diferença de datas. Mas, de qualquer forma, isso é menos errado do que era.