libui/test/lib/timer.c

353 lines
8.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 2 may 2019
#include <string.h>
#include "timer.h"
#include "timerpriv.h"
// This is based on the algorithm that Go uses for time.Duration.
// Of course, we're not expressing it the same way...
struct timerStringPart {
char suffix;
char suffix2;
int mode;
uint32_t maxOrMod;
int precision;
};
enum {
modeMaxAndStop,
modeFracModContinue,
};
static const struct timerStringPart parts[] = {
{ 'n', 's', modeMaxAndStop, 1000, 0 },
{ 'u', 's', modeMaxAndStop, 1000000, 3 },
{ 'm', 's', modeMaxAndStop, 1000000000, 6 },
{ 's', 0, modeFracModContinue, 60, 9 },
{ 'm', 0, modeFracModContinue, 60, 0 },
{ 'h', 0, modeFracModContinue, 60, 0 },
{ 0, 0, 0, 0, 0 },
};
static int fillFracPart(char *buf, int precision, int start, uint64_t *unsec)
{
int i;
bool print;
uint64_t digit;
print = false;
for (i = 0; i < precision; i++) {
digit = *unsec % 10;
print = print || (digit != 0);
if (print) {
buf[start - 1] = "0123456789"[digit];
start--;
}
*unsec /= 10;
}
if (print) {
buf[start - 1] = '.';
start--;
}
return start;
}
static int fillIntPart(char *buf, int start, uint64_t unsec)
{
if (unsec == 0) {
buf[start - 1] = '0';
start--;
return start;
}
while (unsec != 0) {
buf[start - 1] = "0123456789"[unsec % 10];
start--;
unsec /= 10;
}
return start;
}
void timerDurationString(timerDuration d, char buf[timerDurationStringLen])
{
uint64_t unsec;
bool neg;
int start;
const struct timerStringPart *p;
memset(buf, 0, timerDurationStringLen * sizeof (char));
start = 32;
if (d == 0) {
buf[0] = '0';
buf[1] = 's';
return;
}
neg = d < 0;
if (neg) {
// C99 §6.2.6.2 resticts the possible signed integer representations in C to either sign-magnitude, 1's complement, or 2's complement.
// Therefore, INT64_MIN will always be either -INT64_MAX or -INT64_MAX - 1, so we can safely do this to see if we need to special-case INT64_MIN as -INT64_MIN cannot be safely represented, or if we can just say -d as that can be safely represented.
// See also https://stackoverflow.com/questions/29808397/how-to-portably-find-out-minint-max-absint-min
if (d < -INT64_MAX) {
// INT64_MIN is -INT64_MAX - 1.
// This value comes directly from forcing such a value into Go's code; let's just use it.
memmove(buf, "-2562047h47m16.854775808s", (25 + 1) * sizeof (char));
return;
}
unsec = (uint64_t) (-d);
} else
unsec = (uint64_t) d;
for (p = parts; p->suffix != 0; p++) {
if (p->mode == modeMaxAndStop && unsec < p->maxOrMod) {
if (p->suffix2 != 0) {
buf[start - 1] = p->suffix2;
start--;
}
buf[start - 1] = p->suffix;
start--;
start = fillFracPart(buf, p->precision, start, &unsec);
start = fillIntPart(buf, start, unsec);
break;
}
if (p->mode == modeFracModContinue && unsec != 0) {
if (p->suffix2 != 0) {
buf[start - 1] = p->suffix2;
start--;
}
buf[start - 1] = p->suffix;
start--;
start = fillFracPart(buf, p->precision, start, &unsec);
start = fillIntPart(buf, start, unsec % p->maxOrMod);
unsec /= p->maxOrMod;
// and move on to the next one
}
}
if (neg) {
buf[start - 1] = '-';
start--;
}
memmove(buf, buf + start, 33 - start);
}
// portable implementations of 64x64-bit MulDiv(), because:
// - a division intrinsic was not added to Visual Studio until VS2015
// - there does not seem to be a division intrinsic in GCC or clang as far as I can tell
// - there are no 128-bit facilities in macOS as far as I can tell
static void int128FromUint64(uint64_t n, timerprivInt128 *out)
{
out->neg = false;
out->high = 0;
out->low = n;
}
static void int128FromInt64(int64_t n, timerprivInt128 *out)
{
if (n >= 0) {
int128FromUint64((uint64_t) n, out);
return;
}
out->neg = true;
out->high = 0;
// And now we do the same INT64_MIN/INT64_MAX juggling as above.
if (n < -INT64_MAX) {
out->low = ((uint64_t) INT64_MAX) + 1;
return;
}
out->low = (uint64_t) (-n);
}
// references for this part:
// - https://opensource.apple.com/source/Libc/Libc-1272.200.26/gen/nanosleep.c.auto.html
// - https://en.wikipedia.org/wiki/Division_algorithm#Integer_division_(unsigned)_with_remainder
static void int128UAdd(timerprivInt128 *x, const timerprivInt128 *y)
{
x->high += y->high;
x->low += y->low;
if (x->low < y->low)
x->high++;
}
static void int128USub(timerprivInt128 *x, const timerprivInt128 *y)
{
x->high -= y->high;
if (x->low < y->low)
x->high--;
x->low -= y->low;
}
static void int128Lsh1(timerprivInt128 *x)
{
x->high <<= 1;
if ((x->low & 0x8000000000000000) != 0)
x->high |= 1;
x->low <<= 1;
}
static uint64_t int128Bit(const timerprivInt128 *x, int i)
{
uint64_t which;
which = x->low;
if (i >= 64) {
i -= 64;
which = x->high;
}
return (which >> i) & 1;
}
static int int128UCmp(const timerprivInt128 *x, const timerprivInt128 *y)
{
if (x->high < y->high)
return -1;
if (x->high > y->high)
return 1;
if (x->low < y->low)
return -1;
if (x->low > y->low)
return 1;
return 0;
}
static void int128BitSet(timerprivInt128 *x, int i)
{
uint64_t bit;
bit = 1;
if (i >= 64) {
i -= 64;
bit <<= i;
x->high |= bit;
return;
}
bit <<= i;
x->low |= bit;
}
static void int128MulDiv64(timerprivInt128 *x, timerprivInt128 *y, timerprivInt128 *z, timerprivInt128 *quot)
{
bool finalNeg;
uint64_t x64high, x64low;
uint64_t y64high, y64low;
timerprivInt128 add, numer, rem;
int i;
finalNeg = false;
if (x->neg)
finalNeg = !finalNeg;
if (y->neg)
finalNeg = !finalNeg;
if (z->neg)
finalNeg = !finalNeg;
quot->neg = finalNeg;
// we now treat x, y, and z as unsigned
// first, multiply x and y into numer
// this assumes x->high == y->high == 0
numer.neg = false;
// the idea is if x = (a * 2^32) + b and y = (c * 2^32) + d, we can express x * y as ((a * 2^32) + b) * ((c * 2^32) + d)...
x64high = (x->low >> 32) & 0xFFFFFFFF;
x64low = x->low & 0xFFFFFFFF;
y64high = (y->low >> 32) & 0xFFFFFFFF;
y64low = y->low & 0xFFFFFFFF;
// and we can expand that out to get...
numer.high = x64high * y64high; // a * c * 2^64 +
numer.low = x64low * y64low; // b * d +
add.neg = false;
add.high = x64high * y64low; // a * d * 2^32 +
add.low = (add.high & 0xFFFFFFFF) << 32;
add.high >>= 32;
int128UAdd(&numer, &add);
add.high = x64low * y64high; // b * c * 2^32
add.low = (add.high & 0xFFFFFFFF) << 32;
add.high >>= 32;
int128UAdd(&numer, &add);
// I did type this all by hand, btw; the idea does come from Apple's implementation, though they explain it a bit more obtusely, and the odd behavior with anding high into low is to avoid looking like I directly copied their code which does the opposite
// and now long-divide
// Apple's implementation uses NewtonRaphson division using doubles to store 1/z but I'd rather go with "slow but guaranteed to be accurate"
// (Apple also rejects quotients > UINT64_MAX; we won't)
quot->high = 0;
quot->low = 0;
rem.neg = false;
rem.high = 0;
rem.low = 0;
for (i = 127; i >= 0; i--) {
int128Lsh1(&rem);
rem.low |= int128Bit(&numer, i);
if (int128UCmp(&rem, z) >= 0) {
int128USub(&rem, z);
int128BitSet(quot, i);
}
}
}
void timerprivMulDivInt64(int64_t x, int64_t y, int64_t z, timerprivInt128 *quot)
{
timerprivInt128 a, b, c;
int128FromInt64(x, &a);
int128FromInt64(y, &b);
int128FromInt64(z, &c);
int128MulDiv64(&a, &b, &c, quot);
}
void timerprivMulDivUint64(uint64_t x, uint64_t y, uint64_t z, timerprivInt128 *quot)
{
timerprivInt128 a, b, c;
int128FromUint64(x, &a);
int128FromUint64(y, &b);
int128FromUint64(z, &c);
int128MulDiv64(&a, &b, &c, quot);
}
int64_t timerprivInt128ToInt64(const timerprivInt128 *n, int64_t min, int64_t minCap, int64_t max, int64_t maxCap)
{
if (n->neg) {
int64_t ret;
if (n->high > 0)
return minCap;
if (n->low > (uint64_t) INT64_MAX) {
// we can't safely convert n->low to int64_t
#if INT64_MIN == -INT64_MAX
// in this case, we can't store -n->low in an int64_t at all!
// therefore, it must be out of range
return minCap;
#else
// in this case, INT64_MIN == -INT64_MAX - 1
if (n->low > ((uint64_t) INT64_MAX) + 1)
// we still can't store -n->low in an int64_t
return minCap;
// only one option left
ret = INT64_MIN;
#endif
} else {
// -n->low can safely be stored in an int64_t, so do so
ret = (int64_t) (n->low);
ret = -ret;
}
if (ret < min)
return minCap;
return ret;
}
if (n->high > 0)
return maxCap;
if (n->low > (uint64_t) max)
return maxCap;
return (int64_t) (n->low);
}
uint64_t timerprivInt128ToUint64(const timerprivInt128 *n, uint64_t max, uint64_t maxCap)
{
if (n->neg)
return 0;
if (n->high != 0)
return maxCap;
if (n->low > maxCap)
return maxCap;
return n->low;
}