将64位整数相除,就好像被除数向左移64位,而没有128位类型

时间:2019-02-26 15:59:30

标签: c integer-division 128-bit int128

致歉的标题致歉。我不确定如何更好地描述我要完成的工作。我基本上是想做相反的事情 getting the high half of a 64-bit multiplication在C平台上

int64_t divHi64(int64_t dividend, int64_t divisor) {
    return ((__int128)dividend << 64) / (__int128)divisor;
}

由于缺少对__int128的支持而无法使用。

1 个答案:

答案 0 :(得分:1)

无需多词划分即可完成

假设我们想做⌊2 64 × x y ⌋,那么我们可以像这样变换表达式

Unicode math: ⌊(2^64 x)/y⌋=⌊(⌊2^64/y⌋+{2^64/y})x⌋=⌊2^64/y⌋x+⌊{2^64/y}x┤

根据该问题How to compute 2⁶⁴/n in C?,第一个词通常以((-y)/y + 1)*x的形式完成

第二项等效于(2 64 %y)/ y * x,有点棘手。我尝试了各种方法,但是如果仅使用整数运算,则都需要128位乘法和128/64除法。可以使用算法来计算以下问题中的MulDiv64(a, b, c) = a*b/c

但是它们可能很慢,如果您具有这些功能,则可以像MulDiv64(x, UINT64_MAX, y) + x/y + something一样轻松地计算整个表达式,而不会弄乱上面的转换

如果long double具有64位或更高的精度,则似乎是最简单的方法。所以现在可以通过(2 64 %y)/(long double)y * x

来完成
uint64_t divHi64(uint64_t x, uint64_t y) {
    uint64_t mod_y = UINT64_MAX % y + 1;
    uint64_t result = ((-y)/y + 1)*x;
    if (mod_y != y)
        result += (uint64_t)((mod_y/(long double)y)*x);
    return result;
}

为简化起见,省略了溢出检查。如果您需要签名分割,则需要稍作修改


如果您定位的是 64位Windows ,但您使用的MSVC没有__int128,则now it has a 64-bit divide intrinsic可以大大简化工作,而无需使用128-位整数类型。尽管您仍然需要处理溢出,因为div instruction会在这种情况下引发异常

uint64_t divHi64(uint64_t x, uint64_t y) {
    uint64_t high, remainder;
    uint64_t low = _umul128(UINT64_MAX, y, &high);
    if (x <= high /* && 0 <= low */)
        return _udiv128(x, 0, y, &remainder);
    // overflow case
    errno = EOVERFLOW;
    return 0;
}

上面的溢出检查可以简化为检查x = y则结果将溢出


另请参见


对16/16位除法的详尽测试表明,我的解决方案适用于所有情况。但是,即使double的精度超过16位,您仍然需要float,否则偶尔会返回少于一的结果。可以通过在截断之前添加 epsilon 值来解决该问题:(uint64_t)((mod_y/(long double)y)*x + epsilon)。这意味着,如果不使用 epsilon 校正结果,则需要gcc中的__float128(或-m128bit-long-double option)以进行精确的64/64位输出。但是that type is available on 32-bit targets__int128不同,#include <thread> #include <iostream> #include <limits> #include <climits> #include <mutex> std::mutex print_mutex; #define MAX_THREAD 8 #define NUM_BITS 27 #define CHUNK_SIZE (1ULL << NUM_BITS) // typedef uint32_t T; // typedef uint64_t T2; // typedef double D; typedef uint64_t T; typedef unsigned __int128 T2; // the type twice as wide as T typedef long double D; // typedef __float128 D; const D epsilon = 1e-14; T divHi(T x, T y) { T mod_y = std::numeric_limits<T>::max() % y + 1; T result = ((-y)/y + 1)*x; if (mod_y != y) result += (T)((mod_y/(D)y)*x + epsilon); return result; } void testdiv(T midpoint) { T begin = midpoint - CHUNK_SIZE/2; T end = midpoint + CHUNK_SIZE/2; for (T i = begin; i != end; i++) { T x = i & ((1 << NUM_BITS/2) - 1); T y = CHUNK_SIZE/2 - (i >> NUM_BITS/2); // if (y == 0) // continue; auto q1 = divHi(x, y); T2 q2 = ((T2)x << sizeof(T)*CHAR_BIT)/y; if (q2 != (T)q2) { // std::lock_guard<std::mutex> guard(print_mutex); // std::cout << "Overflowed: " << x << '&' << y << '\n'; continue; } else if (q1 != q2) { std::lock_guard<std::mutex> guard(print_mutex); std::cout << x << '/' << y << ": " << q1 << " != " << (T)q2 << '\n'; } } std::lock_guard<std::mutex> guard(print_mutex); std::cout << "Done testing [" << begin << ", " << end << "]\n"; } uint16_t divHi16(uint32_t x, uint32_t y) { uint32_t mod_y = std::numeric_limits<uint16_t>::max() % y + 1; int result = ((((1U << 16) - y)/y) + 1)*x; if (mod_y != y) result += (mod_y/(double)y)*x; return result; } void testdiv16(uint32_t begin, uint32_t end) { for (uint32_t i = begin; i != end; i++) { uint32_t y = i & 0xFFFF; if (y == 0) continue; uint32_t x = i & 0xFFFF0000; uint32_t q2 = x/y; if (q2 > 0xFFFF) // overflowed continue; uint16_t q1 = divHi16(x >> 16, y); if (q1 != q2) { std::lock_guard<std::mutex> guard(print_mutex); std::cout << x << '/' << y << ": " << q1 << " != " << q2 << '\n'; } } } int main() { std::thread t[MAX_THREAD]; for (int i = 0; i < MAX_THREAD; i++) t[i] = std::thread(testdiv, std::numeric_limits<T>::max()/MAX_THREAD*i); for (int i = 0; i < MAX_THREAD; i++) t[i].join(); std::thread t2[MAX_THREAD]; constexpr uint32_t length = std::numeric_limits<uint32_t>::max()/MAX_THREAD; uint32_t begin, end = length; for (int i = 0; i < MAX_THREAD - 1; i++) { begin = end; end += length; t2[i] = std::thread(testdiv16, begin, end); } t2[MAX_THREAD - 1] = std::thread(testdiv, end, UINT32_MAX); for (int i = 0; i < MAX_THREAD; i++) t2[i].join(); std::cout << "Done\n"; } 仅在64位目标上受支持,因此生活会更轻松一些。当然,如果只需要非常接近的结果,则可以按原样使用该功能

下面是我用于验证的代码

if ($('.rate2-star5').is(':checked')) {
  alert("checked");
}