Делимое может имеет порядок, например,
Ну то есть в 64-битный регистр делимое не влезает.
Главное что при x64 делитель влезает в регистр и деление выполняется одной-двумя командами, суммарно 100-200 тактов (одной командой можно обойтись при условии что делитель больше сдвинутого вправо на 64 бита делимого, что для делимого
выполняется для делителей больше
, т.е. для 99.995% всего диапазона). На x86 делитель в регистр не влезает и возможно быстрее будет классический цикл на 50 сдвигов и вычитаний, частично развёрнутый в условные пересылки командами cmov.
А ещё можно сделать финт ушами: использовать FDIV, которая выдаст
примерный результат за 24 такта, который парой-тройкой умножений или полдесятком сложений/вычитаний (и то и то за пару десятков тактов) довести до точного. А в AVX есть команда VDIVPD, которая за 35 тактов выдаст сразу 4 итерации, которые тоже тактов за 30-40 наверняка можно досчитать до точных значений, сразу все 4, итого в среднем 15-20 тактов на итерацию.
Как всё это объяснить компилятору ЯВУ - не спрашивайте!
-- 13.12.2019, 18:28 --Ну и чисто алгоритмически.
Плюс здесь целочисленная арифметика, а не приблизительные вычисления, поэтому важна каждая единица в частном и остатке.
Можно не вычислять на каждой итерации деление, а вычислять скажем с шагом 16-1024, промежуточные же получать линейной интерполяцией, что потребует лишь трёх умножений (третье - для проверки и коррекции результата если ошиблись на единицу) и одного сдвига вправо. Ускорение в десятки раз, до скорости трёх умножений (два из которых кстати выполнятся параллельно) вместо одного деления. И работает для достаточно больших делителей, например при делимом
и интерполяции 1:16 делитель должен быть больше
, что покрывает 99.99% диапазона. Для интерполяции 1:1024 делитель должен быть больше
, но и это 99.9% всего диапазона.
Примерно так (алгоритм, не исходный код):
w=10**30;//w=qn+r, w=10^30, n=10^11..10^15
for (n=10**11; n<10**15; n+=16) {
a=w\n; b=w\(n+16);
for (i=0; i<16; i++) {
ni=n+i;
q=(a*(16-i)+b*i)\16;//Линейная интерполяция
r=w-q*ni;
while (r<0) {q--; r+=ni}//Коррекция если результат завышен, выполняется 0-1 раз
while (r>=ni) {q++; r-=ni}//Коррекция если результат занижен, выполняется 0-1 раз
...
}
}
Hint: оба умножения в линейной интерполяции тоже можно заменить на сложение/вычитание, что ускорит вообще до одного умножения и сложений/вычитаний.
И да, пороги я бы не считал заранее, а сделал бы автовыбор алгоритма, или прямое деление если делитель маленький, или линейную интерполяцию если он уже достаточно большой. И переходил бы от первого ко второму проверяя точность интерполяции одного (или пары) из чисел внутри диапазона интерполяции. При 1:16 это прилично времени займёт, но можно же и сразу 1:256 или более сделать, где это время растворится. Да и в любом случае это будет не более чем 0.01% от всего времени.