Ошибка точности преобразования при преобразовании чисел с плавающей запятой половинной точности IEE в десятичные числа

У меня есть некоторая ошибка точности во время преобразования из 16-битного формата с плавающей запятой половинной точности в десятичный. Он может точно преобразовывать одни числа и в то же время неточно для других.

Первоначально код был разработан для 32-битного преобразования одинарной точности с плавающей запятой в десятичное число. Таким образом, я попытался отредактировать его, чтобы он соответствовал 16-битному формату с плавающей запятой половинной точности. В результате полученное окончательное значение оказалось вдвое меньше ожидаемого.

Например. Ожидаемое значение будет 1800, результатом будет 900.

Таким образом, я решил добавить * 2 к финальной операции. Я не уверен, как исправить текущую ошибку точности, которая у меня есть, и почему результат также вдвое меньше ожидаемого значения.

Ниже приведен код, который я отредактировал с соответствующими результатами.

#include <stdio.h> 
//#include <bits/stdc++.h> 
#include <string>
#include <iostream>
#include <sstream>
#include <math.h>
#include <limits.h>
#include <bitset>
using namespace std;

// Convert the 16-bit binary encoding into hexadecimal
int Binary2Hex( std::string Binary )
{
    std::bitset<16> set(Binary);      
    int hex = set.to_ulong();

    return hex;
}

// Convert the 16-bit binary into the decimal
float GetFloat16( std::string Binary )
{
    int HexNumber = Binary2Hex( Binary );
    printf("Test: %d\n", HexNumber);

    bool negative  = !!(HexNumber & 0x8000);
    int  exponent  =   (HexNumber & 0xf800) >> 10;    
    int sign = negative ? -1 : 1;

    // Subtract 15 from the exponent
    exponent -= 15;

    // Convert the mantissa into decimal using the
    // last 10 bits
    int power = -1;
    float total = 0.0;
    for ( int i = 0; i < 10; i++ )
    {
        int c = Binary[ i + 6 ] - '0';
        total += (float) c * (float) pow( 2.0, power );
        power--;
    }
    total += 1.0;

    float value = sign * (float) pow( 2.0, exponent ) * total * 2;

 }


16-битное значение с плавающей запятой, которое я использую, будет: 0101010100010011

Ожидаемый результат: 81.2 Фактический результат: 81.1875


person Kai    schedule 05.08.2019    source источник
comment
Может ли 18.2 быть точно представлено в 16-битном формате с плавающей запятой? Нет ошибок округления из-за всей математики, которую вы делаете (и помните, что ошибки округления складываются)?   -  person Some programmer dude    schedule 05.08.2019
comment
@Someprogrammerdude Я проверил следующую ссылку ссылка, используя вышеупомянутое значение с плавающей запятой 0101010100010011, и оно преобразуется в 81,2   -  person Kai    schedule 05.08.2019
comment
Затем я предлагаю вам сначала разбить все сложные выражения на более простые выражения, предпочтительно выполняя только одну операцию в каждом, и сохраняя это во временных переменных, которые затем объединяются для более сложных выражений. Когда вы это сделаете, используйте отладчик для пошагового выполнения вашего кода, оператор за оператором, проверяя результаты каждого маленького подвыражения, чтобы убедиться, что они соответствуют тому, что у вас уже есть на бумаге для вычислений.   -  person Some programmer dude    schedule 05.08.2019
comment
Ваша функция GetFloat16 возвращает значение, потому что она не содержит оператора return.   -  person Eric Postpischil    schedule 05.08.2019
comment
Маска для поля экспоненты в формате IEEE-754 binary16 должна быть 0x7c00, а не 0xf800.   -  person Eric Postpischil    schedule 05.08.2019
comment
Не рекомендуется использовать pow для точного возведения в степень, потому что некоторые реализации заведомо плохи тем, что не могут вернуть правильные результаты, когда математический результат точно представим. Он также может плохо работать. Обычно необходимые арифметические действия для такого рода работы можно выполнить с помощью простого умножения и деления. Если это невозможно, ldexp является лучшей альтернативой pow. На самом деле цикл и использование pow совершенно не нужны. Цикл можно заменить на total = (HexNumber & 0x3ff) / 1024.;.   -  person Eric Postpischil    schedule 05.08.2019
comment
Удалите * из float value = … * total * 2;. Я предполагаю, что вы добавили это, чтобы сделать результат приблизительно правильным, но это было необходимо только из-за правильной маски экспоненты, упомянутой выше.   -  person Eric Postpischil    schedule 05.08.2019
comment
Почему вы ожидаете 81.2 от 0101010100010011? Он имеет знак 0, показатель степени 10101, который равен 21 смещенному, 6 натуральному, и мантиссу 1,0100010011, что равно 1,2685546875, поэтому представленное число равно +1 • 2^6 • 1,2685546875 = 81,1875.   -  person Eric Postpischil    schedule 05.08.2019
comment
@ user207421: Пожалуйста, не закрывайте беспорядочно вопросы как дубликаты Разрушена ли математика с плавающей запятой? . Этот вопрос касается ошибок в коде, а не поведения арифметики с плавающей запятой, неожиданного для некоторых людей.   -  person Eric Postpischil    schedule 05.08.2019
comment
@EricPostpischil Спасибо за советы! Буду исправлять эти ошибки, а там посмотрим. Чтобы ответить на некоторые из ваших вопросов, да, я добавил * 2, чтобы получить лучший результат, так как я не был уверен, какие еще поля мне нужно отредактировать. Я ожидал 81.2 от 0101010100010011, так как онлайн-калькулятор предоставил этот ответ.   -  person Kai    schedule 06.08.2019