【问题标题】:Formatting timers without losing accuracy? [duplicate]在不丢失准确性的情况下格式化计时器? [复制]
【发布时间】:2016-08-14 05:54:06
【问题描述】:

我有一组开始/停止时间。我基本上想显示每个条目所花费的时间,以及所有条目的总时间。这是我写的代码:

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 one。

【问题讨论】:

标签: javascript ecmascript-6 rounding precision


【解决方案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 时间 = 12189
  • 实际时间 = 12189 到 12193
  • 显示时间 = 12180 到 12240

换句话说,通过加起来显示的时间,显示精度的损失也加起来了。 真正的问题是没有考虑到人类的损失。

解决方案

  1. 提高显示精度(如果不四舍五入,就不会丢失任何内容)
  2. 使用显示的总和不太准确,但如果您还说明公差 (±),则不会不正确。
  3. 检测问题并显示消息。

这是解决方案 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>

或者只是忍受它

所有计时器,甚至是物理计时器,都必须忍受这个舍入问题。 你见过做出这种免责声明的计时器吗?

对于计时器,显示更准确的总时间当然更正确,即使它不一致。

如果您注意了,您应该会看到/意识到相对于实时而言,javascript 时间也失去了这种准确性。 还有一个更大的问题: Date.time 与时钟同步,因此不稳定。 鉴于您的示例单圈范围为几秒钟,您甚至可能会得到负单圈。

使用专为计时目的设计的不同计时器Performance.now,可以最大限度地减少错误并解决时间弯曲魔法。

【讨论】:

  • 除了好的答案之外,Performance.now 的 +1,对于这种情况令人惊讶地未被探索
【解决方案2】:

不可能达到你想要的。当您将两个数字四舍五入并相加时,以及首先将数字相加然后四舍五入时,您基本上希望得到相同的结果。

不幸的是,它不是这样工作的。例如,Math.round(0.4) + Math.round(0.4) 给出 0,但 Math.round(0.4 + 0.4) 给出 1。

使数字正确相加的唯一方法是显示三位小数。


使用(现已删除)answer by Gerardo Furtado 的解决方案可以得到更准确的结果——也就是说,使用Math.round() 对数字进行四舍五入,而不是削减第三位数字,但这仍然不会在某些情况下不起作用。

【讨论】:

  • 另一种使数字正确相加的方法是向上和向下舍入相同的次数。
【解决方案3】:

您遇到的问题是,当您格式化时间时,您的精度只会下降到几分之一秒。你第一次拥有它的方式(没有 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);

您还可以根据您所需的体验以整毫秒的精度显示各个时间。

【讨论】:

  • 不知道否决票是由于错别字还是什么,但现在已修复。我确实相信这更接近 OP 所追求的精神,Hin Fan Chan 的回答是我的意思是“以毫秒精度显示单个时间”,这当然也是一个有效的解决方案。
  • 谢谢。对不起,如果这是一个荒谬的问题,但有没有办法只显示两位数但仍然是一个准确的计时器?
  • 你不能在不同的精度级别上进行两次计算并得到相同的结果。从技术上讲,您最初的想法是正确的,只是查看各个时间的人可能会根据这些格式化的值做不太精确的数学运算(因为他们无法获得完整的精度)。这确实是您问题的症结所在,用户可能会做不精确的数学运算并感到困惑。如果这个假设的用户体验是一个重要的问题,我会直接在顶部做一个四舍五入到最接近的厘秒的地图,然后忘记我曾经有过毫秒。
  • 否则您可以显示总和,在加法后四舍五入,并说明各个时间已四舍五入。
【解决方案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);

【讨论】:

  • 只有两位数是期望的结果。 “有没有办法确保时间只显示两个地方......”
  • 删除一个数字意味着你失去了准确性。您可以将 individualTimes 的最后一个有效数字四舍五入,以使 mainTimer = sum(individualTimes)。
猜你喜欢
  • 2022-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-11
  • 2018-08-19
  • 2011-01-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多