• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {assertUnreachable} from './logging';
16import {Time, time} from './time';
17
18export type RoundMode = 'round' | 'floor' | 'ceil';
19
20/**
21 * Represents a time value in trace processor's time units, which is capable of
22 * representing a time with at least 64 bit integer precision and 53 bits of
23 * fractional precision.
24 *
25 * This class is immutable - any methods that modify this time will return a new
26 * copy containing instead.
27 */
28export class HighPrecisionTime {
29  // This is the high precision time representing 0
30  static readonly ZERO = new HighPrecisionTime(Time.fromRaw(0n));
31
32  // time value == |integral| + |fractional|
33  // |fractional| is kept in the range 0 <= x < 1 to avoid losing precision
34  readonly integral: time;
35  readonly fractional: number;
36
37  /**
38   * Constructs a HighPrecisionTime object.
39   *
40   * @param integral The integer part of the time value.
41   * @param fractional The fractional part of the time value.
42   */
43  constructor(integral: time, fractional: number = 0) {
44    // Normalize |fractional| to the range 0.0 <= x < 1.0
45    const fractionalFloor = Math.floor(fractional);
46    this.integral = (integral + BigInt(fractionalFloor)) as time;
47    this.fractional = fractional - fractionalFloor;
48  }
49
50  static max(a: HighPrecisionTime, b: HighPrecisionTime) {
51    if (a.integral > b.integral) return a;
52    if (a.integral < b.integral) return b;
53    if (a.fractional > b.fractional) return a;
54    return b;
55  }
56
57  static min(a: HighPrecisionTime, b: HighPrecisionTime) {
58    return HighPrecisionTime.max(a, b) === a ? b : a;
59  }
60
61  /**
62   * Converts to an integer time value.
63   *
64   * @param round How to round ('round', 'floor', or 'ceil').
65   */
66  toTime(round: RoundMode = 'floor'): time {
67    switch (round) {
68      case 'round':
69        return Time.fromRaw(
70          this.integral + BigInt(Math.round(this.fractional)),
71        );
72      case 'floor':
73        return Time.fromRaw(this.integral);
74      case 'ceil':
75        return Time.fromRaw(this.integral + BigInt(Math.ceil(this.fractional)));
76      default:
77        assertUnreachable(round);
78    }
79  }
80
81  /**
82   * Converts to a JavaScript number. Precision loss should be expected when
83   * integral values are large.
84   */
85  toNumber(): number {
86    return Number(this.integral) + this.fractional;
87  }
88
89  /**
90   * Adds another HighPrecisionTime to this one and returns the result.
91   *
92   * @param time A HighPrecisionTime object to add.
93   */
94  add(time: HighPrecisionTime): HighPrecisionTime {
95    return new HighPrecisionTime(
96      Time.add(this.integral, time.integral),
97      this.fractional + time.fractional,
98    );
99  }
100
101  /**
102   * Adds an integer time value to this HighPrecisionTime and returns the result.
103   *
104   * @param t A time value to add.
105   */
106  addTime(t: time): HighPrecisionTime {
107    return new HighPrecisionTime(Time.add(this.integral, t), this.fractional);
108  }
109
110  /**
111   * Adds a floating point time value to this one and returns the result.
112   *
113   * @param n A floating point value to add.
114   */
115  addNumber(n: number): HighPrecisionTime {
116    return new HighPrecisionTime(this.integral, this.fractional + n);
117  }
118
119  /**
120   * Subtracts another HighPrecisionTime from this one and returns the result.
121   *
122   * @param time A HighPrecisionTime object to subtract.
123   */
124  sub(time: HighPrecisionTime): HighPrecisionTime {
125    return new HighPrecisionTime(
126      Time.sub(this.integral, time.integral),
127      this.fractional - time.fractional,
128    );
129  }
130
131  /**
132   * Subtract an integer time value from this HighPrecisionTime and returns the
133   * result.
134   *
135   * @param t A time value to subtract.
136   */
137  subTime(t: time): HighPrecisionTime {
138    return new HighPrecisionTime(Time.sub(this.integral, t), this.fractional);
139  }
140
141  /**
142   * Subtracts a floating point time value from this one and returns the result.
143   *
144   * @param n A floating point value to subtract.
145   */
146  subNumber(n: number): HighPrecisionTime {
147    return new HighPrecisionTime(this.integral, this.fractional - n);
148  }
149
150  /**
151   * Checks if this HighPrecisionTime is approximately equal to another, within
152   * a given epsilon.
153   *
154   * @param other A HighPrecisionTime object to compare.
155   * @param epsilon The tolerance for equality check.
156   */
157  equals(other: HighPrecisionTime, epsilon: number = 1e-6): boolean {
158    return Math.abs(this.sub(other).toNumber()) < epsilon;
159  }
160
161  /**
162   * Checks if this time value is within the range defined by [start, end).
163   *
164   * @param start The start of the time range (inclusive).
165   * @param end The end of the time range (exclusive).
166   */
167  containedWithin(start: time, end: time): boolean {
168    return this.integral >= start && this.integral < end;
169  }
170
171  /**
172   * Checks if this HighPrecisionTime is less than a given time.
173   *
174   * @param t A time value.
175   */
176  lt(t: time): boolean {
177    return this.integral < t;
178  }
179
180  /**
181   * Checks if this HighPrecisionTime is less than or equal to a given time.
182   *
183   * @param t A time value.
184   */
185  lte(t: time): boolean {
186    return (
187      this.integral < t ||
188      (this.integral === t && Math.abs(this.fractional - 0.0) < Number.EPSILON)
189    );
190  }
191
192  /**
193   * Checks if this HighPrecisionTime is greater than a given time.
194   *
195   * @param t A time value.
196   */
197  gt(t: time): boolean {
198    return (
199      this.integral > t ||
200      (this.integral === t && Math.abs(this.fractional - 0.0) > Number.EPSILON)
201    );
202  }
203
204  /**
205   * Checks if this HighPrecisionTime is greater than or equal to a given time.
206   *
207   * @param t A time value.
208   */
209  gte(t: time): boolean {
210    return this.integral >= t;
211  }
212
213  /**
214   * Clamps this HighPrecisionTime to be within the specified range.
215   *
216   * @param lower The lower bound of the range.
217   * @param upper The upper bound of the range.
218   */
219  clamp(lower: time, upper: time): HighPrecisionTime {
220    if (this.integral < lower) {
221      return new HighPrecisionTime(lower);
222    } else if (this.integral >= upper) {
223      return new HighPrecisionTime(upper);
224    } else {
225      return this;
226    }
227  }
228
229  /**
230   * Returns the absolute value of this HighPrecisionTime.
231   */
232  abs(): HighPrecisionTime {
233    if (this.integral >= 0n) {
234      return this;
235    }
236    const newIntegral = Time.fromRaw(-this.integral);
237    const newFractional = -this.fractional;
238    return new HighPrecisionTime(newIntegral, newFractional);
239  }
240
241  /**
242   * Converts this HighPrecisionTime to a string representation.
243   */
244  toString(): string {
245    const fractionalAsString = this.fractional.toString();
246    if (fractionalAsString === '0') {
247      return this.integral.toString();
248    } else {
249      return `${this.integral}${fractionalAsString.substring(1)}`;
250    }
251  }
252}
253