Comment additionner des nombres flottants au format heures et minutes?

Ceux. si vous calculez deux de ces nombres 0.35+3.45, ce devrait être = 4.20, pas 3.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
14
user12916796 19 févr. 2020 à 08:26

8 réponses

Meilleure réponse

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) reconvertir les résultats en le format d'origine.

(Bien sûr, la solution réelle consiste à cesser 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 et approprié 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 exactement représentable dans le 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 simple et agréable:

console.log(Math.trunc(100 * 0.29))  // you'd think this would be 29...
5
Ilmari Karonen 19 févr. 2020 à 15:47

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 effectuer des ajouts à l'aide de flottants de cette manière, la question est de savoir si vous devriez . Les problèmes liés à l'utilisation de nombres flottants pour fonctionner 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 dans 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.

0
Neil_UK 19 févr. 2020 à 16:24

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!
2
Kaustubh - KAY 19 févr. 2020 à 05:37

Voilà, implémentation en Javascript ES6. J'ai bouclé chaque élément et divisé les heures et les minutes par ., vérifié si les minutes sont inexistantes sinon attribuer 0 compter le total des heures et des minutes et appliquer 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);
3
onecompileman 19 févr. 2020 à 07:08
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);
0
Ian 19 févr. 2020 à 06:54

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]));
1
Akash Shrivastava 19 févr. 2020 à 05:58
// 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

6
Zlytherin 19 févr. 2020 à 05:47

Puis-je savoir pourquoi vous souhaitez 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);
}
1
Spangle 19 févr. 2020 à 06:58