c ++中的大数字问题是余弦相似性

时间:2016-05-19 18:29:48

标签: c++ precision long-integer

我正在写这个函数

double long CosineDistance(const vector<unsigned long>& a,const vector<unsigned long>& b){
  double long num = 0.0, den1 = 0.0, den2 = 0.0 ;
    for(int i = 0; i < a.size(); ++i) {
     num+=a[i]*b[i] ;
     den1+=a[i]*a[i] ;
     den2+=b[i]*b[i] ;
     } 
return num/(sqrt(den1)*sqrt(den2));
}

它的工作方式与预期的数字相同:

即。传递{1,3,8}{5,4,9}会返回0.936686(右边)

现在我正在构建的项目使用大数字(它们是散列字符串)并使用像

这样的数字

{3337682107,92015386,2479056,2478761,4153082938}

{104667454,92015386,150359366,2225484100,2479056}

根据WolframAlpha的说法,它返回给我1,我认为它是0.968597的近似值。

已经检查过溢出并且没有发生。

有没有办法解决这个问题?

由于

4 个答案:

答案 0 :(得分:3)

当您计算两个向量ab之间的余弦相似度时,以下情况属实:

CosineDistance(a*x,b*x) == CosineDinstance(a,b);

任何数字x(但不是0)。因此,您可以简单地使用双精度和适当的缩放因子x来避免溢出。

答案 1 :(得分:0)

有几个地方可能会失去精确度。

  • 当两个非常大的无符号长整数相乘时,它可能会溢出。
  • 当将无符号长整数转换为长双精度时,基本上可以忽略低位。 (截短的)
  • 当添加两个长双打时,其中一个比另一个大两个数量级,则基本上会忽略较小的双精度数。如果它只是几个数量级的大,那么较小的那个的低阶位将基本上被忽略。

在你的例子中,计算并没有失去太多精确度,1 vs .95相对来说相当接近。如果您需要计算不丢失精确度 ,那么在这里执行此操作的一种方法是使用像boost::multiprecision这样的bignum库。你可以在代码中使用long double,而不是在该库中使用无限精度有理数,如cpp::rational。然后在取平方根时将其转换为long double。

如果你说这些数字是字符串的哈希值,并且这些数字本身并没有那么重要的意义,(大概你只是想把它们聚类或什么?)那么你可以做的一件事就是选择一个哈希函数输出较小的数字,或者,修改这些数字,只说6位数。这将大大降低失去精确度的可能性。

答案 2 :(得分:0)

{3337682107,92015386,2479056,2478761,4153082938}的平方和大于2 ^ 64,这似乎是双长的尾数的典型最大尺寸。假设情况如此,那么你获得的精度与无符号长度的精度相同。

答案 3 :(得分:0)

我使用Matlab和C ++(x64 VC2013)检查了这一点,对于你的“大数字”情况,我得到了0.0314034而不是0.968597的答案。我将原始数字用作double,而不是从int转换为double

这是我检查事物的方式。

#include <cmath>
#include <vector>
#include <iostream>
using namespace std;

double CosineDistance(const vector<double> &a, const vector<double> &b);
long double CosineDistance2(const vector<long double> &a, const vector<long double> &b);
long double Cos2(const vector<unsigned long> &a, const vector<unsigned long> &b);
long double Cos3(const vector<unsigned long> &a, const vector<unsigned long> &b);

int main(int argc, char * argv[]){

    vector<double> a = { 1, 3, 8 };
    vector<double> b = { 5, 4, 9 };

    double v1 = CosineDistance(a, b);

    vector<double> a2 = { 3337.682107, 92.015386, 2.479056, 2.478761, 4153.082938 };
    vector<double> b2 = { 104.667454, 92.015386, 150.359366, 2225.484100, 2.479056 };

    double v2 = CosineDistance(a2, b2);

    vector<double> a3 = { 333.7682107, 9.2015386, .2479056, .2478761, 415.3082938 };
    vector<double> b3 = { 10.4667454, 9.2015386, 15.0359366, 222.5484100, .2479056 };

    double v3 = CosineDistance(a3, b3);

    vector<double> a4 = { .1, .3, .8 };
    vector<double> b4 = { .5, .4, .9 };

    double v4 = CosineDistance(a4, b4);

    vector<long double> a5 = { 3337682107, 92015386, 2479056, 2478761, 4153082938 };
    vector<long double> b5 = { 104667454, 92015386, 150359366, 2225484100, 2479056 };

    long double v5 = CosineDistance2(a5, b5);

    vector<unsigned long> a6 = { 3337682107, 92015386, 2479056, 2478761, 4153082938 };
    vector<unsigned long> b6 = { 104667454, 92015386, 150359366, 2225484100, 2479056 };

    long double v6 = Cos2(a6, b6);
    long double v7 = Cos3(a6, b6);

    cout << v1 << endl;
    cout << v2 << endl;
    cout << v3 << endl;
    cout << v4 << endl;
    cout << v5 << endl;
    cout << v6 << endl;
    cout << v7 << endl;

    return 0;
}

double CosineDistance(const vector<double> &a, const vector<double> &b){

    double num(0.0), den1(0.0), den2(0.0);

    for (unsigned int i = 0; i < a.size(); ++i){
        num += a[i] * b[i];
        den1 += a[i] * a[i];
        den2 += b[i] * b[i];
    }

    double res = num / (sqrt(den1) * sqrt(den2));

    return res;
}

long double CosineDistance2(const vector<long double> &a, const vector<long double> &b){

    long double num(0.0), den1(0.0), den2(0.0);

    for (unsigned int i = 0; i < a.size(); ++i){
        num += a[i] * b[i];
        den1 += a[i] * a[i];
        den2 += b[i] * b[i];
    }

    long double res = num / (sqrt(den1) * sqrt(den2));

    return res;
}

long double Cos2(const vector<unsigned long> &a, const vector<unsigned long> &b){

    vector<long double> ad(a.size());
    vector<long double> bd(b.size());
    for (unsigned int i = 0; i < a.size(); ++i){
        ad[i] = static_cast<long double>(a[i]);
        bd[i] = static_cast<long double>(b[i]);
    }

    long double num(0.0), den1(0.0), den2(0.0);

    for (unsigned int i = 0; i < a.size(); ++i){
        num += ad[i] * bd[i];
        den1 += ad[i] * ad[i];
        den2 += bd[i] * bd[i];
    }

    long double res = num / (sqrt(den1) * sqrt(den2));

    return res;
}

long double Cos3(const vector<unsigned long> &a, const vector<unsigned long> &b){

    long double num(0.0), den1(0.0), den2(0.0);

    for (unsigned int i = 0; i < a.size(); ++i){
        num += a[i] * b[i];
        den1 += a[i] * a[i];
        den2 += b[i] * b[i];
    }

    long double res = num / (sqrt(den1) * sqrt(den2));

    return res;
}

输出结果为:

0.936686
0.0314034
0.0314034
0.936686
0.0314034
0.0314034
0.581537

请注意,当我专门从unsigned long转换为long double时,我的回答与Matlab和我的其他C ++编号一致。