2010-06-03 3 views
4

Что такое самый портативный и «правильный» способ преобразования из расширенного прецизионного поплавка (80-битное значение, также известное как «long double "в некоторых компиляторах), чтобы удвоить (64-бит) в MSVC win32/win64?MSVC win32: конвертировать расширенный прецизионный поплавок (80 бит) в двойной (64-разрядный)

MSVC в настоящее время (с 2010 года) предполагает, что «длинный двойной» является «двойным» синонимом.

Возможно, я мог бы написать пару ассемблеров fld/fstp inline asm, но встроенный asm не доступен для кода win64 в MSVC. Нужно ли мне переместить этот код ассемблера для разделения файла .asm? Разве это действительно так, что нет хорошего решения?

ответ

4

Просто сделал это в x86 коде ...

.686P 
    .XMM 

_TEXT SEGMENT 

EXTRN __fltused:DWORD 

PUBLIC _cvt80to64 
PUBLIC _cvt64to80 

_cvt80to64 PROC 

    mov eax, dword ptr [esp+4] 
    fld TBYTE PTR [eax] 

    ret 0 
_cvt80to64 ENDP 


_cvt64to80 PROC 
    mov eax, DWORD PTR [esp+12] 
    fld QWORD PTR [esp+4] 
    fstp TBYTE PTR [eax] 
    ret 0 
_cvt64to80 ENDP 

ENDIF 

_TEXT ENDS 
    END 
4

Если ваш компилятор/платформа не имеет встроенной поддержки для значений с плавающей запятой в 80 бит, вам необходимо декодировать значение самостоятельно.

Если предположить, что 80 бит с плавающей точкой сохраняется в буфере байт, расположенный в определенном смещении, вы можете сделать это следующим образом:

float64 C_IOHandler::readFloat80(IColl<uint8> buffer, uint32 *ref_offset) 
{ 
    uint32 &offset = *ref_offset; 

    //80 bit floating point value according to the IEEE-754 specification and the Standard Apple Numeric Environment specification: 
    //1 bit sign, 15 bit exponent, 1 bit normalization indication, 63 bit mantissa 

    float64 sign; 
    if ((buffer[offset] & 0x80) == 0x00) 
     sign = 1; 
    else 
     sign = -1; 
    uint32 exponent = (((uint32)buffer[offset] & 0x7F) << 8) | (uint32)buffer[offset + 1]; 
    uint64 mantissa = readUInt64BE(buffer, offset + 2); 

    //If the highest bit of the mantissa is set, then this is a normalized number. 
    float64 normalizeCorrection; 
    if ((mantissa & 0x8000000000000000) != 0x00) 
     normalizeCorrection = 1; 
    else 
     normalizeCorrection = 0; 
    mantissa &= 0x7FFFFFFFFFFFFFFF; 

    offset += 10; 

    //value = (-1)^s * (normalizeCorrection + m/2^63) * 2^(e - 16383) 
    return (sign * (normalizeCorrection + (float64)mantissa/((uint64)1 << 63)) * g_Math->toPower(2, (int32)exponent - 16383)); 
} 

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

+0

Этот код предполагает, что данные в формате большого эндиана. – Matt

0

игры с заданными ответами и в конечном итоге с этим.

#include <cmath> 
#include <limits> 
#include <cassert> 

#ifndef _M_X64 

__inline __declspec(naked) double _cvt80to64(void*) { 
    __asm { 
    // PUBLIC _cvt80to64 PROC 

    mov eax, dword ptr [esp+4] 
    fld TBYTE PTR [eax] 

    ret 0 
    // _cvt80to64 ENDP 
    } 
} 

#endif 

#pragma pack(push) 
#pragma pack(2) 
typedef unsigned char tDouble80[10]; 
#pragma pack(pop) 


typedef struct { 
    unsigned __int64 mantissa:64; 
    unsigned int exponent:15; 
    unsigned int sign:1; 
} tDouble80Struct; 

inline double convertDouble80(const tDouble80& val) 
{ 
    assert(10 == sizeof(tDouble80)); 

    const tDouble80Struct* valStruct = reinterpret_cast<const tDouble80Struct*>(&val); 

    const unsigned int mask_exponent = (1 << 15) - 1; 
    const unsigned __int64 mantissa_high_highestbit = unsigned __int64(1) << 63; 
    const unsigned __int64 mask_mantissa = (unsigned __int64(1) << 63) - 1; 

    if (mask_exponent == valStruct->exponent) { 

    if(0 == valStruct->mantissa) { 
     return (0 != valStruct->sign) ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity(); 
    } 

    // highest mantissa bit set means quiet NaN 
    return (0 != (mantissa_high_highestbit & valStruct->mantissa)) ? std::numeric_limits<double>::quiet_NaN() : std::numeric_limits<double>::signaling_NaN(); 
    } 

    // 80 bit floating point value according to the IEEE-754 specification and 
    // the Standard Apple Numeric Environment specification: 
    // 1 bit sign, 15 bit exponent, 1 bit normalization indication, 63 bit mantissa 

    const double sign(valStruct->sign ? -1 : 1); 


    //If the highest bit of the mantissa is set, then this is a normalized number. 
    unsigned __int64 mantissa = valStruct->mantissa; 
    double normalizeCorrection = (mantissa & mantissa_high_highestbit) != 0 ? 1 : 0; 
    mantissa &= mask_mantissa; 

    //value = (-1)^s * (normalizeCorrection + m/2^63) * 2^(e - 16383) 
    return (sign * (normalizeCorrection + double(mantissa)/mantissa_high_highestbit) * pow(2.0, int(valStruct->exponent) - 16383)); 
} 
1

Я только что написал этот. Он строит двойной номер IEEE с номера расширенной точности IEEE с использованием битовых операций. Требуется 10-байтовый расширенный прецизионный номер в маломерном формате:

typedef unsigned long long uint64; 

double makeDoubleFromExtended(const unsigned char x[10]) 
{ 
    int exponent = (((x[9] << 8) | x[8]) & 0x7FFF); 
    uint64 mantissa = 
     ((uint64)x[7] << 56) | ((uint64)x[6] << 48) | ((uint64)x[5] << 40) | ((uint64)x[4] << 32) | 
     ((uint64)x[3] << 24) | ((uint64)x[2] << 16) | ((uint64)x[1] << 8) | (uint64)x[0]; 
    unsigned char d[8] = {0}; 
    double result; 

    d[7] = x[9] & 0x80; /* Set sign. */ 

    if ((exponent == 0x7FFF) || (exponent == 0)) 
    { 
     /* Infinite, NaN or denormal */ 
     if (exponent == 0x7FFF) 
     { 
      /* Infinite or NaN */ 
      d[7] |= 0x7F; 
      d[6] = 0xF0; 
     } 
     else 
     { 
      /* Otherwise it's denormal. It cannot be represented as double. Translate as singed zero. */ 
      memcpy(&result, d, 8); 
      return result; 
     } 
    } 
    else 
    { 
     /* Normal number. */ 
     exponent = exponent - 0x3FFF + 0x03FF; /*< exponent for double precision. */ 

     if (exponent <= -52) /*< Too small to represent. Translate as (signed) zero. */ 
     { 
      memcpy(&result, d, 8); 
      return result; 
     } 
     else if (exponent < 0) 
     { 
      /* Denormal, exponent bits are already zero here. */ 
     } 
     else if (exponent >= 0x7FF) /*< Too large to represent. Translate as infinite. */ 
     { 
      d[7] |= 0x7F; 
      d[6] = 0xF0; 
      memset(d, 0x00, 6); 
      memcpy(&result, d, 8); 
      return result; 
     } 
     else 
     { 
      /* Representable number */ 
      d[7] |= (exponent & 0x7F0) >> 4; 
      d[6] |= (exponent & 0xF) << 4; 
     } 
    } 
    /* Translate mantissa. */ 

    mantissa >>= 11; 

    if (exponent < 0) 
    { 
     /* Denormal, further shifting is required here. */ 
     mantissa >>= (-exponent + 1); 
    } 

    d[0] = mantissa & 0xFF; 
    d[1] = (mantissa >> 8) & 0xFF; 
    d[2] = (mantissa >> 16) & 0xFF; 
    d[3] = (mantissa >> 24) & 0xFF; 
    d[4] = (mantissa >> 32) & 0xFF; 
    d[5] = (mantissa >> 40) & 0xFF; 
    d[6] |= (mantissa >> 48) & 0x0F; 

    memcpy(&result, d, 8); 

    printf("Result: 0x%016llx", *(uint64*)(&result)); 

    return result; 
} 
+0

Я считаю, что обращение к случаю 'if (exponent <= 0)' означает, что числа, которые могут быть представлены как двоичные подмножества binary64, представлены как '0.0'. –

+0

Исправлено. Точный случай 'экспонента == 0' действительно может дать денормальный двойник. – Calmarius

+0

Это все еще не совсем верно: случаи, когда есть что-то делать, - это случаи, когда 'exponent' находится между -52 и 0 (в этой точке кода), и что должно быть сделано ** ** ** сделайте неявный бит явным, сдвиньте значение, которое будет использоваться справа от '-exponent', и установите' exponent' в ноль. OP закончил использование 'FSTP', поэтому это не очень важно, но вам нужно будет сделать это для' FSTP' в вашем эмуляторе :) –