¿Cómo sumar números flotantes en el formato de horas y minutos?

14

¿Cómo sumar números flotantes en el formato de horas y minutos?

Aquellos. si calcula dos de esos números, 0.35+3.45entonces debería ser = 4.20, no3.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);
});

El resultado debería ser así:

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
usuario12916796
fuente
1
¿Cómo definirás la lógica?
Juhil Somaiya
66
Entonces, ¿0.2 representa 20 minutos, o 0.2 de una hora? ¿1.0 significa una hora, o 0.6 significa una hora?
Spangle
2
Lo sentimos, ¿1.0 significa 1 hora y 0 minutos? ¿Qué significa 0.6 y 0.7 por ejemplo?
Spangle
2
Encontrarás una respuesta aquí: codereview.stackexchange.com/a/109478 . Convierta sus números en cadenas, y en la función use "."para dividir esas cadenas, no ":".
Gerardo Furtado
66
Te complicas con tal lógica. 0.5las horas deben ser de 30 minutos, cualquier otra cosa es completamente irracional. Espero que no sea para un cliente en la vida real. O trabaje con valores flotantes de una unidad de tiempo fija o separe horas / minutos. Tan simple como eso.
Kaddath

Respuestas:

7

La mejor manera de lidiar con formatos de datos "locos" como ese es primero convertirlos a un formato sensato y fácilmente procesable, hacer lo que sea necesario con los datos convertidos y finalmente (si es necesario) convertir los resultados nuevamente a El formato original.

(Por supuesto, la solución real es dejar de usar representaciones de tiempo tan locas por completo. Pero eso no siempre es práctico, por ejemplo, porque necesita interactuar con un sistema heredado que no puede cambiar).

En su caso, un formato adecuado adecuado para sus datos sería, por ejemplo, un número integral de minutos. Una vez que haya convertido sus datos a este formato, puede hacer aritmética ordinaria en él.

// 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) )

Tenga en cuenta que el código de conversión anterior multiplica primero el flotante de entrada por 100 y lo redondea a un entero antes de separar las partes de hora y minuto. Esto debería manejar de manera robusta las entradas con posibles errores de redondeo, evitando la necesidad de stringificar las entradas.

La razón para tener especial cuidado aquí es porque el número 0.01 = 1/100 (y la mayoría de sus múltiplos) en realidad no es exactamente representable en el formato binario de coma flotante utilizado por JavaScript. Por lo tanto, no puede tener un número JS que sea exactamente igual a 0.01; lo mejor que puede tener es un número tan cercano que convertirlo en una cadena ocultará automáticamente el error. Pero el error de redondeo sigue ahí y puede morderte si intentas hacer cosas como comparar esos números con un umbral exacto. Aquí hay una demostración agradable y simple:

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

Ilmari Karonen
fuente
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)

Puede usar esta lógica después de hacer una simple suma aritmética, esto convertirá la suma en formato de tiempo

Letsintegreat
fuente
2
¿Por qué en math.floor(sample/1)lugar de solo math.floor(sample)?
selbie
2
@selbie, simplemente porque en primer lugar agregué solo sample / 1, pensé que JS lo colocaría solo porque no había decimal en el divisor (como en sample / 1.0, hace mucho tiempo desde que usé JS;), pero no fue así, rápidamente busqué en Google y agregué floor, olvidé eliminar eso. De todos modos, gracias por señalar eso
Letsintegreat
2
Debe mencionar que esto debe hacerse para cada elemento que arrno está en el resultado. Por ejemplo, si arr = [1.5, 2.5]el resultado será en 4lugar de4.4
Tito
1
@Titus, sí, me perdí esa parte, por lo que necesitamos obtener horas y minutos de cada elemento, luego agregarlos por separado, luego solo mi enfoque será útil.
Letsintegreat
4

Aquí tienes, implementación en Javascript ES6. Hice un bucle de cada elemento y dividí las horas, minutos por minuto ., verifiqué si los minutos no existían; de lo contrario, asigne 0 cuenta el total de horas y minutos y apliqué el cálculo necesario.

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
fuente
1
No funcionará como está, por ejemplo, 0.3 solo se contará como tres minutos, no 30 minutos con su solución.
Spangle
3

Debe convertir todo en horas o en minutos y luego hacer los cálculos.

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
fuente
2

Primero debes convertirlos a horas y minutos

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
fuente
2
Lamentablemente, nadie se dio cuenta de esto al principio. Y aunque probablemente sea correcto, ¿de dónde viene el aprendizaje? Copiar y pegar desde el OP de su solución ayuda mucho, por supuesto, a corto plazo. Pero era una pregunta inteligente que merece una explicación inteligente.
GetSet
2

¿Puedo saber por qué quieres que el primer resultado sea 0 ? A continuación se muestra una manera de hacer esto con un solo bucle. Los comentarios están en el código para explicar. Con cada ciclo agregamos los minutos, y si son mayores de 60, agregamos una hora y luego usamos % 60 para obtener los minutos restantes. Las carrozas pueden ser una molestia para trabajar,

ej. 1.1 + 2.2 === 3.3 es falso

Así que convertí los números a una cadena y luego a un 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);
}

Lentejuela
fuente
uh ... por
supuesto
1
toString().split('.');- Argh, en serio?
user253751
3
@ eagle275 Pero el número 1.1 + 2.2 no es 3.3 cuando los números son de coma flotante. Es 3.3000000000000001 o 3.29999999999999 o algo así. Eso es porque 1.1 no es realmente 1.1 y 2.2 no es realmente 2.2 y 3.3 no es realmente 3.3 en coma flotante.
user253751
no comience a dividir los pelos: no elegiría este extraño esquema de números desde el principio ... cuando comencé la programación, probablemente habría convertido todo a minutos (o segundos si fuera necesario), conviértalos de nuevo para mostrarlos ... pero hoy en día DateTime gobierna en mis ojos
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
fuente
1
@lan 0.15 + 0.2 ¿ Por qué =0.17? Debe ser0.35
user12916796
1
En ese momento pensé que el decimal 0.2 era igual a dos minutos en lugar de 20 minutos. Ahora que he ajustado el código, confirme nuevamente.
Ian
1

La forma estándar de reducir un módulo es agregar el déficit después de la adición si se produjo un desbordamiento. Así es como haría la adición de BCD utilizando hardware binario, por ejemplo.

Aunque puede hacer la suma usando flotadores de esta manera, hay una cuestión de si debería hacerlo . Los problemas con el uso de números flotantes para operar en lo que son básicamente cantidades enteras son bien conocidos, y no los voy a repetir aquí.

Normalmente, la suma de fracciones flotantes funciona implícitamente con un módulo de 1.0, un desbordamiento de la fracción incrementa la parte entera. Si interpretamos el punto como el separador de horas.minutos, entonces queremos que la fracción se desborde en 0.6. Esto necesita que se agregue el déficit de 0.4 en el momento apropiado.

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 produce el siguiente resultado, correcto dentro de un redondeo a lo que solicitó el OP

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

Dejé el formato final a dos decimales como ejercicio para el lector. Los 15 dígitos finales en todo su esplendor ilustran parte de por qué usar flotadores no es la mejor manera de hacerlo.

Además, considere lo que sucede cuando desea incluir segundos o días. Las dos formas bastante más estándar de segundos flotantes, o una tupla de enteros, de repente parecen mucho más sensatas.

Neil_UK
fuente