Як підсумовувати плаваючі числа у форматі годин і хвилин?


14

Як підсумовувати плаваючі числа у форматі годин і хвилин?

Ті. якщо обчислити два таких числа, 0.35+3.45то воно повинно бути = 4.20, не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);
});

Результат повинен бути таким:

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

1
Як ви визначите логіку?
Джухіл Сомая

6
Так 0,2 означає 20 хвилин, або 0,2 години? 1,0 означає годину, або 0,6 означає годину?
Спанч

2
Вибачте, так 1,0 означає 1 годину і 0 хвилин ?. Що наприклад означає 0,6 та 0,7?
Спанч

2
Відповідь ви знайдете тут: codereview.stackexchange.com/a/109478 . Перетворіть ваші числа в рядки, а в функції використовуйте "."для розділення цих рядків, а не ":".
Джерардо Фуртадо

6
Ви ускладнюєте себе такою логікою. 0.5годин має бути 30 хвилин, все інше - абсолютно нераціональне. Я сподіваюся, що це не для клієнта в реальному житті. Або працюйте з плаваючими значеннями фіксованої одиниці часу, або ви розділяєте години / хвилини. Так просто.
Каддат

Відповіді:


7

Найкращий спосіб поводитися з такими "божевільними" форматами даних, як спочатку перетворити їх у здоровий та легко оброблюваний формат, виконайте все, що вам потрібно зробити, використовуючи перетворені дані і, нарешті, (якщо вам це потрібно) конвертувати результати назад у оригінальний формат.

(Звичайно, справжнє рішення - припинити використання таких шалених уявлень часу повністю. Але це не завжди практично, наприклад, тому що вам потрібно взаємодіяти зі застарілою системою, яку ви не можете змінити.)

У вашому випадку відповідним здоровим форматом для ваших даних буде, наприклад, цілісна кількість хвилин. Після перетворення даних у цей формат ви зможете виконувати звичайну арифметику на ньому.

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

Зауважте, що код перетворення вище, перш за все, помножує вхідний поплавок на 100 і округляє його до цілого числа, перш ніж розділяти частину години та хвилини. Це повинно надійно обробляти входи з можливими помилками округлення, уникаючи необхідності впорядковувати входи.

Причина проявляти особливу обережність тут полягає в тому, що число 0,01 = 1/100 (і більшість його кратних) насправді не є точно представним у двійковому форматі з плаваючою комою, що використовується JavaScript. Таким чином , ви не можете на самому справі є ряд JS , який буде точно дорівнює 0,01 - краще , що ви можете мати це число , яке так близько , що перетворення його в рядок автоматично приховати помилку. Але помилка округлення все ж є, і може вкусити вас, якщо ви спробуєте виконати такі дії, як порівняння таких чисел із точним порогом. Ось приємна і проста демонстрація:

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


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)

Ви можете використовувати цю логіку після простого арифметичного додавання, це перетворить суму у формат часу


2
Чому math.floor(sample/1)замість просто math.floor(sample)?
selbie

2
@selbie, просто тому, що я по-перше додав лише те sample / 1, що я, хоча JS прокладе це сам, тому що в делителях не було десятків (як sample / 1.0, наприклад, досить давно, коли використовувався JS;), але це не так, я швидко погуглив і додав floor, забув видалити це. Все-таки спасибі за вказівку на це
Letsintegreat

2
Ви повинні зазначити, що це потрібно зробити для кожного елемента, який arrне має результату. Наприклад, якщо arr = [1.5, 2.5]результат буде 4замість4.4
Тит

1
@Titus, Так, я пропустив цю частину, тому нам потрібно отримати години та хвилини з кожного елемента, а потім додати їх окремо, тоді корисний буде лише мій підхід.
Letsintegreat

4

Ось вам і реалізація в Javascript ES6. Я зациклився на кожному елементі і розділив години, хвилини ., перевірив, чи немає хвилин, інакше присвоїти 0 рахувати загальну кількість годин і хвилин і застосувати необхідні обчислення.

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


1
Не працюватиме так, як є, наприклад, 0,3 вважатиметься лише трьома хвилинами, а не 30 хвилин вашим рішенням.
Спанч

3

Вам потрібно перетворити все на години або хвилини, а потім зробити математику.

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

Ви повинні спочатку їх перетворити на години та хвилини

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


2
На жаль, спочатку ніхто цього не помітив. І хоча, мабуть, правильно, куди йде навчання? Копіювати та вставляти з OP вашого рішення допомагає багато звичайно в короткому періоді. Але це було розумне питання, яке заслуговує на розумне пояснення.
GetSet

2

Чи можу я знати, чому ви хочете, щоб перший результат був 0 ? Нижче наведено спосіб зробити це за допомогою однієї петлі. Коментарі в коді для пояснення. Кожною петлею додаємо хвилини, і якщо їх більше 60, ми додаємо годину, а потім використовуємо % 60, щоб отримати інші хвилини. Поплавці можуть бути болем для роботи,

наприклад, 1,1 + 2,2 === 3,3 помилково

Тож я перетворив числа в рядок, а потім назад до 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);
}


е .. всіма силами .. 2,2 + 1,1 IS 3,3 із значенням 3h30min
eagle275

1
toString().split('.');- Так, справді?
користувач253751

3
@ eagle275 Але число 1,1 + 2,2 не є 3,3, коли числа мають плаваючу крапку. Це 3.3000000000000001 або 3.29999999999999 чи щось таке. Це тому, що 1,1 насправді не 1,1 і 2,2 насправді не 2,2, а 3,3 не є дійсно 3,3 у плаваючому крапці.
користувач253751

не починайте розщеплювати волосся - я б не вибирав цю дивну схему числення з самого початку ... коли я розпочав програмування, я, мабуть, перетворив би все на хвилини (або секунди, якщо потрібно) - перетворити їх назад для відображення .. але в наш час правила DateTime в моїх очах
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);


1
@lan 0.15 + 0.2 Чому =0.17? Повинен бути0.35
користувач12916796

1
Тоді я подумав, що десяткова 0,2 дорівнює двом хвилинам замість 20 хвилин. Тепер, коли я відкоригував код, підтвердьте його ще раз.
Ян

1

Стандартний спосіб зменшити модуль - це додавання дефіциту після додавання, якщо викликано переповнення. Ось як можна зробити додавання BCD, використовуючи, наприклад, двійкове обладнання.

Хоча ви можете зробити доповнення, використовуючи поплавці таким чином, є питання, чи варто . Проблеми із застосуванням плаваючих чисел для роботи над загально цілими величинами добре відомі, і я не збираюся їх повторно переробляти тут.

Зазвичай додавання на поплавкові дроби функціонує неявно з модулем 1,0, переповнення фракції збільшує цілу частину. Якщо ми інтерпретуємо точку як роздільник годин.minutes, тоді ми хочемо, щоб частка переливалася на рівні 0,6. Для цього потрібно дефіцит 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));
});

який дає наступний висновок, правильний для округлення до того, про що вимагав ОП

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

Остаточне форматування я залишив у двох десяткових знаках як вправу для читача. 15 кінцевих цифр у всій красі ілюструють частину того, чому використання плавців не найкращий спосіб зробити це.

Також врахуйте, що відбувається, коли ви хочете включити секунди чи дні. Два радше стандартних способу або плаваючих секунд, або набору цілих чисел, раптом виглядають набагато розумнішими.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.