Como somar números flutuantes no formato de horas e minutos?

14

Como somar números flutuantes no formato de horas e minutos?

Essa. se calcular dois desses números 0.35+3.45, deve ser = 4.20, não3.80

var arr = [0.15, 0.2, 3.45, 0.4, 2, 0.3, 5.2, 1, 1.4, 1.1, 2.4, 1, 3.4]
    
var sum = 0.0;
    
arr.forEach(function(item) {
  console.log(sum);
  sum += parseFloat(item);
});

O resultado deve ser assim:

0
0.15
0.35
4.20
5.0
7.0
7.30
12.50
13.50
15.30
16.40
19.20
20.20
user12916796
fonte
11
Como você definirá a lógica?
Juhil Somaiya
6
Então 0,2 representa 20 minutos ou 0,2 hora? 1.0 significa uma hora ou 0.6 significa uma hora?
Spangle
2
Desculpe, então 1,0 significa 1 hora e 0 minutos ?. O que significa 0.6 e 0.7, por exemplo?
Spangle
2
Você encontrará uma resposta aqui: codereview.stackexchange.com/a/109478 . Converta seus números em strings e, na função use "."para dividir essas strings, não ":".
Gerardo Furtado
6
Você se complica com essa lógica. 0.5horas devem ser de 30 minutos, qualquer outra coisa é completamente irracional. Espero que não seja para um cliente na vida real. Trabalhe com valores flutuantes de uma unidade de tempo fixa ou separe horas / minutos. Tão simples como isso.
Kaddath 19/02

Respostas:

7

A melhor maneira de lidar com formatos de dados "malucos" como esse é primeiro convertê-los em um formato sadio e facilmente processável, fazer o que for necessário usando os dados convertidos e finalmente (se for necessário) converter os resultados novamente para o formato original.

(Obviamente, a solução real é parar de usar essas representações de tempo totalmente loucas. Mas isso nem sempre é prático, por exemplo, porque você precisa interagir com um sistema legado que não pode mudar.)

No seu caso, um formato adequado para os seus dados seria, por exemplo, um número integral de minutos. Depois de converter seus dados para esse formato, você poderá fazer aritmética comum.

// converts a pseudo-float of the form hh.mm into an integer number of minutes
// robust against floating-point roundoff errors, also works for negative numbers
function hhMmToMinutes(x) {
  const hhmm = Math.round(100 * x)  // convert hh.mm -> hhmm
  return 60 * Math.trunc(hhmm / 100) + (hhmm % 100)
}

// convert minutes back to hh.mm pseudo-float format
// use minutesToHhMm(minutes).toFixed(2) if you want trailing zeros
function minutesToHhMm(minutes) {
  return Math.trunc(minutes / 60) + (minutes % 60) / 100
}

const arr = [0.15, 0.2, 3.45, 0.4, 2, 0.3, 5.2, 1, 1.4, 1.1, 2.4, 1, 3.4]

let sum = 0
console.log( arr.map(hhMmToMinutes).map(x => sum += x).map(minutesToHhMm) )

Observe que o código de conversão acima primeiro multiplica o valor flutuante da entrada por 100 e o arredonda para um número inteiro antes de separar as partes da hora e do minuto. Isso deve manipular de maneira robusta as entradas com possíveis erros de arredondamento, evitando a necessidade de especificar as entradas.

O motivo para ter cuidado extra aqui é porque o número 0,01 = 1/100 (e a maioria de seus múltiplos) não é exatamente representável no formato de ponto flutuante binário usado pelo JavaScript. Portanto, você não pode realmente ter um número JS que seria exatamente igual a 0,01 - o melhor que você pode ter é um número tão próximo que a conversão em uma string ocultará automaticamente o erro. Mas o erro de arredondamento ainda existe, e você pode mordê-lo se você tentar fazer coisas como comparar esses números com um limite exato. Aqui está uma demonstração agradável e simples:

console.log(Math.trunc(100 * 0.29))  // you'd think this would be 29...

Ilmari Karonen
fonte
7

// We got this after simple addition
// Now we want to change it into 4.2
sample = 3.8

// Now here are the minutes that the float currently shows
minutes = (sample % 1) * 100

// And the hours
hours = Math.floor(sample)

// Here are the number of hours that can be reduced from minutes
AddHours = Math.floor(minutes / 60)

// Adding them to the hours count
hours += AddHours

// Reducing mintues
minutes %= 60

// Finally formatting hour and minutes into your format
final = hours + (minutes / 100.0)
console.log(final)

Você pode usar essa lógica depois de fazer uma adição aritmética simples, isso converterá a soma em formato de hora

Letsintegreat
fonte
2
Por que ao math.floor(sample/1)invés de apenas math.floor(sample)?
selbie 19/02
2
@selbie, simplesmente porque eu adicionei apenas em primeiro lugar sample / 1, embora o JS seja o próprio piso porque não havia decimal no divisor (como em sample / 1.0, há muito tempo desde que o JS foi usado;), mas não funcionou, então procurei rapidamente no Google e adicionei floor, esqueci de remover isso. De qualquer forma obrigado por apontar que
Letsintegreat
2
Você deve mencionar que isso precisa ser feito para todos os elementos que arrnão estão no resultado. Por exemplo, se arr = [1.5, 2.5]o resultado será em 4vez de4.4
Titus
11
@ Titus, sim, eu perdi essa parte, por isso precisamos buscar horas e minutos de cada elemento e adicioná-los separadamente, para que apenas minha abordagem seja útil.
Letsintegreat
4

Aqui está, implementação em Javascript ES6. Passei um loop em cada elemento e divido horas, minutos por ., verifiquei se minutos não existem, ou seja, atribua 0 a contar o total de horas e minutos e apliquei a computação necessária.

const resultObj = [0.35, 3.45].reduce((a, e) => {
  const [hours, minutes] = e.toString().split('.');
  a.h += +hours;
  a.m += (+(minutes.padEnd(2, 0)) || 0); 
  return a;
}, {h: 0, m: 0});


const result = resultObj.h + Math.floor(resultObj.m / 60) + ((resultObj.m % 60) / 100);
console.log(result);

onecompileman
fonte
11
Não funcionará como está, por exemplo, 0,3 será contado apenas como três minutos, e não 30 minutos com a sua solução.
Spangle
3

Você precisa converter tudo em horas ou minutos e depois fazer as contas.

Example: 0.35 + 3.45
Convert 0.35 to Hours Number((35/60).toFixed(2))
= 0.58

Then convert 3hours 45 minutes into hours
= 3 + Number((45/60).toFixed(2))
= 3hours + 0.75 hours
= 3.75

Add the 2 conversions
= 0.58 + 3.75
= 4.33 hours

Convert the 0.33 back to minutes by multiplying by 60
= 4 hours 19.8 minutes ~ 20 mins

Hope that helps!
Kaustubh - KAY
fonte
2

Você precisa convertê-los para horas e minutos primeiro

var arr = [0.15, 0.2, 3.45, 0.4, 2, 0.3, 5.2, 1, 1.4, 1.1, 2.4, 1, 3.4];

function add(arr) {
  var hm = arr
    .map(v => [Math.floor(v), (v % 1).toFixed(2) * 100])  // Map the array to values having [hours, minutes]
    .reduce((t, v) => { //  reduce the array of [h, m] to a single object having total hours and minutes
      t = {
        h: t.h + v[0],
        m: t.m + v[1]
      };
      return t;
    }, {
      h: 0,
      m: 0
    });

  // final result would be = 
  // total hours from above
  // + hours from total minutes (retrived using Math.floor(hm.m / 60)) 
  // + minutes (after converting it to be a decimal part)
  return (parseFloat(hm.h + Math.floor(hm.m / 60)) + parseFloat(((hm.m % 60) / 100).toFixed(2))).toFixed(2);
}

// adding `arr` array
console.log(add(arr));

// adding 0.35+3.45
console.log(add([0.35, 3.45]));

Akash Shrivastava
fonte
2
Infelizmente, ninguém percebeu isso no começo. E, embora provavelmente correto, de onde vem o aprendizado? Copiar e colar do OP da sua solução ajuda, é claro, a curto prazo. Mas foi uma pergunta inteligente que merece uma explicação inteligente.
GetSet
2

Posso saber por que você deseja que o primeiro resultado seja 0 ? Abaixo está uma maneira de fazer isso com um único loop. Os comentários estão no código para explicar. A cada loop, adicionamos os minutos e, se forem maiores que 60, adicionamos uma hora e usamos % 60 para obter os minutos restantes. Flutuar pode ser uma dor de se trabalhar,

por exemplo, 1,1 + 2,2 === 3,3 é falso

Então, converti os números em uma string e depois voltei a um int.

var arr = [0.15, 0.2, 3.45, 0.4, 2, 0.3, 5.2, 1, 1.4, 1.1, 2.4, 1, 3.4];
let hoursTally = 0;
let minutesTally = 0;

for(var i = 0; i < arr.length; i++) {
  // Convert the float to a string, as floats can be a pain to add together
  let time = arr[i].toString().split('.');
  // Tally up the hours.
  hoursTally += parseInt(time[0]);
  // Tally up the minutes;
  let minute = time[1];
  if(minute) {
    // We do this so we add 20 minutes, not 2 minutes
    if(minute.length < 2) {
      minutesTally += parseInt(minute) * 10;
    } else {
      minutesTally += parseInt(minute);
    }
    // If the minutesTally is greater than 60, add an hour to hours
    if(minutesTally >= 60) {
      hoursTally++;
      // Set the minutesTally to be the remainder after dividing by 60
      minutesTally = minutesTally % 60;
    }
  }
  console.log(hoursTally + "." + minutesTally);
}

Spangle
fonte
uh .. por todos os meios .. 2.2 + 1.1 IS 3.3 com o significado de 3h30min
eagle275
11
toString().split('.');- argh, sério?
user253751 19/02
3
@ eagle275 Mas o número 1.1 + 2.2 não é 3.3 quando os números são de ponto flutuante. É 3.3000000000000001 ou 3.29999999999999 ou algo assim. Isso porque 1.1 não é realmente 1.1 e 2.2 não é realmente 2.2 e 3.3 não é realmente 3.3 no ponto flutuante.
user253751 19/02
não comece a dividir pêlos - eu não escolheria esse esquema de números estranho desde o início ... quando iniciei a programação, provavelmente converteria tudo em minutos (ou segundos, se necessário) - os converteria novamente para exibição .. mas hoje em dia DateTime regras nos meus olhos
eagle275
1

const arr = [0.15, 0.2, 3.45, 0.4, 2, 0.3, 5.2, 1, 1.4, 1.1, 2.4, 1, 3.4]

const reducer = (acc, curr) => {
  acc = `${acc}`.split('.').length > 1 ? acc : `${acc}.0`;
  curr = `${curr}`.split('.').length > 1 ? curr : `${curr}.0`;
  let hours = parseStrAndSum(true, `${acc}`.split('.')[0], `${curr}`.split('.')[0]);
  let minute = parseStrAndSum(false, `${acc}`.split('.')[1], `${curr}`.split('.')[1]);
  hours = parseInt(hours / 1) + parseInt(minute / 60);
  minute = minute % 60;
  console.log(`${acc} + ${curr} = ${hours}.${minute}`);
  return `${hours}.${minute}`;
}

const parseStrAndSum = (is_hours, ...strs) => {
  let result = 0;
  strs.forEach(function(number) {
    if (!is_hours) {
      number = number.length == 1 ? `${number}0` : number;
    }
    result += parseInt(number);
  });
  return result;
};

arr.reduce(reducer);

Ian
fonte
11
@lan 0.15 + 0.2 Por quê =0.17? Deve ser0.35
user12916796 19/02
11
Naquela época, eu pensava que o decimal 0,2 era igual a dois minutos em vez de 20 minutos. Agora que ajustei o código, confirme novamente.
Ian
1

A maneira padrão de reduzir um módulo é adicionar o déficit após o acréscimo se um estouro foi causado. É assim que você faria a adição do BCD usando hardware binário, por exemplo.

Embora você possa fazer acréscimos usando carros alegóricos dessa maneira, há uma questão de saber se você deveria . Os problemas com o uso de números flutuantes para operar em quantidades basicamente inteiras são bem conhecidos, e não vou reformulá-los aqui.

Normalmente, a adição em frações flutuantes funciona implicitamente com um módulo de 1,0, um excesso da fração incrementa a parte inteira. Se interpretarmos o ponto como o separador hours.minutes, queremos que a fração transborde em 0,6. Isso precisa que o déficit de 0,4 seja adicionado no momento apropriado.

function add_float_hours_minutes(a, b)
{
  var trial_sum = a+b;
  // did an actual overflow occur, has the integer part incremented?
  if ( Math.floor(trial_sum) > Math.floor(a)+Math.floor(b) )  {
    trial_sum += 0.4;
  }
  // has the fractional part exceeded its allotted 60 minutes?
  if ( trial_sum-Math.floor(trial_sum) >= 0.6)  {
    trial_sum += 0.4;
  }
  return trial_sum;
}

var arr = [0.15, 0.2, 3.45, 0.4, 2, 0.3, 5.2, 1, 1.4, 1.1, 2.4, 1, 3.4]

var sum = 0.0;

arr.forEach(function(item) {
  console.log(sum);
  // sum += parseFloat(item);
  sum = add_float_hours_minutes(sum, parseFloat(item));
});

que produz a seguinte saída, correta dentro de um arredondamento para o que o OP solicitou

0
0.15
0.35
4.2
5.000000000000001
7.000000000000001
7.300000000000001
12.5
13.5
15.3
16.400000000000002
19.2
20.2

Deixei a formatação final com duas casas decimais como exercício para o leitor. Os 15 dígitos à direita em toda a sua glória ilustram parte do motivo pelo qual usar carros alegóricos não é a melhor maneira de fazer isso.

Além disso, considere o que acontece quando você deseja incluir segundos ou dias. As duas maneiras mais comuns de segundos flutuantes ou uma tupla de números inteiros parecem subitamente muito mais sensatas.

Neil_UK
fonte