1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
|
#ifndef AWS_COMMON_CLOCK_INL
#define AWS_COMMON_CLOCK_INL
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#include <aws/common/clock.h>
#include <aws/common/common.h>
#include <aws/common/math.h>
AWS_EXTERN_C_BEGIN
/**
* Converts 'timestamp' from unit 'convert_from' to unit 'convert_to', if the units are the same then 'timestamp' is
* returned. If 'remainder' is NOT NULL, it will be set to the remainder if convert_from is a more precise unit than
* convert_to (but only if the old frequency is a multiple of the new one). If conversion would lead to integer
* overflow, the timestamp returned will be the highest possible time that is representable, i.e. UINT64_MAX.
*/
AWS_STATIC_IMPL uint64_t
aws_timestamp_convert_u64(uint64_t ticks, uint64_t old_frequency, uint64_t new_frequency, uint64_t *remainder) {
AWS_FATAL_ASSERT(old_frequency > 0 && new_frequency > 0);
/*
* The remainder, as defined in the contract of the original version of this function, only makes mathematical
* sense when the old frequency is a positive multiple of the new frequency. The new convert function needs to be
* backwards compatible with the old version's remainder while being a lot more accurate with its conversions
* in order to handle extreme edge cases of large numbers.
*/
if (remainder != NULL) {
*remainder = 0;
/* only calculate remainder when going from a higher to lower frequency */
if (new_frequency < old_frequency) {
uint64_t frequency_remainder = old_frequency % new_frequency;
/* only calculate remainder when the old frequency is evenly divisible by the new one */
if (frequency_remainder == 0) {
uint64_t frequency_ratio = old_frequency / new_frequency;
*remainder = ticks % frequency_ratio;
}
}
}
/*
* Now do the actual conversion.
*/
uint64_t old_seconds_elapsed = ticks / old_frequency;
uint64_t old_remainder = ticks - old_seconds_elapsed * old_frequency;
uint64_t new_ticks_whole_part = aws_mul_u64_saturating(old_seconds_elapsed, new_frequency);
/*
* This could be done in one of three ways:
*
* (1) (old_remainder / old_frequency) * new_frequency - this would be completely wrong since we know that
* (old_remainder / old_frequency) < 1 = 0
*
* (2) old_remainder * (new_frequency / old_frequency) - this only gives a good solution when new_frequency is
* a multiple of old_frequency
*
* (3) (old_remainder * new_frequency) / old_frequency - this is how we do it below, the primary concern is if
* the initial multiplication can overflow. For that to be the case, we would need to be using old and new
* frequencies in the billions. This does not appear to be the case in any current machine's hardware counters.
*
* Ignoring arbitrary frequencies, even a nanosecond to nanosecond conversion would not overflow either.
*
* If this did become an issue, we would potentially need to use intrinsics/platform support for 128 bit math.
*
* For review consideration:
* (1) should we special case frequencies being a multiple of the other?
* (2) should we special case frequencies being the same? A ns-to-ns conversion does the full math and
* approaches overflow (but cannot actually do so).
*/
uint64_t new_ticks_remainder_part = aws_mul_u64_saturating(old_remainder, new_frequency) / old_frequency;
return aws_add_u64_saturating(new_ticks_whole_part, new_ticks_remainder_part);
}
AWS_STATIC_IMPL uint64_t aws_timestamp_convert(
uint64_t timestamp,
enum aws_timestamp_unit convert_from,
enum aws_timestamp_unit convert_to,
uint64_t *remainder) {
return aws_timestamp_convert_u64(timestamp, convert_from, convert_to, remainder);
}
AWS_EXTERN_C_END
#endif /* AWS_COMMON_CLOCK_INL */
|