在不损失准确性的情况下格式化计时器?
Formatting timers without losing accuracy?
我有一个 start/stop 次数组。我基本上想显示每个条目花费的时间,以及所有条目的总时间。这是我为尝试执行此操作而编写的代码:
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);
但是,我正在失去准确性。如您所见,各个时间加起来不等于 mainTimer
值。无论时间如何,它总是关闭 0.01 - 0.03。
有什么方法可以确保时间只显示两个地方,但仍然正确相加?任何帮助将不胜感激。
我在 JSFiddle 上也有这个,它更容易 运行。
编辑:当前答案确实适用于我上面提供的案例,但它不适用于所有案例,例如 this 一个。
您遇到的问题是因为您在设置时间格式时仅将精度降低到厘秒。您最初拥有它的方式(没有 Math.round())基本上只是通过修剪最后一个字符来实现 Math.floor。所以无论哪种方式,你都会失去精度。如果您只想显示到厘秒,并且您希望用户看到的数学有效,您可以对格式化的金额而不是原始金额进行加法,如下所示:
// 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);
您还可以根据您想要的体验以完整的毫秒精度显示各个时间。
您的解决方案裁剪了最后一个有效数字。
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);
你想要的是不可能实现的。当您将两个数字四舍五入并相加时,以及当您首先将数字相加然后四舍五入时,您基本上期望得到相同的结果。
不幸的是,它不是那样工作的。例如,Math.round(0.4) + Math.round(0.4)
给出 0
,但 Math.round(0.4 + 0.4)
给出 1.
使数字相加正确的唯一方法是显示三位小数。
使用来自(现已删除)answer by Gerardo Furtado 的解决方案,您可以获得更准确的结果 – 也就是说,使用 Math.round()
来舍入数字,而不是删除第三位数字, 但在某些情况下仍然行不通。
问题
您每次显示舍入时间都会失去准确性。
您的圈数越多,问题就越大:
╔══════╦════════════════════════════════╗
║ 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时间=12189
- 实际时间 = 12189 到 12193
- 显示时间 = 12180 到 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>
或者忍受它
所有计时器,甚至物理计时器,都必须忍受这个舍入问题。
你见过有这样免责声明的计时器吗?
对于一个计时器来说,总时间显示得越准确,即使不一致,也肯定是越正确的。
If you paid attention, you should see / realise that javascript time also has this accuracy lost relative to real time.
There is also a bigger problem:
Date.time is synced with clock so it is unstable.
Given your sample lap ranges of a few seconds, you may even get negative lap.
Using a different timer designed for timing purpose, Performance.now, can minimize errors and solve the time bending magic.
我有一个 start/stop 次数组。我基本上想显示每个条目花费的时间,以及所有条目的总时间。这是我为尝试执行此操作而编写的代码:
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);
但是,我正在失去准确性。如您所见,各个时间加起来不等于 mainTimer
值。无论时间如何,它总是关闭 0.01 - 0.03。
有什么方法可以确保时间只显示两个地方,但仍然正确相加?任何帮助将不胜感激。
我在 JSFiddle 上也有这个,它更容易 运行。
编辑:当前答案确实适用于我上面提供的案例,但它不适用于所有案例,例如 this 一个。
您遇到的问题是因为您在设置时间格式时仅将精度降低到厘秒。您最初拥有它的方式(没有 Math.round())基本上只是通过修剪最后一个字符来实现 Math.floor。所以无论哪种方式,你都会失去精度。如果您只想显示到厘秒,并且您希望用户看到的数学有效,您可以对格式化的金额而不是原始金额进行加法,如下所示:
// 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);
您还可以根据您想要的体验以完整的毫秒精度显示各个时间。
您的解决方案裁剪了最后一个有效数字。
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);
你想要的是不可能实现的。当您将两个数字四舍五入并相加时,以及当您首先将数字相加然后四舍五入时,您基本上期望得到相同的结果。
不幸的是,它不是那样工作的。例如,Math.round(0.4) + Math.round(0.4)
给出 0
,但 Math.round(0.4 + 0.4)
给出 1.
使数字相加正确的唯一方法是显示三位小数。
使用来自(现已删除)answer by Gerardo Furtado 的解决方案,您可以获得更准确的结果 – 也就是说,使用 Math.round()
来舍入数字,而不是删除第三位数字, 但在某些情况下仍然行不通。
问题
您每次显示舍入时间都会失去准确性。 您的圈数越多,问题就越大:
╔══════╦════════════════════════════════╗
║ 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时间=12189
- 实际时间 = 12189 到 12193
- 显示时间 = 12180 到 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>
或者忍受它
所有计时器,甚至物理计时器,都必须忍受这个舍入问题。 你见过有这样免责声明的计时器吗?
对于一个计时器来说,总时间显示得越准确,即使不一致,也肯定是越正确的。
If you paid attention, you should see / realise that javascript time also has this accuracy lost relative to real time. There is also a bigger problem: Date.time is synced with clock so it is unstable. Given your sample lap ranges of a few seconds, you may even get negative lap.
Using a different timer designed for timing purpose, Performance.now, can minimize errors and solve the time bending magic.