Как преобразовать двойной десятичный в C # в C ++? - PullRequest
14 голосов
/ 19 мая 2011

Учитывая, что у меня есть десятичное представление - вы можете найти его здесь, например, -, я попытался преобразовать double таким образом:

explicit Decimal(double n)
{
    DoubleAsQWord doubleAsQWord;
    doubleAsQWord.doubleValue = n;
    uint64 val = doubleAsQWord.qWord;

    const uint64 topBitMask = (int64)(0x1 << 31) << 32;

    //grab the 63th bit
    bool isNegative = (val & topBitMask) != 0;

    //bias is 1023=2^(k-1)-1, where k is 11 for double
    uint32 exponent = (((uint64)(val >> 31) >> 21) & 0x7FF) - 1023;

    //exclude both sign and exponent (<<12, >>12) and normalize mantissa
    uint64 mantissa = ((uint64)(0x1 << 31) << 21) | (val << 12) >> 12;

    // normalized mantissa is 53 bits long,
    // the exponent does not care about normalizing bit
    uint8 scale = exponent + 11; 
    if (scale > 11)
        scale = 11;
    else if (scale < 0)
        scale = 0;
    lo_ = ((isNegative ? -1 : 1) * n) * std::pow(10., scale);
    signScale_ = (isNegative ? 0x1 : 0x0) | (scale << 1);

    // will always be 0 since we cannot reach
    // a 128 bits precision with a 64 bits double
    hi_ = 0;
}

Тип DoubleAsQWord:используется для «преобразования» из double в его представление uint64:

union DoubleAsQWord
{
    double doubleValue;
    uint64 qWord;
};

В типе My Decimal есть следующие поля:

uint64 lo_;
uint32 hi_;
int32 signScale_;

Все эти вещи инкапсулированы в моем классе Decimal.Вы можете заметить, что я извлекаю мантиссу, даже если я ее не использую.Я все еще думаю о том, как точно угадать масштаб.

Это чисто практично и, похоже, работает в случае стресс-теста:

BOOST_AUTO_TEST_CASE( convertion_random_stress )
{
    const double EPSILON = 0.000001f;

    srand(time(0));
    for (int i = 0; i < 10000; ++i)
    {
        double d1 = ((rand() % 10) % 2 == 0 ? -1 : 1)
            * (double)(rand() % 1000 + 1000.) / (double)(rand() % 42 + 2.);
        Decimal d(d1);

        double d2 = d.toDouble();

        double absError = fabs(d1 - d2);
        BOOST_CHECK_MESSAGE(
            absError <= EPSILON,
            "absError=" << absError << " with " << d1 << " - " << d2
        );
    }
}

В любом случае, как бывы конвертируете из double в это decimal представление?

Ответы [ 5 ]

3 голосов
/ 28 июня 2011

Я думаю, что вы, ребята, будете заинтересованы в реализации оболочки C ++ для библиотеки математических вычислений с плавающей запятой Intel:

C ++ Десятичный класс обертки

Intel DFP

2 голосов
/ 27 мая 2011

Как насчет использования функции VarR8FromDec ?

EDIT: эта функция объявлена ​​только в системе Windows. Однако эквивалентная реализация C доступна с WINE , здесь: http://source.winehq.org/source/dlls/oleaut32/vartype.c

0 голосов
/ 25 мая 2011

Возможно, вы ищете System::Convert::ToDecimal()
http://msdn.microsoft.com/en-us/library/a69w9ca0%28v=vs.80%29.aspx

В качестве альтернативы вы можете попробовать преобразовать Double в десятичное число.

Пример из MSDN.
http://msdn.microsoft.com/en-us/library/aa326763%28v=vs.71%29.aspx

// Convert the double argument; catch exceptions that are thrown.
void DecimalFromDouble( double argument )
{
    Object* decValue;

    // Convert the double argument to a Decimal value.
    try
    {
        decValue = __box( (Decimal)argument );
    }
    catch( Exception* ex )
    {
        decValue = GetExceptionType( ex );
    }

    Console::WriteLine( formatter, __box( argument ), decValue );
}
0 голосов
/ 25 мая 2011

Если у вас нет доступа к подпрограммам .Net, то это сложно. Я сделал это сам для своего шестнадцатеричного редактора (чтобы пользователи могли отображать и редактировать десятичные значения C # с помощью диалога свойств) - см. http://www.hexedit.com для получения дополнительной информации. Также источник для HexEdit находится в свободном доступе - см. Мою статью на http://www.codeproject.com/KB/cpp/HexEdit.aspx.

На самом деле мои процедуры конвертируются между десятичным и строковым значениями, но вы, конечно, можете сначала использовать sprintf для преобразования двойного в строковое значение. (Также, когда вы говорите о double, я думаю, вы явно имеете в виду 64-битный формат IEEE с плавающей запятой, хотя это то, что в настоящее время используется большинством компиляторов / систем.)

Обратите внимание, что есть несколько ошибок, если вы хотите точно обработать все допустимые десятичные значения и вернуть ошибку для любого значения, которое не может быть преобразовано, так как формат плохо документирован. (Десятичный формат действительно ужасен, например, одно и то же число может иметь много представлений.)

Вот мой код, который преобразует строку в десятичную. Обратите внимание, что в нем используется арифметическая библиотека GNU Multiple Precision (функции, начинающиеся с mpz_). Функция String2Decimal, очевидно, возвращает false, если по какой-то причине происходит сбой, например, слишком большое значение. Параметр «presult» должен указывать на буфер размером не менее 16 байт, чтобы сохранить результат.

bool String2Decimal(const char *ss, void *presult)
{
    bool retval = false;

    // View the decimal (result) as four 32 bit integers
    unsigned __int32 *dd = (unsigned __int32 *)presult;

    mpz_t mant, max_mant;
    mpz_inits(mant, max_mant, NULL);
    int exp = 0;                // Exponent
    bool dpseen = false;        // decimal point seen yet?
    bool neg = false;           // minus sign seen?

    // Scan the characters of the value
    const char *pp;
    for (pp = ss; *pp != '\0'; ++pp)
    {
        if (*pp == '-')
        {
            if (pp != ss)
                goto exit_func;      // minus sign not at start
            neg = true;
        }
        else if (isdigit(*pp))
        {
            mpz_mul_si(mant, mant, 10);
            mpz_add_ui(mant, mant, unsigned(*pp - '0'));
            if (dpseen) ++exp;  // Keep track of digits after decimal pt
        }
        else if (*pp == '.')
        {
            if (dpseen)
                goto exit_func;    // more than one decimal point
            dpseen = true;
        }
        else if (*pp == 'e' || *pp == 'E')
        {
            char *end;
            exp -= strtol(pp+1, &end, 10);
            pp = end;
            break;
        }
        else
            goto exit_func;       // unexpected character
    }
    if (*pp != '\0')
        goto exit_func;           // extra characters after end

    if (exp < -28 || exp > 28)
        goto exit_func;          // exponent outside valid range

    // Adjust mantissa for -ve exponent
    if (exp < 0)
    {
        mpz_t tmp;
        mpz_init_set_si(tmp, 10);
        mpz_pow_ui(tmp, tmp, -exp);
        mpz_mul(mant, mant, tmp);
        mpz_clear(tmp);
        exp = 0;
    }

    // Get max_mant = size of largest mantissa (2^96 - 1)
    //mpz_set_str(max_mant, "79228162514264337593543950335", 10); // 2^96 - 1
    static unsigned __int32 ffs[3] = { 0xFFFFffffUL, 0xFFFFffffUL, 0xFFFFffffUL };
    mpz_import(max_mant, 3, -1, sizeof(ffs[0]), 0, 0, ffs);

    // Check for mantissa too big.
    if (mpz_cmp(mant, max_mant) > 0)
        goto exit_func;      // value too big
    else if (mpz_sgn(mant) == 0)
        exp = 0;  // if mantissa is zero make everything zero

    // Set integer part
    dd[2] = mpz_getlimbn(mant, 2);
    dd[1] = mpz_getlimbn(mant, 1);
    dd[0] = mpz_getlimbn(mant, 0);

    // Set exponent and sign
    dd[3] = exp << 16;
    if (neg && mpz_sgn(mant) > 0)
        dd[3] |= 0x80000000;

    retval = true;   // indicate success

exit_func:
    mpz_clears(mant, max_mant, NULL);
    return retval;
}
0 голосов
/ 19 мая 2011

Как насчет этого:

1) номер sprintf в s 2) найти десятичную точку (strchr), сохранить в idx 3) atoi = легко получить целую часть, использовать union для разделения high / lo 4) usestrlen - idx для получения количества цифр после точки

sprintf может быть медленным, но вы получите решение менее чем через 2 минуты после ввода ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...