做了一个根据搜索词计算embedding向量的服务,但是算法同学发现新服务打分精度变低了,原来能保存到小数点后16位的,现在打分只有小数点后6位。
看到这问题,首先怀疑的是double类型数据被强转float类型,导致精度丢失。
float类型大概精度就是到小数点之后6,7位,来做个实验
int main(int argc, char**argv){
float a = 0.0123456789012;
std::cout << std::fixed << std::setprecision(16) << a;
return 0;
}
然后编译运行输出
ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 0
0.0123456791043282
我们发现这个输出已经跟原来的a不一样了,后面81043282是垃圾数据。
其实计算机对float的编码类型,精度没那么高,double能提供的52 位有效位、11 位指数和 1 位符号位。
参考这个帖子https://stackoverflow.com/questions/5098558/float-vs-double-precision。
但我再仔细对了上下游文件使用的pb,发现这个打分使用的是double类型。所以理论上这个double应该没有类型转换丢失问题。
这个看起来不应该是类型转换的问题。然后仔细又观察了这段代码。
context->m_query_embedding+=to_string(recommend_info->vecitem(0).vecscore(i));
于是我把这个recommend_info->vecitem(0).vecscore(i)打印出来,这个数据精度是小数点后16位没问题,但是context->m_query_embedding是小数点后6问题。显然是这个std::to_string出现了精读丢失的问题。
果不其然,谷歌搜索到了这个帖子,https://stackoverflow.com/questions/16605967/set-precision-of-stdto-string-when-converting-floating-point-values
谈到to_stiring默认设置精度为6位,如果需要更高精度需要这样设置。
#include <sstream>
template <typename T>
std::string to_string_with_precision(const T a_value, const int n = 6)
{
std::ostringstream out;
out.precision(n);
out << std::fixed << a_value;
return out.str();
}
当然这里考虑到std提供的ostringstream在多线程环境下性能不如snprintf,
std::stringstream
是类型安全的,使用运算符 <<,使用内部缓冲区,属于C++ 的一部分,性能不如sprintf
。而sprintf
不是类型安全的,不能使用 c++ 运算符,使用外部缓冲区,它只能用于从 C 继承的 POD 类型,速度很快。
具体参考这个帖子https://stackoverflow.com/questions/11574391/snprintf-vs-stdstringstream
于是对这个代码做了如下改写。
char query_embedding_with_high_precision[20];
snprintf(query_embedding_with_high_precision, 20, "%.16f", query_embedding_with_high_precision);
context->m_query_embedding += std::string(query_embedding_with_high_precision);
问题至此解决了,做到了同时保留精度和保持性能
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。