Как суммировать числа с плавающей точкой в ​​формате часов и минут?

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
user12916796
источник
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)

Вы можете использовать эту логику после выполнения простого арифметического сложения, которое преобразует сумму в формат времени

Letsintegreat
источник
2
Почему math.floor(sample/1)вместо просто math.floor(sample)?
Селби
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);

onecompileman
источник
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!
Kaustubh - KAY
источник
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('.');- правда, правда?
user253751
3
@ eagle275 Но число 1.1 + 2.2 не 3.3, когда числа с плавающей точкой. Это 3.3000000000000001 или 3.29999999999999 или что-то еще. Это потому, что 1.1 на самом деле не 1.1, а 2.2 на самом деле не 2.2, а 3.3 не совсем 3.3 в плавающей точке.
user253751
не начинайте расщеплять волосы - я не выбрал бы эту странную схему чисел с самого начала ... когда я начинал программировать, я, вероятно, преобразовал бы все в минуты (или секунды, если необходимо) - преобразовал бы их обратно для отображения .. но сегодня 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
user12916796
1
В то время я думал, что десятичная 0,2 была равна двум минутам вместо 20 минут. Теперь, когда я настроил код, пожалуйста, подтвердите снова.
Ян
1

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

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

Как правило, сложение дробных дробей работает неявно с модулем 1,0, переполнение дробной части увеличивает целочисленную часть. Если мы интерпретируем точку как разделитель hours.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 последних цифр во всей их красе иллюстрируют, почему использование поплавков - не лучший способ сделать это.

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

Neil_UK
источник