Как преобразовать double в C# decimal в C++?

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

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;
};

мой десятичный тип имеет следующие поля:

uint64 lo_;
uint32 hi_;
int32 signScale_;

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

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

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 ответов


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

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


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

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

Intel DFP


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

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

пример из 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 );
}

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

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

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

вот мой код, который преобразует строку в десятичное число. Обратите внимание, что он использует арифметическую библиотеку 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 != ''; ++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 != '')
        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;
}

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

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

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