jabk
jabk

Reputation: 1458

QbyteArray to float

I have a function that converts QByteArray to float and it works perfectly fine except when I feed it zeros I get 5.87747e-39 instead of 0.0.

float QByteArrayToFloat(QByteArray f){
    bool ok;
    int sign = 1;

    f = f.toHex(); // Convert to Hex

    qDebug() << "QByteArrayToFloat: QByteArray hex = " << f;

    f = QByteArray::number(f.toLongLong(&ok, 16), 2);    // Convert hex to binary

    if(f.length() == 32) {
        if(f.at(0) == '1') sign =-1;     // If bit 0 is 1 number is negative
        f.remove(0,1);                   // Remove sign bit
    }

    QByteArray fraction = f.right(23);  // Get the fractional part
    double mantissa = 0;
    for(int i = 0; i < fraction.length(); i++){  // Iterate through the array to claculate the fraction as a decimal.
        if(fraction.at(i) == '1')
            mantissa += 1.0 / (pow(2, i+1));
    }

    int exponent = f.left(f.length() - 23).toLongLong(&ok, 2) - 127;     // Calculate the exponent

    qDebug() << "QByteArrayToFloat: float number = "<< QString::number(sign * pow(2, exponent) * (mantissa + 1.0),'f', 5);

    return (sign * pow(2, exponent) * (mantissa + 1.0));
}

There is no useful function in QByteArray (isEmpty() doesn't work) that checks for zeros. I could do (after toHex()) if(f.indexOf("00000000") == -1) return 0.0; or if(exponent = -127 && mantissa == 0) return 0.0;, but is there a more elegant solution?

Also, what's interesting is that QString::number(sign * pow(2, exponent) * (mantissa + 1.0),'f', 5); works just fine and prints "0.00000". However, as soon as I transform it back to float with toFloat(&ok); same thing happens.

Upvotes: 1

Views: 5528

Answers (2)

jabk
jabk

Reputation: 1458

I've found out there's a better solution for QByteArray to float conversion using bit shifts and reinterpret_cast. It also handles the zero values.

float QByteArrayToFloat(QByteArray arr)
{
    static_assert(std::numeric_limits<float>::is_iec559, "Only supports IEC 559 (IEEE 754) float");

    quint32 temp = ((char)arr[0] << 24)|((char)arr[1] << 16)|((char)arr[2] << 8)|(char)arr[3]; // Big endian
    float* out = reinterpret_cast<float*>(&temp);

    return *out;
}

Upvotes: 1

paceholder
paceholder

Reputation: 1104

I cite from this document:

Denormalized Numbers

If you have an exponent field that's all zero bits, this is what's called a denormalized number. With the exponent field equal to zero, you would think that the real exponent would be -127, so this number would take the form of 1.MANTISSA * 2-127 as described above, but it does not. Instead, it is 0.MANTISSA * 2-126. Notice that the exponent is no longer the value of the exponent field minus 127. It is simply -126. Also notice that we no longer include an implied one bit for the mantissa.

Zero

You can think of zero as simply another denormalized number. Zero is represented by an exponent of zero and a mantissa of zero. From our understanding of denormalized numbers, this translates into 0*2-126 = 0. This sign bit can be either positive (0) or negative (1), leading to either a positive or negative zero. This doesn't make very much sense mathematically, but it is allowed.

After correcting your code just for zero case to:

int exponent = - 126;
// and
return (sign * pow(2, exponent) * (mantissa + 0.0));

I get the answer 0.0.

Upvotes: 2

Related Questions