7

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
einpoklum
  • 118,144
  • 57
  • 340
  • 684
tunafish24
  • 2,288
  • 6
  • 28
  • 47
  • 9
    That can probably be done but I hope you already know that it's really *odd*, for example you can't represent `1.01` that way because it would have `x = 1, y = 1` but that actually means `1.1` already. Meanwhile `x = 1, y = 10` *also* means `1.1` – harold Apr 05 '20 at 18:19
  • @harold good catch with 1.1 = 1.10, still would like to know how its done. – tunafish24 Apr 05 '20 at 18:22
  • What do you mean by float and integer? is it 4 byte float? ist it 4 byte integer? signed or unsigned? What means efficient? – Tarek Dakhran Apr 05 '20 at 18:23
  • With the `log `function you can find how many digits a number has. Then divide by 10 expont this digits-number – Ripi2 Apr 05 '20 at 18:25
  • 1
    What are you planning to do with the float? – einpoklum Apr 05 '20 at 18:26
  • 1
    @TarekDakhran unsigned values, int value would be 2-bytes max each. Floating value could be a double – tunafish24 Apr 05 '20 at 18:30
  • 5
    Why do you need this kind of conversion? Serialize? Do you need the double->2 ints conversion? What about representing `1.01`? – Ripi2 Apr 05 '20 at 18:55
  • 1
    You should tell whether you want the **NEAREST** float or an approximation to a few bits (a few ulp) – aka.nice Apr 06 '20 at 08:22
  • 1
    Most of the answers are subject to double rounding problems. For example, `2.5001 != (2.0 + 0.5001)` that's why specifying if answer has to be correctly rounded or not will make a difference on *efficiency* – aka.nice Apr 06 '20 at 10:40
  • @tunafish24 "_whats the most efficient way_" - seems to be a question without a definite answer - or what kind of answer would make you accept it? – Ted Lyngmo Nov 05 '20 at 16:05

9 Answers9

7

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).

Toastrackenigma
  • 7,604
  • 4
  • 45
  • 55
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 2
    I could not find the word "fast" in the question. My interpretation of the word "efficient" is different. And the execution speed has no meaning in this more academic question – A M Apr 05 '20 at 19:38
  • That's interesting. Thanks! – 3Dave Apr 05 '20 at 20:40
  • @einpoklum, I updated my answer, please reevaluate. – Tarek Dakhran Apr 05 '20 at 20:48
  • @3Dave: Now even more interesting... :-P – einpoklum Apr 05 '20 at 21:45
  • @einpoklum Damn lookup tables getcha every time. Nice solution, though. :) – 3Dave Apr 05 '20 at 21:50
  • @einpoklum I did earlier. :) – 3Dave Apr 05 '20 at 22:01
  • @einpoklum look at my post, version 2 and benchmark it if you can, I think this will do much better than the first – aliberro Apr 05 '20 at 22:13
  • @einpoklum So, what are the numbers for my solution with `uint32_t`? I see the table update, but no numbers there – Tarek Dakhran Apr 05 '20 at 22:24
  • @TarekDakhran: Going to sleep now. Also, please implement a conversion of two `int`s into a `float` so we can compare everything. Alternatively - you can convert everyone else's answers to take `uint32_t`s and return `double`s and run that benchmark. – einpoklum Apr 05 '20 at 22:36
  • 1
    I compared your implementation to mine myself. Here are the results http://quick-bench.com/xao6aY9m0YHMpr55J1oXjaspUgM . Good night. If we take your table lookup for pow_10 and my digits_10 together, this will give fastest possible solution. – Tarek Dakhran Apr 05 '20 at 22:38
  • 1
    @TarekDakhran: No more UB, but - our numbers both look a bit suspicious, as though everything is being optimized away. – einpoklum Apr 05 '20 at 23:19
  • 1
    This doesn't appear to be an answer in its own right - it's a meta-answer about other answers. [From Review](https://stackoverflow.com/review/low-quality-posts/26022995). – Wai Ha Lee May 04 '20 at 16:27
  • 1
    @WaiHaLee: It is a partial answer, in the sense of being important to understand what's efficient and what's not. Also, there are multiple such questions, AFAICR, which have answers benchmarking other answers. Finally, viewers find this useful. – einpoklum May 04 '20 at 16:32
6
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.

3Dave
  • 28,657
  • 18
  • 88
  • 151
4

(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.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • For `(0, 9)`, this returns 0.900000035762786865234375 but 0.89999997615814208984375 is a closer representable value to .9 (with IEEE-754 binary32). For `(0, 10)` this returns 1 but should return a value near .10. – Eric Postpischil Apr 06 '20 at 18:51
  • @EricPostpischil: See edit. About the non-optimality - I believe that is a reasonable price to pay for efficiency; but - do you have a concrete suggestion? Perhaps checking whether adding or subtracting epsilon (depending on the direction of error) brings us closer? – einpoklum Apr 06 '20 at 19:01
  • `(x*p + y)/p`, where `p` is a `float` with the selected power of 10, will produce the best answer as long as `x*p+y` is within the range where the floating-point format represents all integers (up to 2^24 for IEEE-754 binary32), but then you have an unwanted division. – Eric Postpischil Apr 06 '20 at 19:07
  • @EricPostpischil: 1. What about `(x *(1/p) + y) * (1/p)`? 2. We can't assume `x*p+y` is within that range. In fact, for uniformly-sampled integers (albeit an unlikely distribution in practice), this will rarely be the case. – einpoklum Apr 06 '20 at 19:10
  • I presume you mean `(x*p + y) * (1/p)`, not `x *(1/p) + y) * (1/p)`. Either way, it is not possible since 1/p is not representable in binary floating point for p over 1. It must first be converted to the floating-point format, and that introduces a rounding error that will likely cause some results to be an ULP off. – Eric Postpischil Apr 07 '20 at 00:49
  • @EricPostpischil: Yes, that's what I meant. So, it seems that avoiding a division and the rounding error are bound together. – einpoklum Apr 07 '20 at 06:06
2

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

Tarek Dakhran
  • 2,021
  • 11
  • 21
  • Here is the benchmark compared to @3Dave implementation http://quick-bench.com/0KN46EiDISDW8DQKdxNnbVMOyB0 – Tarek Dakhran Apr 05 '20 at 21:03
  • Tarek - while it's true that OP said that in a comment, it's not what OP asked and what most people answered. That means everybody else's answers can't be directly compared against yours. Can you please also provide an `int` solution for benchmarking? Obviously with `uint16_t` it's somewhat faster. – einpoklum Apr 05 '20 at 21:58
  • Updated to uint32_t to align with other answers. Let's see how fast is this. http://quick-bench.com/4Ur-hb9ArPzFiPIajgaJ8w-3T2Y – Tarek Dakhran Apr 05 '20 at 22:17
0

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
gharabat
  • 177
  • 2
  • 13
  • You probably mean `std::cout << x+"."+y` – fas Apr 05 '20 at 18:26
  • @user3365922, yes. – gharabat Apr 05 '20 at 18:27
  • 2
    This does not answer the question. OP didn't say they wanted to just print the value. – einpoklum Apr 05 '20 at 18:39
  • @einpoklum how is that?, plus the cout was for demonstrations only, he can do what ever he want with the resulted data, the important is the idea. – gharabat Apr 05 '20 at 19:31
  • 1
    @einpoklum: Just out of curiosity: Are you downvoting all these answers? – A M Apr 05 '20 at 19:39
  • @ArminMontigny: I downvoted the answers suggesting the use of strings. – einpoklum Apr 05 '20 at 19:46
  • @einpoklum why? i could use log and/or tons of mathematical methods, but why when there is a much easier way using strings? – gharabat Apr 05 '20 at 19:48
  • 1
    @einpoklum: In the first comment to the questions, user harold asked, how to produce 1.01. That is indeed a good question. Without strings or character operations, this will be difficult to achieve. In your post, you did not answer the question at all. But I do not downvote it. Basically I do never downvote answers (except when they are flat wrong and misleading) and especially, if I can see some effort in there. But, hey, this is (still) a free world. Everybody can do what he wants. – A M Apr 05 '20 at 20:04
0

(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.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
0

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;
}
aliberro
  • 530
  • 2
  • 9
  • 1
    Note `pow` and `log10` operate in floating point. Converting the results back to integers can inject some very unfortunate errors. Use with caution. There is often a faster, integer-only alternative that doesn't have these problems. – user4581301 Apr 05 '20 at 19:04
  • What if `decimal` is 0? – einpoklum Apr 05 '20 at 20:11
  • @einpoklum You're right, then we just have to add this line instead of using: int base = log10(decimal); We may use this: int base = decimal == 0 ? -1 : log10(decimal); I'll update my post – aliberro Apr 05 '20 at 21:09
  • 1
    Please use `x` and `y` for compatibility for other answers and to make my work simpler. – einpoklum Apr 05 '20 at 22:17
  • Version 2 produces incorrect results. Recheck it please. Also - don't have multiple versions of your solution, just one. – einpoklum Apr 05 '20 at 22:20
  • @einpoklum sorry about that I fixed it, if this is better then i might as will make it the only version – aliberro Apr 05 '20 at 22:32
  • @aliberro Sorry, it's still slow :-( – einpoklum Apr 05 '20 at 22:41
  • For (1, 18), both versions return 1.1800000667572021484375, but 1.17999994754791259765625 is a closer representable value (in IEEE-754 binary32). – Eric Postpischil Apr 06 '20 at 18:55
  • @aliberro: Perhaps GCC uses excess precision in some of the calculations. The C++ standard permits this, but it cannot be relied on. When `1.f + 18.f/100.f` is computed using IEEE-754 binary32 arithmetic, the result is 1.1800000667572021484375. – Eric Postpischil Apr 06 '20 at 19:04
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;
}
Ripi2
  • 7,031
  • 1
  • 17
  • 33
  • Dividing by 10, nine or ten times, is not fast. – einpoklum Apr 05 '20 at 18:41
  • @einpoklum Likely faster that 'log' funct or whatever method you use to figure out the number of digits. Well, perhaps a 32 if-tree depth is faster. – Ripi2 Apr 05 '20 at 18:41
  • 1
    It's _much_ slower. 2.6x times slower on typical hardware. - see my benchmark answer. – einpoklum Apr 05 '20 at 19:23
  • 1
    Division is notoriously slow, while a good logarithm implementation is a largely polynomial evaluation combined with some bit manipulation. – Eric Postpischil Apr 06 '20 at 09:01
  • For `(0, 10)`, this returns 1 but should return a value near .10. – Eric Postpischil Apr 06 '20 at 18:53
  • @EricPostpischil Oh, yeah. It should be `(dp >=1)`. But the code I posted is just a quick squeleton, not a full-debugged code. The OP's question is vague, so the answers too. – Ripi2 Apr 06 '20 at 18:56
0

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.

fkantner
  • 79
  • 5