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 /** 51 * Converts to an integer time value. 52 * 53 * @param round How to round ('round', 'floor', or 'ceil'). 54 */ 55 toTime(round: RoundMode = 'floor'): time { 56 switch (round) { 57 case 'round': 58 return Time.fromRaw( 59 this.integral + BigInt(Math.round(this.fractional)), 60 ); 61 case 'floor': 62 return Time.fromRaw(this.integral); 63 case 'ceil': 64 return Time.fromRaw(this.integral + BigInt(Math.ceil(this.fractional))); 65 default: 66 assertUnreachable(round); 67 } 68 } 69 70 /** 71 * Converts to a JavaScript number. Precision loss should be expected when 72 * integral values are large. 73 */ 74 toNumber(): number { 75 return Number(this.integral) + this.fractional; 76 } 77 78 /** 79 * Adds another HighPrecisionTime to this one and returns the result. 80 * 81 * @param time A HighPrecisionTime object to add. 82 */ 83 add(time: HighPrecisionTime): HighPrecisionTime { 84 return new HighPrecisionTime( 85 Time.add(this.integral, time.integral), 86 this.fractional + time.fractional, 87 ); 88 } 89 90 /** 91 * Adds an integer time value to this HighPrecisionTime and returns the result. 92 * 93 * @param t A time value to add. 94 */ 95 addTime(t: time): HighPrecisionTime { 96 return new HighPrecisionTime(Time.add(this.integral, t), this.fractional); 97 } 98 99 /** 100 * Adds a floating point time value to this one and returns the result. 101 * 102 * @param n A floating point value to add. 103 */ 104 addNumber(n: number): HighPrecisionTime { 105 return new HighPrecisionTime(this.integral, this.fractional + n); 106 } 107 108 /** 109 * Subtracts another HighPrecisionTime from this one and returns the result. 110 * 111 * @param time A HighPrecisionTime object to subtract. 112 */ 113 sub(time: HighPrecisionTime): HighPrecisionTime { 114 return new HighPrecisionTime( 115 Time.sub(this.integral, time.integral), 116 this.fractional - time.fractional, 117 ); 118 } 119 120 /** 121 * Subtract an integer time value from this HighPrecisionTime and returns the 122 * result. 123 * 124 * @param t A time value to subtract. 125 */ 126 subTime(t: time): HighPrecisionTime { 127 return new HighPrecisionTime(Time.sub(this.integral, t), this.fractional); 128 } 129 130 /** 131 * Subtracts a floating point time value from this one and returns the result. 132 * 133 * @param n A floating point value to subtract. 134 */ 135 subNumber(n: number): HighPrecisionTime { 136 return new HighPrecisionTime(this.integral, this.fractional - n); 137 } 138 139 /** 140 * Checks if this HighPrecisionTime is approximately equal to another, within 141 * a given epsilon. 142 * 143 * @param other A HighPrecisionTime object to compare. 144 * @param epsilon The tolerance for equality check. 145 */ 146 equals(other: HighPrecisionTime, epsilon: number = 1e-6): boolean { 147 return Math.abs(this.sub(other).toNumber()) < epsilon; 148 } 149 150 /** 151 * Checks if this time value is within the range defined by [start, end). 152 * 153 * @param start The start of the time range (inclusive). 154 * @param end The end of the time range (exclusive). 155 */ 156 containedWithin(start: time, end: time): boolean { 157 return this.integral >= start && this.integral < end; 158 } 159 160 /** 161 * Checks if this HighPrecisionTime is less than a given time. 162 * 163 * @param t A time value. 164 */ 165 lt(t: time): boolean { 166 return this.integral < t; 167 } 168 169 /** 170 * Checks if this HighPrecisionTime is less than or equal to a given time. 171 * 172 * @param t A time value. 173 */ 174 lte(t: time): boolean { 175 return ( 176 this.integral < t || 177 (this.integral === t && Math.abs(this.fractional - 0.0) < Number.EPSILON) 178 ); 179 } 180 181 /** 182 * Checks if this HighPrecisionTime is greater than a given time. 183 * 184 * @param t A time value. 185 */ 186 gt(t: time): boolean { 187 return ( 188 this.integral > t || 189 (this.integral === t && Math.abs(this.fractional - 0.0) > Number.EPSILON) 190 ); 191 } 192 193 /** 194 * Checks if this HighPrecisionTime is greater than or equal to a given time. 195 * 196 * @param t A time value. 197 */ 198 gte(t: time): boolean { 199 return this.integral >= t; 200 } 201 202 /** 203 * Clamps this HighPrecisionTime to be within the specified range. 204 * 205 * @param lower The lower bound of the range. 206 * @param upper The upper bound of the range. 207 */ 208 clamp(lower: time, upper: time): HighPrecisionTime { 209 if (this.integral < lower) { 210 return new HighPrecisionTime(lower); 211 } else if (this.integral >= upper) { 212 return new HighPrecisionTime(upper); 213 } else { 214 return this; 215 } 216 } 217 218 /** 219 * Returns the absolute value of this HighPrecisionTime. 220 */ 221 abs(): HighPrecisionTime { 222 if (this.integral >= 0n) { 223 return this; 224 } 225 const newIntegral = Time.fromRaw(-this.integral); 226 const newFractional = -this.fractional; 227 return new HighPrecisionTime(newIntegral, newFractional); 228 } 229 230 /** 231 * Converts this HighPrecisionTime to a string representation. 232 */ 233 toString(): string { 234 const fractionalAsString = this.fractional.toString(); 235 if (fractionalAsString === '0') { 236 return this.integral.toString(); 237 } else { 238 return `${this.integral}${fractionalAsString.substring(1)}`; 239 } 240 } 241} 242