在C++中,将两个整数X和Y转换为X.Y浮点值的最有效方法是什么?
例如:
X = 3, Y = 1415 -> 3.1415
X = 2, Y = 12 -> 2.12
在C++中,将两个整数X和Y转换为X.Y浮点值的最有效方法是什么?
例如:
X = 3, Y = 1415 -> 3.1415
X = 2, Y = 12 -> 2.12
以下是在我的计算机上将两个int
转换为float
的所有解决方案的餐巾纸基准测试结果,截至撰写本文时。
注意:我现在添加了自己的解决方案,似乎效果不错,因此存在偏见!请仔细检查我的结果。
测试 | 迭代次数 | ns / 迭代 |
---|---|---|
@aliberro的转换v2 | 79,113,375 | 13 |
@3Dave的转换 | 84,091,005 | 12 |
@einpoklum的转换 | 1,966,008,981 | 0 |
@Ripi2的转换 | 47,374,058 | 21 |
@TarekDakhran的转换 | 1,960,763,847 | 0 |
-O3 -march=native -mtune=native
基准测试代码 (Github Gist)。
float sum = x + y / pow(10,floor(log10(y)+1));
log10
函数返回其参数的以10为底数的对数。对于1234来说,结果将会是3点几。
具体解释如下:
log10(1234) = 3.091315159697223
floor(log10(1234)+1) = 4
pow(10,4) = 10000.0
3 + 1234 / 10000.0 = 3.1234.
但是,就像 @einpoklum 指出的那样,log(0)
的结果是 NaN
,因此您必须检查它。
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
float foo(int x, unsigned int y)
{
if (0==y)
return x;
float den = pow(10,-1 * floor(log10(y)+1));
return x + y * den;
}
int main()
{
vector<vector<int>> tests
{
{3,1234},
{1,1000},
{2,12},
{0,0},
{9,1}
};
for(auto& test: tests)
{
cout << "Test: " << test[0] << "," << test[1] << ": " << foo(test[0],test[1]) << endl;
}
return 0;
}
可运行版本请见: https://onlinegdb.com/rkaYiDcPI
带有测试输出:
测试: 3,1234: 3.1234
测试: 1,1000: 1.1
测试: 2,12: 2.12
测试: 0,0: 0
测试: 9,1: 9.1
编辑
小修改以取消除法操作。
y=1000
,那么 log10y(y)+1
就无法正常工作。 - Ripi2(改进后的解决方案)
最初,我想通过编写整数专用版本的10的幂和10的幂除法函数来提高性能。然后有@TarekDakhran评论说要对数字计数做同样的事情。然后我意识到:这本质上是在做两次相同的事情...所以让我们把它们整合起来。这将使我们能够完全避免在运行时进行任何除法或反转:
inline float convert(int x, int y) {
float fy (y);
if (y == 0) { return float(x); }
if (y >= 1e9) { return float(x + fy * 1e-10f); }
if (y >= 1e8) { return float(x + fy * 1e-9f); }
if (y >= 1e7) { return float(x + fy * 1e-8f); }
if (y >= 1e6) { return float(x + fy * 1e-7f); }
if (y >= 1e5) { return float(x + fy * 1e-6f); }
if (y >= 1e4) { return float(x + fy * 1e-5f); }
if (y >= 1e3) { return float(x + fy * 1e-4f); }
if (y >= 1e2) { return float(x + fy * 1e-3f); }
if (y >= 1e1) { return float(x + fy * 1e-2f); }
return float(x + fy * 1e-1f);
}
备注:
y == 0
;但不适用于负数x或y值。将其调整为负值相当容易且不太耗费资源。(0, 9)
,该函数返回的值是0.900000035762786865234375,但0.89999997615814208984375是一个更接近IEEE-754 binary32规范下的.9的可表示值。对于(0, 10)
,该函数返回的值是1,但应该返回一个接近.10的值。 - Eric Postpischilp
是一个选定的10的幂时,(x*p + y)/p
将产生最佳答案,只要x*p+y
在浮点格式表示所有整数的范围内(对于IEEE-754二进制32位,最高可达2^24),但这样就会产生不必要的除法。 - Eric Postpischil(x *(1/p) + y) * (1/p)
怎么样?2. 我们不能假设x*p+y
在该范围内。 实际上,对于均匀采样的整数(尽管在实践中分布不太可能),这很少是真的。 - einpoklum(x*p + y) * (1/p)
,而不是x *(1/p) + y) * (1/p)
。无论哪种方式,都不可能实现,因为对于大于1的p,1/p在二进制浮点数中无法表示。必须先将其转换为浮点格式,这会引入舍入误差,可能导致一些结果偏离一个ULP。 - Eric Postpischilinline uint32_t digits_10(uint32_t x) {
return 1u
+ (x >= 10u)
+ (x >= 100u)
+ (x >= 1000u)
+ (x >= 10000u)
+ (x >= 100000u)
+ (x >= 1000000u)
+ (x >= 10000000u)
+ (x >= 100000000u)
+ (x >= 1000000000u)
;
}
inline uint64_t pow_10(uint32_t exp) {
uint64_t res = 1;
while(exp--) {
res *= 10u;
}
return res;
}
inline double fast_zip(uint32_t x, uint32_t y) {
return x + static_cast<double>(y) / pow_10(digits_10(y));
}
int
解决方案来进行基准测试?显然,使用uint16_t
会更快一些。 - einpoklum#include <string>
#include <iostream>
std::string x_string = std::to_string(x);
std::string y_string = std::to_string(y);
std::cout << x_string +"."+ y_string ; // the result, cast it to float if needed
std::cout << x+"."+y
。 - fas试试这个
#include <iostream>
#include <math.h>
using namespace std;
float int2Float(int integer,int decimal)
{
float sign = integer/abs(integer);
float tm = abs(integer), tm2 = abs(decimal);
int base = decimal == 0 ? -1 : log10(decimal);
tm2/=pow(10,base+1);
return (tm+tm2)*sign;
}
int main()
{
int x,y;
cin >>x >>y;
cout << int2Float(x,y);
return 0;
}
版本2,试一下
#include <iostream>
#include <cmath>
using namespace std;
float getPlaces(int x)
{
unsigned char p=0;
while(x!=0)
{
x/=10;
p++;
}
float pow10[] = {1.0f,10.0f,100.0f,1000.0f,10000.0f,100000.0f};//don't need more
return pow10[p];
}
float int2Float(int x,int y)
{
if(y == 0) return x;
float sign = x != 0 ? x/abs(x) : 1;
float tm = abs(x), tm2 = abs(y);
tm2/=getPlaces(y);
return (tm+tm2)*sign;
}
int main()
{
int x,y;
cin >>x >>y;
cout << int2Float(x,y);
return 0;
}
pow
和log10
是浮点数操作。将结果转换回整数可能会导致一些非常不幸的错误。请谨慎使用。通常有一种更快的、仅限于整数的替代方法,不会出现这些问题。 - user4581301x
和 y
以兼容其他答案,并使我的工作更简单。 - einpoklumdouble IntsToDbl(int ipart, int decpart)
{
//The decimal part:
double dp = (double) decpart;
while (dp > 1)
{
dp /= 10;
}
//Joint boths parts
return ipart + dp;
}
(0, 10)
,这返回1,但应该返回接近0.10的值。 - Eric Postpischil(dp >=1)
。但我发布的代码只是一个快速的框架,不是完全调试好的代码。OP的问题比较模糊,所以回答也是如此。 - Ripi2(基于OP没有表明他们想要使用float
的原因而回答。)
最快(最有效)的方法是隐式地进行,但实际上不做任何事情(经过编译器优化后)。
也就是说,编写一个“伪浮点”类,其成员是小数点前后x和y类型的整数;并具有用于执行您要使用浮点数执行的任何操作的运算符:operator+、operator*、operator/、operator-,甚至可能还包括pow()、log2()、log10()等函数的实现。
除非您计划直接保存4字节的浮点数以供以后使用,否则如果您已经有了下一个需要处理的操作数,那么从仅有的x和y创建浮点数已经失去了精度并浪费了时间,几乎肯定会更快。
float convertToDecimal(int x)
{
float y = (float) x;
while( y > 1 ){
y = y / 10;
}
return y;
}
float convertToDecimal(int x, int y)
{
return (float) x + convertToDecimal(y);
}
这只是将一个整数减少到小于1的第一个浮点数,并将其添加到另一个整数。
如果您想要使用像1.0012这样的数字表示为2个整数,那么这确实会成为一个问题。但这不是问题的一部分。为了解决这个问题,我会使用第三个整数表示负10的幂,以便将第二个数字相乘。例如,1.0012将变为1、12、4。然后可以编写以下代码:
float convertToDecimal(int num, int e)
{
return ((float) num) / pow(10, e);
}
float convertToDecimal(int x, int y, int e)
{
return = (float) x + convertToDecimal(y, e);
}
这个回答更为简练,但并没有帮助回答你的问题。如果你坚持使用这个数据模型,它可能有助于展示只使用两个整数的问题。
1.01
,因为它将具有x = 1,y = 1
,但实际上意味着1.1
。同时,x = 1,y = 10
也意味着1.1
。 - harold1.01
怎样表示? - Ripi22.5001 != (2.0 + 0.5001)
,因此指定答案是否必须正确舍入将对效率产生影响。 - aka.nice