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

Учитывая выдачу десятичного числа, у меня есть - вы можете найти его здесь, например, -, я попытался преобразовать 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 используется для "отбрасывания" из двойного представления в 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?

Ответ 1

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

С++ Decimal Wrapper Class

Intel DFP

Ответ 3

Возможно, вы ищете 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 );
}

Ответ 4

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

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

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

Вот мой код, который преобразует строку в десятичный. Обратите внимание, что в нем используется многоадресная библиотека арифметики GNU (функции, начинающиеся с 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;
}

Ответ 5

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

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

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