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