Форматирование таймеров без потери точности?
У меня есть массив времени начала/остановки. Я в основном хочу отображать время, которое требуется для каждой записи, а также общее время для всех из них. Вот код, который я написал, чтобы попытаться сделать это:
function timeFormatter (milliseconds) {
const padZero = (time) => `0${time}`.slice(-2);
const minutes = padZero(milliseconds / 60000 | 0);
const seconds = padZero((milliseconds / 1000 | 0) % 60);
const centiseconds = padZero((milliseconds / 10 | 0) % 100);
return `${minutes} : ${seconds} . ${centiseconds}`;
}
// Example stopwatch times
const timeIntervals = [
{ startTime: 1470679294008, stopTime: 1470679300609 },
{ startTime: 1470679306278, stopTime: 1470679314647 },
{ startTime: 1470679319718, stopTime: 1470679326693 },
{ startTime: 1470679331229, stopTime: 1470679336420 }
];
// Calculate time it took for each entry
const times = timeIntervals.map(time => time.stopTime - time.startTime);
// Run the timeFormatter on each individual time
const individualTimes = times.map(timeFormatter);
// Run the timeFormatter on the sum of all the times
const mainTimer = timeFormatter(times.reduce((a, b) => a + b));
/**
* [
* '00 : 06 . 60',
* '00 : 08 . 36',
* '00 : 06 . 97',
* '00 : 05 . 19'
* ]
*/
console.log(individualTimes);
/**
* 00 : 27 . 13
*/
console.log(mainTimer);
Ответы
Ответ 1
Проблема
Вы теряете точность с каждым отображением округленного времени.
Чем больше кругов у вас, тем больше проблема может быть:
╔══════╦════════════════════════════════╗
║ Lap ║ Total time (ms) ║
║ Time ╠═══════╦═════════════╦══════════╣
║ (ms) ║ JS ║ Real World ║ Display ║
╠══════╬═══════╬═════════════╬══════════╣
║ 3157 ║ 3157 ║ 3157.5±0.5 ║ 3160±5 ║
║ 2639 ║ 5796 ║ 5797.0±1 ║ 5800±10 ║
║ 3287 ║ 9083 ║ 9084.5±1.5 ║ 9090±15 ║
║ 3106 ║ 12189 ║ 12191.0±2 ║ 12200±20 ║
╚══════╩═══════╩═════════════╩══════════╝
Различные итоговые значения фактически перекрывают друг друга, как только вы учитываете допуски:
- JS time = 12189
- Фактическое время = 12189 - 12193
- Отображаемое время = 12180 to 12240
Другими словами, путем добавления отображаемого времени также добавляется потерянная отображаемая точность.
Неспособность учитывать это, потерянное человеком, является реальной проблемой.
Решение
- Увеличьте отображаемую точность (если вы не округлите цифры, вы ничего не потеряете)
- Используйте отображаемую сумму, которая менее точна, но не является неправильной, если вы также указали допуск (±).
- Обнаружение проблемы и отображение сообщения.
Ниже приведена демонстрация решения 3.
function run ( set ) {
show ( set === 0
? // Good set
[ { startTime: 1470679294008, stopTime: 1470679300609 },
{ startTime: 1470679306278, stopTime: 1470679314647 },
{ startTime: 1470679319718, stopTime: 1470679326693 },
{ startTime: 1470679331229, stopTime: 1470679336420 } ]
: // Bad set
[ { startTime: 1472104779284, stopTime: 1472104782441 },
{ startTime: 1472104782442, stopTime: 1472104785081 },
{ startTime: 1472104785081, stopTime: 1472104788368 },
{ startTime: 1472104788369, stopTime: 1472104791475 }, ] );
}
function show ( timeIntervals ) {
const sum = (a, b) => a + b;
const roundTime = (ms) => Math.round(ms/10);
function timeFormatter (centi) {
const padZero = (time) => `0${~~time}`.slice(-2);
const minutes = padZero(centi / 6000);
const seconds = padZero((centi / 100) % 60);
const centiseconds = padZero(centi % 100);
return `${minutes} : ${seconds} . ${centiseconds} `;
}
// Calculate time it took for each entry.
const times = timeIntervals.map(time => time.stopTime - time.startTime);
// Rou and run the timeFormatter on each individual time
const roundedTimes = times.map(roundTime);
const individualTimes = roundedTimes.map(timeFormatter);
// Calculate sum of displayed time
const displayedSum = roundedTimes.reduce(sum);
// Sum time and run timeFormatter
const totalTime = roundTime( times.reduce(sum) );
const mainTimer = timeFormatter(totalTime);
let html = '<ol><li>' + individualTimes.join('<li>') + '</ol>Sum: ' + mainTimer;
// Show warning if sum of rounded time is different.
if ( displayedSum !== totalTime )
html += ' (Rounding error corrected)';
document.querySelector('div').innerHTML = html;
}
run(1);
<button onclick='run(0)'>Perfect</button>
<button onclick='run(1)'>Opps</button>
<div></div>
Ответ 2
Невозможно достичь того, чего вы хотите. В основном вы ожидаете получить тот же результат, когда вы округлите два числа и добавите их, и когда вы сначала добавите числа, а затем округлите их.
К сожалению, это не работает. Например, Math.round(0.4) + Math.round(0.4)
дает 0
, но Math.round(0.4 + 0.4)
дает 1.
Единственный способ правильно сделать цифры - отобразить три десятичных знака.
Вы могли бы получить более точные результаты, используя решение из (теперь удалённого) ответа Херардо Фуртадо. – то есть использовать Math.round()
для округления числа вместо сокращения третьей цифры, но в некоторых случаях это все равно не будет работать.
Ответ 3
Проблема, связанная с тем, что ваша проблема заключается в том, что вы снижаете точность, только когда вы отформатируете время в секундах. То, как вы впервые это сделали (без Math.round()), по существу делает Math.floor, просто отключив последний символ. Так или иначе вы теряете точность. Если вы хотите отображать только до 100 секунд, и вы хотите, чтобы математика, которую пользователь видит, работает, вы можете сделать добавление в отформатированных количествах вместо необработанных сумм, например:
// this just does the work of adding up the individuals after they've been formatted
const individualAdder = timeFormatter(individualTimes.reduce((total, time) => {
return total + parseFloat(time.replace(/[^0-9]/g, ""));
}, 0) * 10);
/**
* 00 : 27 . 12
*/
console.log(individualAdder);
Вы также можете отображать отдельные моменты с полной точностью в миллисекундах в зависимости от желаемого опыта.
Ответ 4
Ваше решение обрезало последнюю значащую цифру.
function timeFormatter (milliseconds) {
const padZero = (time) => `0${time}`.slice(-2);
const minutes = padZero(milliseconds / 60000 | 0);
const seconds = padZero((milliseconds / 1000 | 0) % 60);
const centiseconds = `00${milliseconds % 1000}`.slice(-3); //changed
return `${minutes} : ${seconds} . ${centiseconds}`;
}
// Example stopwatch times
const timeIntervals = [
{ startTime: 1470679294008, stopTime: 1470679300609 },
{ startTime: 1470679306278, stopTime: 1470679314647 },
{ startTime: 1470679319718, stopTime: 1470679326693 },
{ startTime: 1470679331229, stopTime: 1470679336420 }
];
// Calculate time it took for each entry
const times = timeIntervals.map(time => time.stopTime - time.startTime);
// Run the timeFormatter on each individual time
const individualTimes = times.map(timeFormatter);
// Run the timeFormatter on the sum of all the times
const mainTimer = timeFormatter(times.reduce((a, b) => a + b));
/**
* [
* '00 : 06 . 601',
* '00 : 08 . 369',
* '00 : 06 . 975',
* '00 : 05 . 191'
* ]
*/
console.log(individualTimes);
/**
* 00 : 27 . 136
*/
console.log(mainTimer);