Эффективно преобразовать два целых числа x и y в число с плавающей точкой xy - PullRequest
6 голосов
/ 05 апреля 2020

Учитывая два целых числа X и Y, каков наиболее эффективный способ преобразования их в значение с плавающей запятой в C ++?

Например,

 X = 3, Y = 1415 -> 3.1415

 X = 2, Y = 12   -> 2.12

Ответы [ 9 ]

7 голосов
/ 05 апреля 2020

Вот некоторые результаты теста коктейльной салфетки на моей машине для всех решений, конвертирующих два int s в float на момент написания.

Предупреждение: я сейчас добавил собственное решение, которое, кажется, хорошо, и поэтому предвзято! Пожалуйста, перепроверьте мои результаты.

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
  • Процессор: четырехъядерный Intel Core i5-7600K скорость / мин / макс: 4000/800/4200 МГц
  • Devuan GNU / Linux 3
  • Ядро: 5.2.0-3-amd64 x86_64
  • G CC 9.2.1, с флагами: -O3 -march=native -mtune=native

Контрольный код (Github Gist).

6 голосов
/ 05 апреля 2020
float sum = x + y / pow(10,floor(log10(y)+1));

log10 возвращает log (основание 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

Редактировать

Небольшая модификация для удаления операции деления.

3 голосов
/ 05 апреля 2020

(переработанное решение)

Первоначально мои мысли улучшались относительно производительности степеней 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 будет работать лучше?
  • A l oop сделает код более приятным; но компилятор должен будет развернуть его. Будет ли он развернуть l oop и вычислить все эти числа заранее? Я не уверен.
2 голосов
/ 05 апреля 2020

Я приложил некоторые усилия для оптимизации моего предыдущего ответа и в итоге получил следующее.

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

0 голосов
/ 17 апреля 2020

Если вы хотите что-то простое для чтения и отслеживания, вы можете попробовать что-то вроде этого:

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 и добавляет его к другому one.

Это становится проблемой, если вы когда-нибудь захотите использовать число типа 1.0012, которое будет представлено как 2 целых числа. Но это не часть вопроса. Чтобы решить эту проблему, я бы использовал третье целочисленное представление в качестве отрицательной степени 10 для умножения второго числа. IE 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);
}

Это немного более кратко с этим ответом, но это не поможет ответить на ваш вопрос. Это может помочь показать проблему с использованием только 2 целых чисел, если вы придерживаетесь этой модели данных.

0 голосов
/ 05 апреля 2020
double IntsToDbl(int ipart, int decpart)
{
    //The decimal part:
    double dp = (double) decpart;
    while (dp > 1)
    {
        dp /= 10;
    }

    //Joint boths parts
    return ipart + dp;
}
0 голосов
/ 05 апреля 2020

Попробуйте

#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;
}
0 голосов
/ 05 апреля 2020

(Ответ основан на том факте, что OP не указал, что они хотят использовать для float.)

Самый быстрый (самый эффективный) способ - это сделать это неявно , но на самом деле ничего не делает (после оптимизации компилятора).

То есть напишите класс "псевдо-float", члены которого являются целыми числами типов x и y до и после десятичной точки; и иметь операторы для того, что бы вы ни делали с помощью float: operator +, operator *, operator /, operator- и, возможно, даже реализации pow (), log2 (), log10 () и т. д.

Если вы не планировали буквально сохранить 4-байтовый float где-нибудь для последующего использования, почти наверняка было бы быстрее, если бы у вас был следующий операнд, с которым вам нужно работать, чтобы действительно создать float из x и у, уже теряя точность и тратя время.

0 голосов
/ 05 апреля 2020

Простое и очень быстрое решение - преобразовать оба значения 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...