高效地将两个整数 x 和 y 转换为浮点数 x.y

7

在C++中,将两个整数X和Y转换为X.Y浮点值的最有效方法是什么?

例如:

 X = 3, Y = 1415 -> 3.1415

 X = 2, Y = 12   -> 2.12

9
可能可以做到,但我希望你已经知道这很奇怪。例如,你无法以这种方式表示1.01,因为它将具有x = 1,y = 1,但实际上意味着1.1。同时,x = 1,y = 10也意味着1.1 - harold
1
你打算如何处理这个浮点数? - einpoklum
1
@TarekDakhran 无符号值,int 值最多为 2 字节。浮点值可以是 double。 - tunafish24
5
为什么需要这种转换?序列化?您是否需要将double转换为2个int?那1.01怎样表示? - Ripi2
1
大多数答案都存在双舍入问题。例如,2.5001 != (2.0 + 0.5001),因此指定答案是否必须正确舍入将对效率产生影响。 - aka.nice
显示剩余5条评论
9个回答

7

以下是在我的计算机上将两个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
  • CPU: 四核英特尔Core i5-7600K速度/最小值/最大值: 4000/800/4200 MHz
  • Devuan GNU/Linux 3
  • 内核: 5.2.0-3-amd64 x86_64
  • GCC 9.2.1,使用标志:-O3 -march=native -mtune=native

基准测试代码 (Github Gist)。


2
我在问题中找不到“快速”一词。我对“高效”的解释不同。而且在这个更学术的问题中,执行速度没有意义。 - A M
1
我自己对比了你的实现和我的实现。这是结果:http://quick-bench.com/xao6aY9m0YHMpr55J1oXjaspUgM 。晚安。如果我们将你的 pow_10 查表和我的 digits_10 结合起来,就可以得到最快的解决方案。 - Tarek Dakhran
1
@TarekDakhran: 没有更多未定义行为,但是 - 我们的数字看起来都有些可疑,好像每件事都被优化掉了。 - einpoklum
1
这似乎不是一个独立的答案,而是关于其他答案的元回答。来自审核 - Wai Ha Lee
1
@WaiHaLee:这是一个部分回答,从理解什么是高效的和什么不是的角度来说很重要。此外,我记得有多个类似的问题,它们有基准测试其他答案的答案。最后,观众会发现这很有用。 - einpoklum
显示剩余8条评论

6
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

编辑

小修改以取消除法操作。


1
那么 y == 0 呢? - einpoklum
@einpoklum 好的,没错。已经修复了。 - 3Dave
Errr...1234 / 1000 != 0.1234 - Ripi2
@Ripi2 我发帖太快了。愚蠢的 off-by-1 和一个边缘情况。我应该回去睡觉,重新开始一天。 - 3Dave
不要随意编写代码,否则会出现错误。例如,如果 y=1000,那么 log10y(y)+1 就无法正常工作。 - Ripi2
显示剩余2条评论

4

(改进后的解决方案)

最初,我想通过编写整数专用版本的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值。将其调整为负值相当容易且不太耗费资源。
  • 我不确定它是否绝对最优。也许使用二分查找来查找y的数字位数会更好?
  • 循环会使代码看起来更美观,但编译器需要展开它。它会展开循环并预先计算所有这些浮点数吗?我不确定。

对于(0, 9),该函数返回的值是0.900000035762786865234375,但0.89999997615814208984375是一个更接近IEEE-754 binary32规范下的.9的可表示值。对于(0, 10),该函数返回的值是1,但应该返回一个接近.10的值。 - Eric Postpischil
@EricPostpischil:请看编辑。关于非最优性——我认为这是为了效率而付出的合理代价;但是,你有具体的建议吗?也许检查添加或减去 epsilon(取决于误差方向)是否能使我们更接近? - einpoklum
p是一个选定的10的幂时,(x*p + y)/p将产生最佳答案,只要x*p+y在浮点格式表示所有整数的范围内(对于IEEE-754二进制32位,最高可达2^24),但这样就会产生不必要的除法。 - Eric Postpischil
@EricPostpischil:1. (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 Postpischil
@EricPostpischil:是的,这就是我的意思。因此,避免除法和舍入误差是相互关联的。 - einpoklum

2
我花了一些时间来优化之前的答案,最终得到了这个结果。
inline 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));
}


这是与@3Dave实现相比的基准测试 http://quick-bench.com/0KN46EiDISDW8DQKdxNnbVMOyB0 - Tarek Dakhran
Tarek - 尽管OP在评论中提到了这一点,但这并不是OP所问的问题,也不是大多数人所回答的问题。这意味着其他所有人的答案不能直接与您的答案进行比较。能否请您提供一个int解决方案来进行基准测试?显然,使用uint16_t会更快一些。 - einpoklum
已更新为uint32_t以与其他答案对齐。让我们看看这有多快。http://quick-bench.com/4Ur-hb9ArPzFiPIajgaJ8w-3T2Y - Tarek Dakhran

0
简单而非常快速的解决方案是将值x和y都转换为字符串,然后将它们连接起来,然后将结果转换为浮点数,具体操作如下:
#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
@user3365922,是的。 - gharabat
2
这并没有回答问题。 OP 没有说他们只想打印值。 - einpoklum
1
@einpoklum:只是出于好奇,你是在给所有这些答案投反对票吗? - A M
1
@einpoklum:在问题的第一个评论中,用户哈罗德问如何得到1.01。这确实是一个好问题。如果没有字符串或字符操作,这将很难实现。在您的帖子中,您根本没有回答这个问题。但我不会对其进行负分评定。基本上,我从不对答案进行负分评定(除非它们完全错误且具有误导性),特别是如果我能看到其中的一些努力。但是,嘿,这仍然是自由的世界。每个人都可以做他想做的事情。 - A M
显示剩余3条评论

0

试试这个

#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;
}

1
请注意,powlog10是浮点数操作。将结果转换回整数可能会导致一些非常不幸的错误。请谨慎使用。通常有一种更快的、仅限于整数的替代方法,不会出现这些问题。 - user4581301
@einpoklum 您是正确的,那么我们只需要添加这一行而不是使用: int base = log10(decimal); 我们可以使用以下内容: int base = decimal == 0 ? -1 : log10(decimal); 我会更新我的帖子。 - aliberro
1
请使用 xy 以兼容其他答案,并使我的工作更简单。 - einpoklum
版本2生成了不正确的结果。请重新检查。另外 - 不要有多个版本的解决方案,只需一个即可。 - einpoklum
@aliberro 抱歉,它仍然很慢 :-( - einpoklum
显示剩余4条评论

0
double IntsToDbl(int ipart, int decpart)
{
    //The decimal part:
    double dp = (double) decpart;
    while (dp > 1)
    {
        dp /= 10;
    }

    //Joint boths parts
    return ipart + dp;
}

除以10,九次或十次,速度不快。 - einpoklum
@einpoklum 可能比“log”函数或您用于确定数字位数的任何方法都要快。好吧,也许32个if树深度更快。 - Ripi2
1
它慢得多。在典型硬件上慢了2.6倍。-请参见我的基准测试答案。 - einpoklum
1
除法是出了名的慢,而良好的对数实现则是大多数情况下的多项式评估加上一些位操作。 - Eric Postpischil
对于(0, 10),这返回1,但应该返回接近0.10的值。 - Eric Postpischil
@EricPostpischil 哦,是的。应该是(dp >=1)。但我发布的代码只是一个快速的框架,不是完全调试好的代码。OP的问题比较模糊,所以回答也是如此。 - Ripi2

0

(基于OP没有表明他们想要使用float的原因而回答。)

最快(最有效)的方法是隐式地进行,但实际上不做任何事情(经过编译器优化后)。

也就是说,编写一个“伪浮点”类,其成员是小数点前后x和y类型的整数;并具有用于执行您要使用浮点数执行的任何操作的运算符:operator+、operator*、operator/、operator-,甚至可能还包括pow()、log2()、log10()等函数的实现。

除非您计划直接保存4字节的浮点数以供以后使用,否则如果您已经有了下一个需要处理的操作数,那么从仅有的x和y创建浮点数已经失去了精度并浪费了时间,几乎肯定会更快。


0
如果你想要一些简单易读且易于理解的内容,可以尝试类似这样的代码:
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);
}

这个回答更为简练,但并没有帮助回答你的问题。如果你坚持使用这个数据模型,它可能有助于展示只使用两个整数的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接