格式化计时器而不会失去准确性?

时间:2016-08-14 05:54:06

标签: javascript ecmascript-6 rounding precision

我有一系列开始/停止时间。我基本上想要显示每个条目花费的时间,以及所有条目的总时间。这是我为了尝试这样做而编写的代码:



var selected = (function(){
  var doc = document;
  var DOM = {
    Ulshow : false,
    button : doc.querySelector( '.wraper button' ),
    input : doc.querySelector( '.wraper input' ),
    ul : doc.querySelector( '.wraper ul' )
  }

  function toggleUl() {
    var display = DOM.Ulshow ? 'none' : 'block';
    DOM.ul.style.display = display;
    DOM.Ulshow = !DOM.Ulshow;
  }

  function selectLi( e ) {
    if ( e.target.tagName.toLowerCase() !== 'li' )
      return;
    DOM.input.value = e.target.innerHTML;
    toggleUl();
  }

  function bindEvent() {
    DOM.button.addEventListener( 'click', toggleUl, false );
    DOM.ul.addEventListener( 'click', selectLi, false );
  }

  function unbindEvent() {
    DOM.button.removeEventListener( 'click', toggleUl, false );
    DOM.ul.removeEventListener( 'click', selectLi, false );
  }

  function init() {
    bindEvent();
  }

  init();

  return {
    unbindEvent : unbindEvent
  };
})();

//Now I can invoke the selected.unbindEvent function to unbind click event
seleted.unbindEvent();




然而,我正在失去准确性。如您所见,个别时间不能合计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);值。无论什么时候,它总是在.01 - .03之间。

有没有办法可以确保时间只显示两个地方,但仍然正确加起来?任何帮助将不胜感激。

我也可以在JSFiddle上使用它,因为它更容易运行。

编辑:目前的答案确实适用于我上面提供的案例,但它并不适用于this之类的所有案例。

4 个答案:

答案 0 :(得分:4)

问题

每次显示舍入时间时,您都会失去准确性。 你拥有的圈数越多,问题就越严重:

╔══════╦════════════════════════════════╗
║ 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. 检测问题并显示消息。
  4. 以下是解决方案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,可以最大限度地减少错误并解决时间弯曲魔法。

答案 1 :(得分:2)

无法达到你想要的效果。当您将两个数字四舍五入并添加它们时,以及首次添加数字然后舍入它们时,您基本上希望获得相同的结果。

不幸的是,它并没有这样做。例如,Math.round(0.4) + Math.round(0.4)给出0,但Math.round(0.4 + 0.4)给出1。

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

您可以使用(现已删除)answer by Gerardo Furtado中的解决方案获得更准确的结果 - 也就是说,使用Math.round()来舍入数字,而不是剪切第三个数字,但是在某些情况下仍然无法发挥作用。

答案 2 :(得分:1)

你遇到的问题是因为你的格式化时间只需要几厘秒就可以降低精度。你第一次使用它的方式(没有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);

您还可以以毫秒的精确度显示单个时间,具体取决于您所需的体验。

答案 3 :(得分:-1)

您的解决方案裁掉了最后一位有效数字。

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