Efficiently convert two Integers x and y into the

2020-07-11 06:39发布

问题:

Given two integers X and Y, whats the most efficient way of converting them into X.Y float value in C++?

E.g.

 X = 3, Y = 1415 -> 3.1415

 X = 2, Y = 12   -> 2.12

回答1:

Here are some cocktail-napkin benchmark results, on my machine, for all solutions converting two ints to a float, as of the time of writing.

Caveat: I've now added a solution of my own, which seems to do well, and am therefore biased! Please double-check my results.

Test                              Iterations    ns / iteration
--------------------------------------------------------------
@aliberro's conversion v2         79,113,375                13
@3Dave's conversion               84,091,005                12
@einpoklum's conversion        1,966,008,981                 0
@Ripi2's conversion               47,374,058                21
@TarekDakhran's conversion     1,960,763,847                 0
  • CPU: Quad Core Intel Core i5-7600K speed/min/max: 4000/800/4200 MHz
  • Devuan GNU/Linux 3
  • Kernel: 5.2.0-3-amd64 x86_64
  • GCC 9.2.1, with flags: -O3 -march=native -mtune=native

Benchmark code (Github Gist).



回答2:

float sum = x + y / pow(10,floor(log10(y)+1));

log10 returns log (base 10) of its argument. For 1234, that'll be 3 point something.

Breaking this down:

log10(1234) = 3.091315159697223
floor(log10(1234)+1) = 4
pow(10,4) = 10000.0
3 + 1234 / 10000.0 = 3.1234. 

But, as @einpoklum pointed out, log(0) is NaN, so you have to check for that.

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

See runnable version at: https://onlinegdb.com/rkaYiDcPI

With test output:

Test: 3,1234: 3.1234
Test: 1,1000: 1.1
Test: 2,12: 2.12
Test: 0,0: 0
Test: 9,1: 9.1

Edit

Small modification to remove division operation.



回答3:

(reworked solution)

Initially, my thoughts were improving on the performance of power-of-10 and division-by-power-of-10 by writing specialized versions of these functions, for integers. Then there was @TarekDakhran's comment about doing the same for counting the number of digits. And then I realized: That's essentially doing the same thing twice... so let's just integrate everything. This will, specifically, allow us to completely avoid any divisions or inversions at runtime:

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

Additional notes:

  • This will work for y == 0; but - not for negative x or y values. Adapting it for negative value is pretty easy and not very expensive though.
  • Not sure if this is absolutely optimal. Perhaps a binary-search for the number of digits of y would work better?
  • A loop would make the code look nicer; but the compiler would need to unroll it. Would it unroll the loop and compute all those floats beforehand? I'm not sure.


回答4:

I put some effort into optimizing my previous answer and ended up with this.

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



回答5:

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

    //Joint boths parts
    return ipart + dp;
}


回答6:

Simple and very fast solution is converting both values x and y to string, then concatenate them, then casting the result into a floating number as following:

#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


回答7:

(Answer based on the fact that OP has not indicated what they want to use the float for.)

The fastest (most efficient) way is to do it implicitly, but not actually do anything (after compiler optimizations).

That is, write a "pseudo-float" class, whose members are integers of x and y's types before and after the decimal point; and have operators for doing whatever it is you were going to do with the float: operator+, operator*, operator/, operator- and maybe even implementations of pow(), log2(), log10() and so on.

Unless what you were planning to do is literally save a 4-byte float somewhere for later use, it would almost certainly be faster if you had the next operand you need to work with then to really create a float from just x and y, already losing precision and wasting time.



回答8:

Try this

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

version 2, try this out

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


回答9:

If you want something that is simple to read and follow, you could try something like this:

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

This simply reduces one integer to the first floating point less than 1 and adds it to the other one.

This does become a problem if you ever want to use a number like 1.0012 to be represented as 2 integers. But that isn't part of the question. To solve it, I would use a third integer representation to be the negative power of 10 for multiplying the second number. IE 1.0012 would be 1, 12, 4. This would then be coded as follows:

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

It a little more concise with this answer, but it doesn't help to answer your question. It might help show a problem with using only 2 integers if you stick with that data model.