Как сделать двойной кусок добавить без неопределенного поведения?
-
01-10-2019 - |
Вопрос
РЕДАКТИРОВАТЬ Предупреждение о здравоохранении общественного здравоохранения - этот вопрос включает в себя ложное предположение о неопределенном поведении. См. Принятый ответ.
После чтения Последние блог Post., Я много думал о практичности избежать всех стандартов-неопределенных предположений в C и C ++ код. Вот фрагмент вырезан из C ++, чтобы сделать без знаки 128 бит ...
void c_UInt64_Pair::operator+= (const c_UInt64_Pair &p)
{
m_Low += p.m_Low;
m_High += p.m_High;
if (m_Low < p.m_Low) m_High++;
}
Это четко зависит от предположений о переполнении поведения. Очевидно, что большинство машин могут поддерживать двоичное целое число правильного типа (хотя, возможно, построение из 32-битных кусков или что-то еще), но, по-видимому, растущему вероятному, что оптимизатор может использовать стандарты-неопределенное поведение здесь. То есть единственный способ, которым m_Low < p.m_Low
Состояние может пройти, если m_Low += p.m_Low
переполняет, что неопределенное поведение, поэтому оптимизатор может законно решать, что условие всегда не удается. В этом случае этот код просто сломан.
Вопрос в том, поэтому ...
Как вы можете написать достаточно эффективную версию вышеперечисленного, не полагаясь на неопределенное поведение?
Предположим, что у вас есть соответствующая 64-битная бинарная машина Integer, но у вас есть вредоносный компилятор, который всегда будет интерпретировать ваше неопределенное поведение в худшем случае возможным (или невозможным). Также предположим, что вы нет есть специальный встроенный, внутренний, библиотечный или что-то, что нужно сделать для вас.
РЕДАКТИРОВАТЬ Незначительное разъяснение - это не только об обнаружении переполнения, но и обеспечения того, чтобы оба M_LOW и M_HIGH заканчиваются правильными результатами Modulo 2 ^ 64, которые также являются стандартами - не определены.
Решение
От стандарта C ++ 1998 года, 3.9.1 (4): «целые числа unsigned, объявленные без знаки, должны соблюдать законы арифметического модуля 2 ^ n, где n представляет собой количество битов в представлении значения этого конкретного размера целогомента». Обратите внимание, что «целое число», здесь, ссылается на любой целочисленный тип, а не просто int
.
Поэтому, предполагая, что это unsigned целые числа, такие как «uint64» в типе, предполагает, что это определенное поведение в C ++ и должно работать, как ожидалось.
Другие советы
Если вы хотите на самом деле эффективный метод, вам придется код в чем-то, кроме C или C ++. Для разумно Эффективно, вы должны убедиться, что переполнение никогда не бывает, а обнаружить и компенсировать, когда оно будет.
По сути, для каждого 64-битного компонента необходимо отдельно рассчитать дополнения, используя низкие 63 бита и самые высокие биты. Из этих отдельных расчетов вы можете отработать, что было 64-битным общем, и если произошло перенос.
Затем, когда вы делаете верхнюю 64-битное добавление, вы добавляете в несущуюся, если есть один. Если отслеживать результаты от этого, вы переполнены вашу 128-битную переменную, и вам нужно будет вызвать исключение, или иным образом обрабатывать дело.