Comment additionner des nombres flottants au format heures et minutes?

14

Comment additionner des nombres flottants au format heures et minutes?

Ceux. si calculer deux de ces nombres, 0.35+3.45alors il devrait être = 4.20, pas3.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);
});

Le résultat devrait être comme ceci:

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
la source
1
Comment définirez-vous la logique?
Juhil Somaiya
6
0,2 représente-t-il donc 20 minutes ou 0,2 heure? 1,0 signifie-t-il une heure ou 0,6 signifie-t-il une heure?
Spangle
2
Désolé, donc 1.0 signifie 1 heure et 0 minutes?. Que signifient par exemple 0,6 et 0,7?
Spangle
2
Vous trouverez une réponse ici: codereview.stackexchange.com/a/109478 . Convertissez vos nombres en chaînes, et dans la fonction utilisez "."pour diviser ces chaînes, non ":".
Gerardo Furtado
6
Vous vous compliquez avec une telle logique. 0.5les heures devraient être de 30 minutes, tout le reste est complètement irrationnel. J'espère que ce n'est pas pour un client dans la vraie vie. Soit vous travaillez avec des valeurs flottantes d'une unité de temps fixe, soit vous séparez les heures / minutes. Aussi simple que cela.
Kaddath

Réponses:

7

La meilleure façon de gérer des formats de données «fous» comme celui-ci est de les convertir d'abord en un format sain et facilement traitable, de faire tout ce que vous devez faire en utilisant les données converties et enfin (si vous devez absolument) de reconvertir les résultats en le format d'origine.

(Bien sûr, le vrai solution est d'arrêter complètement d'utiliser des représentations de temps aussi folles. Mais ce n'est pas toujours pratique, par exemple parce que vous devez vous interfacer avec un système hérité que vous ne pouvez pas changer.)

Dans votre cas, un format sain pour vos données serait par exemple un nombre entier de minutes. Une fois que vous avez converti vos données dans ce format, vous pouvez ensuite faire de l'arithmétique ordinaire dessus.

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

Notez que le code de conversion ci-dessus multiplie d'abord le flottant d'entrée par 100 et l'arrondit à un entier avant de séparer les parties heure et minute. Cela devrait gérer de manière robuste les entrées avec d'éventuelles erreurs d'arrondi tout en évitant d'avoir à filtrer les entrées.

La raison de prendre des précautions supplémentaires ici est que le nombre 0,01 = 1/100 (et la plupart de ses multiples) n'est pas réellement représentable exactement au format binaire à virgule flottante utilisé par JavaScript. Ainsi, vous ne pouvez pas réellement avoir un nombre JS qui serait exactement égal à 0,01 - le mieux que vous puissiez avoir est un nombre si proche que sa conversion en chaîne masquera automatiquement l'erreur. Mais l'erreur d'arrondi est toujours là et peut vous mordre si vous essayez de faire des choses comme comparer de tels nombres à un seuil exact. Voici une démonstration agréable et simple:

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

Ilmari Karonen
la source
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)

Vous pouvez utiliser cette logique après avoir fait un simple ajout arithmétique, cela convertira la somme au format d'heure

Permet d'intégrer
la source
2
Pourquoi math.floor(sample/1)au lieu de juste math.floor(sample)?
selbie
2
@selbie, tout simplement parce que je n'ai ajouté que d'abord sample / 1, je pensais que JS l'établirait lui-même car il n'y avait pas de décimale dans le diviseur (comme dans sample / 1.0JS depuis longtemps;), mais il ne l'a pas fait, j'ai donc rapidement googlé et ajouté floor, j'ai oublié de supprimer cela. Quoi qu'il en soit, merci d'avoir souligné que
Letsintegreat
2
Vous devez mentionner que cela doit être fait pour chaque élément et arrnon sur le résultat. Par exemple, si arr = [1.5, 2.5]le résultat sera 4au lieu de4.4
Titus le
1
@Titus, Oui, j'ai raté cette partie, nous devons donc extraire des heures et des minutes de chaque élément, puis les ajouter séparément, alors seule mon approche sera utile.
Letsintegreat
4

Voilà, implémentation en Javascript ES6. J'ai bouclé chaque élément et divisé les heures, les minutes par. , vérifié si les minutes sont inexistantes sinon attribuez 0 compte le total des heures et des minutes et appliqué le calcul nécessaire.

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
la source
1
Ne fonctionnera pas tel quel, par exemple 0,3 ne comptera que pour trois minutes et non 30 minutes avec votre solution.
Spangle
3

Vous devez tout convertir en heures ou en minutes, puis faire le calcul.

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
la source
2

Vous devez d'abord les convertir en heures et minutes

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
la source
2
Malheureusement, personne ne l'a remarqué au début. Et bien que probablement correct, où en est l'apprentissage? Copier et coller depuis OP de votre solution aide beaucoup bien sûr à court terme. Mais c'était une question intelligente qui mérite une explication intelligente.
GetSet
2

Puis-je savoir pourquoi vous voulez que le premier résultat soit 0 ? Ci-dessous est un moyen de le faire avec une seule boucle. Les commentaires sont dans le code pour expliquer. Avec chaque boucle, nous ajoutons les minutes, et si elles sont supérieures à 60, nous ajoutons une heure, puis utilisons % 60 pour obtenir les minutes restantes. Les flotteurs peuvent être pénibles à travailler,

par exemple 1.1 + 2.2 === 3.3 est faux

J'ai donc converti les nombres en chaîne, puis de nouveau en entier.

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);
}

Paillette
la source
euh .. par tous les moyens .. 2.2 + 1.1 EST 3.3 avec le sens de 3h30min
eagle275
1
toString().split('.');- argh, vraiment?
user253751
3
@ eagle275 Mais le nombre 1,1 + 2,2 n'est pas 3,3 lorsque les nombres sont en virgule flottante. C'est 3.3000000000000001 ou 3.29999999999999 ou quelque chose. C'est parce que 1.1 n'est pas vraiment 1.1 et 2.2 n'est pas vraiment 2.2 et 3.3 n'est pas vraiment 3.3 en virgule flottante.
user253751
ne commencez pas à diviser les cheveux - je ne choisirais pas ce schéma de nombres bizarre dès le début ... quand j'ai commencé la programmation, j'aurais probablement tout converti en minutes (ou secondes si nécessaire) - reconvertissez-les pour l'affichage .. mais de nos jours, DateTime règne à mes yeux
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
la source
1
@lan 0.15 + 0.2 Pourquoi =0.17? Doit être0.35
user12916796
1
À ce moment-là, je pensais que la décimale 0,2 était égale à deux minutes au lieu de 20 minutes. Maintenant que j'ai ajusté le code, veuillez confirmer à nouveau.
Ian
1

La manière standard de réduire un module est d'ajouter le déficit après l'ajout en cas de débordement. C'est ainsi que vous feriez l'ajout de BCD en utilisant du matériel binaire par exemple.

Bien que vous puissiez faire des ajouts en utilisant des flottants de cette manière, il y a une question de savoir si vous devriez . Les problèmes liés à l'utilisation de nombres flottants pour opérer sur des quantités fondamentalement entières sont bien connus, et je ne vais pas les ressasser ici.

Normalement, l'addition sur les fractions flottantes fonctionne implicitement avec un module de 1,0, un débordement hors de la fraction incrémente la partie entière. Si nous interprétons le point comme le séparateur heures.minutes, nous voulons que la fraction déborde à 0,6. Il faut pour cela ajouter le déficit de 0,4 au moment opportun.

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));
});

qui produit la sortie suivante, correcte à l'intérieur d'un arrondi à ce que le PO a demandé

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

J'ai laissé le formatage final à deux décimales comme exercice pour le lecteur. Les 15 chiffres de fin dans toute leur splendeur illustrent en partie pourquoi l'utilisation de flotteurs n'est pas la meilleure façon de le faire.

Réfléchissez également à ce qui se passe lorsque vous souhaitez inclure des secondes ou des jours. Les deux méthodes plutôt standard de flottement des secondes, ou d'un tuple d'entiers, semblent tout à coup beaucoup plus sensées.

Neil_UK
la source