case 6:
for (a = 0; a < N; a++) {
uint64_t b = a + c;
uint64_t a1, b1;
if (a > b) { a1 = a; b1 = b; }
else { a1 = b; b1 = a; }
uint64_t cc = b1 * a1;
c += cc;
if (b1 > 0xffffffff) o++;
else {
uint64_t a1l = (a1 & 0xffffffff) + (a1 >> 32);
a1l = (a1 + (a1 >> 32)) & 0xffffffff;
uint64_t ab1l = a1l * b1;
ab1l = (ab1l & 0xffffffff) + (ab1l >> 32);
ab1l += (ab1l >> 32);
uint64_t ccl = (cc & 0xffffffff) + (cc >> 32);
ccl += (ccl >> 32);
uint32_t ab32 = ab1l; if (ab32 == 0xffffffff) ab32 = 0;
uint32_t cc32 = ccl; if (cc32 == 0xffffffff) cc32 = 0;
if (ab32 != cc32) o++;
}
}
break;
Этот метод сравнивает (возможно, переполнение) результат нормального умножения с результатом умножения, которое не может быть переполнено. Все расчеты по модулю (2 ^ 32 - 1).
Это сложнее и (скорее всего) не быстрее, чем метод Йенса Гастдта.
После некоторых небольших модификаций он может размножаться с точностью до 96 бит (но без контроля переполнения). Что может быть более интересно, идея этого метода может быть использована для проверки переполнения для ряда арифметических операций (умножения, сложения, вычитания).
На некоторые вопросы ответили
Прежде всего, о "your code is not portable"
. Да, код не переносимый, потому что он использует uint64_t
, который запрашивается в исходном вопросе. Строго говоря, вы не можете получить переносимый ответ с помощью (u)int64_t
, поскольку он не требуется по стандарту.
О "once some overflow happens, you can not assume the result value to be anything"
. Стандарт говорит, что неподписанные итераторы не могут переполниться. Глава 6.2.5, пункт 9:
Вычисление с использованием беззнаковых операндов никогда не может быть переполнено,
потому что результат, который не может быть представлен результирующим целым типом без знака,
уменьшено по модулю число, которое на единицу больше наибольшего значения, которое может быть
представлен результирующим типом.
Таким образом, 64-разрядное умножение без знака выполняется по модулю 2 ^ 64, без переполнения.
Теперь о "logic behind"
. «Хэш-функция» здесь не является правильным словом. Я использую только вычисления по модулю (2^32 - 1)
. Результат умножения может быть представлен как n*2^64 + m
, где m
- видимый результат, а n
означает, насколько мы переполнены. Поскольку 2^64 = 1 (mod 2^32 - 1)
, мы можем вычислить [true value] - [visible value] = (n*2^64 + m) - m = n*2^64 = n (mod 2^32 - 1)
. Если вычисленное значение n
не равно нулю, возникает переполнение. Если оно равно нулю, переполнения нет. Любые столкновения возможны только после n >= 2^32 - 1
. Этого никогда не произойдет, поскольку мы проверяем, что один из мультипликатов меньше 2^32
.