Jak sumować liczby zmiennoprzecinkowe w formacie godzin i minut?

14

Jak sumować liczby zmiennoprzecinkowe w formacie godzin i minut?

Te. jeśli obliczymy dwie takie liczby, 0.35+3.45to powinno być = 4.20, nie3.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);
});

Wynik powinien wyglądać następująco:

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
użytkownik12916796
źródło
1
Jak zdefiniujesz logikę?
Juhil Somaiya
6
Czy zatem 0,2 oznacza 20 minut, czy 0,2 godziny? Czy 1.0 oznacza godzinę, czy 0.6 oznacza godzinę?
Spangle
2
Przepraszamy, więc 1.0 oznacza 1 godzinę i 0 minut ?. Co na przykład oznaczają 0,6 i 0,7?
Spangle
2
Odpowiedź znajdziesz tutaj: codereview.stackexchange.com/a/109478 . Konwertuj swoje liczby na ciągi, a w funkcji użyj, "."aby podzielić te ciągi, a nie ":".
Gerardo Furtado
6
Komplikujesz się z taką logiką. 0.5godziny powinny wynosić 30 minut, wszystko inne jest całkowicie irracjonalne. Mam nadzieję, że nie jest to dla klienta w rzeczywistości. Albo pracuj z wartościami zmiennoprzecinkowymi ustalonej jednostki czasu, albo oddzielaj godziny / minuty. Tak proste jak to.
Kaddath

Odpowiedzi:

7

Najlepszym sposobem radzenia sobie z takimi „szalonymi” formatami danych jest najpierw przekonwertowanie ich na rozsądny i łatwy do przetworzenia format, zrób wszystko, co musisz zrobić, używając przekonwertowanych danych, a na koniec (jeśli absolutnie musisz) przekonwertować wyniki z powrotem na oryginalny format.

(Oczywiście, prawdziwym rozwiązaniem jest całkowite zaprzestanie używania tak zwariowanych reprezentacji czasu. Ale to nie zawsze jest praktyczne, np. Ponieważ musisz połączyć się ze starszym systemem, którego nie możesz zmienić).

W twoim przypadku odpowiednim rozsądnym formatem twoich danych będzie np. Całkowita liczba minut. Po przekonwertowaniu danych na ten format możesz wykonywać na nim zwykłą arytmetykę.

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

Zauważ, że powyższy kod konwersji zwielokrotnia liczbę zmiennoprzecinkową przez 100 i zaokrągla ją do liczby całkowitej przed oddzieleniem części godziny i minut. Powinno to skutecznie obsłużyć dane wejściowe z możliwymi błędami zaokrąglania, unikając przy tym konieczności ich usztywnienia.

Powodem, dla którego należy zachować szczególną ostrożność, jest to, że liczba 0,01 = 1/100 (i większość jej wielokrotności) nie jest właściwie dokładnie reprezentowana w binarnym formacie zmiennoprzecinkowym używanym przez JavaScript. Tak więc nie możesz faktycznie mieć liczby JS, która byłaby dokładnie równa 0,01 - najlepszą, jaką możesz mieć, jest liczba tak bliska, że ​​konwersja jej na ciąg automatycznie ukryje błąd. Ale błąd zaokrąglania nadal występuje i może cię ugryźć, jeśli spróbujesz zrobić takie rzeczy, jak porównanie takich liczb z dokładnym progiem. Oto ładna i prosta demonstracja:

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

Ilmari Karonen
źródło
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)

Możesz użyć tej logiki po prostym dodaniu arytmetycznym, co spowoduje konwersję sumy do formatu czasu

Letsintegreat
źródło
2
Dlaczego math.floor(sample/1)zamiast tylko math.floor(sample)?
selbie
2
@selbie, po prostu dlatego, że najpierw dodałem tylko sample / 1, myślałem, że JS sam to zrobi, ponieważ w dzielniku nie było miejsc po przecinku (jak za sample / 1.0dość długi czas, odkąd użyłem JS;), ale tak się nie stało, więc szybko googlowałem i dodałem floor, zapomniałem to usunąć. W każdym razie dzięki za wskazanie tego
Letsintegreat
2
Powinieneś wspomnieć, że należy to zrobić dla każdego elementu, którego arrnie ma w wyniku. Na przykład, jeśli arr = [1.5, 2.5]wynikiem będzie 4zamiast4.4
Tytus
1
@Titus, Tak, tęskniłem za tą częścią, więc musimy pobrać godziny i minuty z każdego elementu, a następnie dodać je osobno, wtedy tylko moje podejście będzie przydatne.
Letsintegreat
4

Proszę bardzo, wdrożenie w Javascript ES6. Zapętliłem każdy element i dzieliłem godziny, minuty ., sprawdziłem, czy minuty nie istnieją, przypisz 0 policz sumę godzin i minut i zastosowałem niezbędne obliczenia.

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
źródło
1
Nie będzie działać tak, jak jest, np. 0,3 będzie liczone jako trzy minuty, a nie 30 minut z twoim rozwiązaniem.
Spangle
3

Musisz przeliczyć wszystko na godziny lub minuty, a następnie wykonać obliczenia matematyczne.

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
źródło
2

Najpierw musisz przeliczyć je na godziny i minuty

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
źródło
2
Niestety na początku nikt tego nie zauważył. I choć prawdopodobnie poprawne, dokąd zmierza nauka? Skopiuj i wklej z OP swojego rozwiązania pomaga oczywiście w krótkim okresie. Ale to było inteligentne pytanie, które zasługuje na inteligentne wyjaśnienie.
GetSet
2

Czy mogę wiedzieć, dlaczego chcesz, aby pierwszy wynik wynosił 0 ? Poniżej znajduje się sposób na zrobienie tego za pomocą pojedynczej pętli. Komentarze są w kodzie do wyjaśnienia. Z każdą pętlą dodajemy minuty, a jeśli są one większe niż 60, dodajemy godzinę, a następnie używamy % 60, aby uzyskać pozostałe minuty. Pływaki mogą być uciążliwe w pracy,

np. 1.1 + 2.2 === 3.3 jest fałszem

Przekształciłem więc liczby na ciąg znaków, a następnie z powrotem na liczbę całkowitą.

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

Blaszka
źródło
uh .. za wszelką cenę .. 2.2 + 1.1 IS 3.3 w znaczeniu 3h30min
eagle275
1
toString().split('.');- argh, naprawdę?
user253751
3
@ eagle275 Ale liczba 1.1 + 2.2 nie wynosi 3.3, gdy liczby zmiennoprzecinkowe. To 3.3000000000000001 lub 3.29999999999999 lub coś takiego. To dlatego, że 1.1 tak naprawdę nie jest 1.1, a 2.2 nie jest tak naprawdę 2.2, a 3.3 nie jest tak naprawdę liczbą zmiennoprzecinkową 3.3.
user253751
nie zaczynaj dzielić włosów - nie wybrałbym tego dziwnego schematu liczb od samego początku ... kiedy zaczynałem programowanie, prawdopodobnie przekonwertowałem wszystko na minuty (lub sekundy, jeśli to konieczne) - przekonwertowałem je z powrotem na wyświetlanie ... ale teraz DateTime rządzi mi w oczach
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
źródło
1
@lan 0.15 + 0.2 Dlaczego =0.17? Musi być0.35
użytkownik12916796
1
W tym czasie myślałem, że dziesiętny 0,2 równa się dwie minuty zamiast 20 minut. Teraz, gdy dostosowałem kod, proszę potwierdzić ponownie.
Ian
1

Standardowym sposobem zmniejszenia modułu jest dodanie deficytu po dodaniu, jeśli nastąpiło przepełnienie. W ten sposób można dodawać BCD na przykład przy użyciu sprzętu binarnego.

Mimo, że można zrobić za pomocą pływaków dodatek w ten sposób, nie jest kwestia tego, czy powinien . Problemy z używaniem liczb zmiennoprzecinkowych do operowania na liczbach całkowitych są dobrze znane i nie zamierzam ich tutaj przerabiać.

Zwykle dodawanie ułamków swobodnych działa domyślnie z modułem 1,0, przelew z ułamka zwiększa część całkowitą. Jeśli interpretujemy punkt jako separator godzin. Minut, chcemy, aby ułamek przepełniał się przy 0,6. W tym celu należy dodać deficyt w wysokości 0,4.

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

co daje następujący wynik, popraw w zaokrągleniu do tego, o co poprosił PO

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

Ostateczne formatowanie zostawiłem do dwóch miejsc po przecinku jako ćwiczenie dla czytelnika. 15 końcowych cyfr w całej okazałości ilustruje, dlaczego używanie pływaków nie jest najlepszym sposobem na zrobienie tego.

Zastanów się także, co się stanie, jeśli chcesz uwzględnić sekundy lub dni. Dwa raczej bardziej standardowe sposoby albo sekund zmiennoprzecinkowych, albo krotkę liczb całkowitych, nagle wyglądają na znacznie rozsądniejsze.

Neil_UK
źródło