TL; Versione DR:
Dal momento che non esistono problemi di overflow / underflow con unsigned
tipi di dati in C ++ puoi calcolare in modo sicuro una differenza tra i tuoi due valori. La dimensione di questa differenza in relazione all'intervallo di valori possibili indica quale valore è inferiore all'altro:
template<bool Strict = true, typename T>
bool ordered(T assumed_smaller, T assumed_larger) {
static_assert(std::is_unsigned<T>::value, "Need unsigned");
// Calculate the half of the range of the type, by using it's bit width.
constexpr T const half_range = 1u << ((sizeof(T) * CHAR_BIT) - 1);
// Calculate difference, possibly with wrap around.
T const difference = assumed_larger - assumed_smaller;
bool const not_equal = difference != 0;
if (Strict) {
// Strict mode, disallow ordered(a,b) and ordered(b,a) to be both true
return not_equal
&& ((difference < half_range)
|| ((difference == half_range)
&& (assumed_smaller < assumed_larger)));
} else {
// Non-strict, both ordered(a,b) and ordered(b,a) are true
// if they're half_range apart.
return not_equal && (difference <= half_range);
}
}
Un po 'più in dettaglio:
Assumendo numeri interi a 2 bit senza segno (quindi da 0 a 3 come valori possibili), uno ha i seguenti casi:
0 0 | false
0 1 | true
0 2 | true
0 3 | false
1 0 | false
1 1 | false
1 2 | true
1 3 | true
2 0 | false (0 2 is already true)
2 1 | false
2 2 | false
2 3 | true
3 0 | true
3 1 | false (1 3 is already true)
3 2 | false
3 3 | false
true
significa che il primo valore sulla linea deve essere considerato "prima di" il secondo valore. Si può immaginare meglio questo quando si visualizzano i possibili valori disposti in una sorta di cerchio (dal momento che vogliamo consentire il wrapping):
0
3 1
2
Due valori sono nell'ordine corretto se siamo in grado di passare dall'assunto più piccolo all'altro - andando in senso orario - in non più di 2 passi. Pertanto 3 0
è nell'ordine corretto, ma 3 2
non lo è.
Per fare ciò, è sufficiente controllare se assumed_larger - assumed_smaller <= 2
(dove 2 è la metà di max_value + 1
, o 2^(bits-1)
). Questa differenza è il numero di passi in senso orario se assumed_larger
> = assumed_smaller
e in senso antiorario altrimenti (grazie a modulo, esempio: 0 - 3 = -3 = -3 + 4 = 1 (mod 4)
).
Questo lascia il seguente problema: 0 2
e 2 0
(così come 1 3
e 3 1
) entrambi richiedono 2 passaggi, quindi con il test precedente entrambi sarebbero nell'ordine corretto, che è una violazione dell'aspettativa che "se a != b
, poi o a
prima di b
o b
prima di a
.
Poiché la domanda afferma che i valori non saranno "distanti", questo non sarebbe un problema, ma per correggerlo è necessario gestire il caso assumed_larger - assumed_smaller == 2
specialmente confrontando effettivamente entrambi i valori.
bool seq_ordered(uint32_t assumed_smaller, uint32_t assumed_larger) {
constexpr uint32_t const half_range = 1 << ((sizeof(uint32_t) * CHAR_BIT) / 2);
uint32_t difference = assumed_larger - assumed_smaller;
return (difference != 0)
&& ((difference < half_range)
|| ((difference == half_range)
&& (assumed_smaller < assumed_larger)));