Конвертировать float в bigint (он же переносной способ получить двоичную экспоненту и мантиссу) - PullRequest
4 голосов
/ 25 января 2010

В C ++ у меня есть класс bigint, который может содержать целое число произвольного размера.

Я хотел бы преобразовать большие числа с плавающей точкой или двойные числа в bigint. У меня есть метод работы, но это немного взломать. Я использовал спецификацию числа IEEE 754, чтобы получить двоичный знак, мантиссу и экспоненту входного числа.

Вот код (здесь знак игнорируется, это не важно):

 float input = 77e12;
 bigint result;

 // extract sign, exponent and mantissa, 
 // according to IEEE 754 single precision number format
 unsigned int *raw = reinterpret_cast<unsigned int *>(&input); 
 unsigned int sign = *raw >> 31;
 unsigned int exponent = (*raw >> 23) & 0xFF;
 unsigned int mantissa = *raw & 0x7FFFFF;

 // the 24th bit is always 1.
 result = mantissa + 0x800000;

 // use the binary exponent to shift the result left or right
 int shift = (23 - exponent + 127);
 if (shift > 0) result >>= shift; else result <<= -shift;

 cout << input << " " << result << endl;

Это работает, но довольно уродливо, и я не знаю, насколько это портативно. Есть лучший способ сделать это? Есть ли менее уродливый, портативный способ извлечь двоичную мантиссу и экспоненту из числа с плавающей или двойной?


Спасибо за ответы. Для потомков, вот решение с использованием frexp. Он менее эффективен из-за цикла, но работает как для чисел с плавающей точкой, так и для двойных, не использует reinterpret_cast или зависит от каких-либо знаний о представлении чисел с плавающей запятой.

float input = 77e12;
bigint result;

int exponent;
double fraction = frexp (input, &exponent);
result = 0;
exponent--;
for (; exponent > 0; --exponent)
{
    fraction *= 2;
    if (fraction >= 1)
    {
        result += 1;
        fraction -= 1;
    }
    result <<= 1;
}   

Ответы [ 3 ]

8 голосов
/ 25 января 2010

Вы не можете обычно извлечь значения, используя frexp (), frexpf (), frexpl () ?

1 голос
/ 21 марта 2011

Мне нравится ваше решение! Это привело меня на правильный путь.

Я бы порекомендовал одну вещь - почему бы не получить кучу битов сразу и почти всегда исключить циклы? Я реализовал функцию float-to-bigint следующим образом:

template<typename F>
explicit inline bigint(F f, typename std::enable_if<(std::is_floating_point<F>::value)>::type* enable = nullptr) {
    int exp;
    F fraction = frexp(fabs(f),&exp);
    F chunk = floor(fraction *= float_pow_2<F,ulong_bit_count>::value);
    *this = ulong(chunk); // will never overflow; frexp() is guaranteed < 1
    exp -= ulong_bit_count;
    while (sizeof(F) > sizeof(ulong) && (fraction -= chunk)) // this is very unlikely
    {
        chunk = floor(fraction *= float_pow_2<F,ulong_bit_count>::value);
        *this <<= ulong_bit_count;
        (*this).data[0] = ulong(chunk);
        exp -= ulong_bit_count;
    }
    *this <<= exp;
    sign = f < 0;
}

(Между прочим, я не знаю простого способа вставить константы степени с плавающей точкой, поэтому я определил float_pow_2 следующим образом):

template<typename F, unsigned Exp, bool Overflow = (Exp >= sizeof(unsigned))>
struct float_pow_2 {
    static constexpr F value = 1u << Exp;
};
template<typename F, unsigned Exp>
struct float_pow_2<F,Exp,true> {
    static constexpr F half = float_pow_2<F,Exp/2>::value;
    static constexpr F value = half * half * (Exp & 1 ? 2 : 1);
};
0 голосов
/ 25 января 2010

Если число с плавающей точкой всегда содержит целочисленное значение, просто приведите его к типу int: float_to_int = (unsigned long) input.

Кстати, 77e12 переполняет поплавок. Дабл будет держать его, но тогда вам понадобится этот приём: (unsigned long long) input.

...