Corrigindo um pequeno erro no calc.exe

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 DateDifferenceDateDifference 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) { // 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 } 

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.

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


All Articles