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

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

void schedule(double _val);

void caller() {
   uint64_t value = 0xFFFFFFFFFFFFFFF;
   schedule(value);
}

поскольку расписание функции принимает тип аргумента double, в случаях, когда значение аргумента превышает 52 бита (учитывая, что double хранит мантиссу как 52-битное значение), я теряю точность в таких случаях.

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

    void caller() {
       uint64_t value = 0xFFFFFFFFFFFFFFF;
       for(count = 0; count < X ; count++) {
           schedule(Y);
       }
    }

мне нужно извлечь X и Y из переменной «значение». Как этого можно достичь? Моя цель - не потерять точность из-за приведения типов.


person Dexter    schedule 01.04.2013    source источник
comment
Есть ли у расписания какое-то волшебное свойство, например, schedule(x+y) совпадает с schedule(x);schedule(y);?   -  person Marc Glisse    schedule 01.04.2013
comment
@MarcGlisse хороший момент. Все, кажется, предполагают это, хотя это никогда не утверждалось.   -  person oefe    schedule 01.04.2013
comment
@MarcGlisse - не понял?   -  person Dexter    schedule 01.04.2013
comment
@Dexter: Ваш вопрос подразумевает, что один вызов schedule можно заменить несколькими последовательными вызовами schedule. Однако вы не объяснили, как производится такая замена. Каково правило замены в этом случае? Какой инвариант необходимо поддерживать? Сумма аргументов должна остаться прежней? Или что-то другое? Не зная этого, невозможно понять, откуда берутся ваши X и Y и что они должны означать.   -  person AnT    schedule 01.04.2013
comment
@AndreyT - проблем с количеством звонков по расписанию нет. Единственное, что нужно поддерживать, это то, что X * Y = значение. На самом деле проблема здесь связана со сценарием моделирования, основанным на событиях. Метод «расписание» вызывает уведомление по истечении периода времени, переданного в качестве аргумента. Это имеет ограничение типа двойного значения. Мне просто нужно обрабатывать случаи, когда значение больше, чем максимальное значение double.   -  person Dexter    schedule 01.04.2013
comment
@Dexter: Ваш последний комментарий говорит нам, что X вызовы schedule(Y); эквивалентны schedule(X*Y);. Мы хотели бы узнать кое-что более общее: эквивалентно ли schedule(A); schedule(B); schedule(A+B);, если предположить, что A и B неотрицательны, а A+B можно вычислить и передать точно, без ошибок округления или других арифметических проблем? Даже если A или B очень большие (например, 1u<<63), до тех пор, пока они передаются без ошибок?   -  person Eric Postpischil    schedule 01.04.2013
comment
@EricPostpischil - да   -  person Dexter    schedule 01.04.2013
comment
@Dexter: Хорошо, тогда мой ответ остается в силе.   -  person Eric Postpischil    schedule 01.04.2013
comment
@EricPostpischil - первый вызов расписания снова приведет к ошибке.   -  person Dexter    schedule 02.04.2013
comment
@Dexter: Если schedule передается большое значение, например 2 ** 63, и оно передается точно, без ошибок, работает ли schedule или нет?   -  person Eric Postpischil    schedule 02.04.2013


Ответы (2)


Если ваша проблема заключается только в потере точности в caller, а не в schedule, то цикл не нужен:

void caller() {
    uint64_t value = 0xFFFFFFFFFFFFFFF;
    uint64_t modulus = (uint64_t) 1 << 53;
    schedule(value - value % modulus);
    schedule(value % modulus)
}

В value - value % modulus имеют значение только старшие 11 бит, потому что младшие 53 бита были очищены. Итак, при преобразовании в double ошибки нет, и точное значение передается в schedule. Точно так же value % modulus имеет только 53 бита и точно преобразуется в double.

(Кодирование мантиссы 64-битного двоичного объекта с плавающей запятой IEEE-754 имеет 52 бита, но фактическая мантисса имеет 53 бита из-за неявного начального бита.)

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

person Eric Postpischil    schedule 01.04.2013
comment
Альтернатива (не лучше, но так как мы уже конвертируем в double): double d1=value; uint64_t v1=d1; schedule(d1); schedule (value-v1);. Кстати, 32 подойдет так же, как и 53. - person Marc Glisse; 01.04.2013
comment
@MarcGlisse: преобразование в double может быть округлено, в результате чего d1 превысит value, а value-v1 станет математически отрицательным. Поскольку это uint64_t, он запакован, и передается неправильное значение. - person Eric Postpischil; 01.04.2013
comment
Хороший момент, который соответствует обычному не используйте двойное число, когда вы можете использовать целое число. - person Marc Glisse; 01.04.2013
comment
но опять же модуль значение-значение% не будет больше 53 бит здесь? - person Dexter; 02.04.2013
comment
@Dexter: величина значения может быть больше 2 ** 53, но ее можно представить точно как double, потому что для ее представления требуется не более 11 последовательных битов. Поскольку точка плавает с плавающей запятой, экспонента говорит вам, где находятся биты, а мантиссы говорят вам, что это за биты. Таким образом, 11 бит легко представить, даже если они имеют большую величину. - person Eric Postpischil; 02.04.2013

Если N является максимальным интегральным значением, которое ваш double может точно представлять, то, очевидно, вы можете использовать

Y = N

и

X = amount / Y

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

R = amount % Y

Только имейте в виду, что все интегральные вычисления должны выполняться в пределах домена типа uint64_t, т.е. вам нужно добавлять правильный суффикс к константам (UL или ULL), либо использовать приведения типов к uint64_t, либо использовать промежуточные переменные типа uint64_t.

Конечно, если вашей программе на самом деле все равно, сколько раз будет вызываться schedule, главное, чтобы сумма была правильной, то вы можете использовать практически любое значение для N, если оно может быть точно представлено. Например, вы можете просто установить N = 10000.

С другой стороны, если вы хотите свести к минимуму количество вызовов schedule, то стоит отметить, что из-за правила «неявной 1» максимальное целое число, которое может быть представлено точно в 52-битной мантиссе, равно (1 << 53) - 1.

person AnT    schedule 01.04.2013