Что именно происходит при умножении двойного значения на 10

Недавно я задумался об умножении чисел с плавающей запятой.
Предположим, у меня есть число, например 3,1415, с гарантированной точностью до трех цифр.
Теперь я умножаю это значение на 10 и получаю 31,415X, где X — цифра, которую я не могу определить из-за ограниченной точности.

Могу ли я быть уверен, что пятерка переносится на точные цифры? Если доказано, что число является точным до 3 цифр, я бы не ожидал, что эта пятерка всегда будет там появляться, но после изучения многих случаев в С++ я заметил, что это всегда происходит.

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

Я задаю этот вопрос, потому что хотел создать функцию, вычисляющую точность типа. Я придумал что-то вроде этого:

template <typename T>
unsigned accuracy(){
        unsigned acc = 0;
        T num = (T)1/(T)3;
        while((unsigned)(num *= 10) == 3){
                acc++;
                num -= 3;
        }
        return acc;
}

Теперь это работает для любых типов, с которыми я его использовал, но я все еще не уверен, что первая неточная цифра всегда будет переноситься в неизменном виде.


person Karol Szustakowski    schedule 17.01.2020    source источник
comment
Это не совсем правильно ...числа с плавающей запятой хранятся по основанию два.... Они хранятся как 1 по основанию два и экспонента по основанию два. например, 0,5 может быть представлено точно, а 0,1 — нет.   -  person Richard Critten    schedule 17.01.2020
comment
связанный/может быть обман: stackoverflow.com/questions/588004/   -  person NathanOliver    schedule 17.01.2020


Ответы (3)


Я буду говорить конкретно о двойниках IEEE754, так как это то, о чем, я думаю, вы просите.

Двойники определяются как бит знака, 11-битная экспонента и 52-битная мантисса, которые объединяются для формирования 64-битного значения:

sign|exponent|mantissa

Биты экспоненты хранятся в смещенном формате, что означает, что мы храним фактическую экспоненту +1023 (для двойного числа). Экспонента со всеми нулями и экспонента со всеми единицами являются особыми, поэтому мы в конечном итоге можем представить экспоненту от 2 ^ -1022 до 2 ^ + 1023.

Это распространенное заблуждение, что целочисленные значения не могут быть точно представлены двойными числами, но на самом деле мы можем хранить любое целое число в [0,2^53) точно, установив правильно мантисса и экспоненту, на самом деле диапазон [2^52,2^53) может только хранить целочисленные значения в этом диапазоне. Таким образом, 10 легко точно хранить в двойном значении.

Когда дело доходит до умножения двойных чисел, у нас фактически есть два числа этой формы:

A = (-1)^sA*mA*2^(eA-1023)
B = (-1)^sB*mB*2^(eB-1023)

Где sA,mA,eA — знак, мантисса и показатель степени для A (и аналогично для B).

Если мы умножим их:

A*B = (-1)^(sA+sB)*(mA*mB)*2^((eA-1023)+(eB-1023))

Мы видим, что мы просто суммируем показатели степени, а затем умножаем мантиссы. Это на самом деле неплохо для точности! Мы можем переполнить биты экспоненты (и, таким образом, получить бесконечность), но кроме этого нам просто нужно округлить промежуточный результат мантиссы обратно до 52 бит, но в худшем случае это изменит только младший значащий бит в новой мантиссе.

В конечном итоге ошибка, которую вы увидите, будет пропорциональна величине результата. Но у двойников есть ошибка, пропорциональная их величине в любом случае, так что это действительно настолько безопасно, насколько это возможно. Чтобы приблизить ошибку в вашем числе, используйте |величина|*2^-53. В вашем случае, поскольку 10 точно, единственная ошибка будет в представлении числа пи. У него будет ошибка ~ 2 ^ -51, и, следовательно, результат тоже будет.

Как правило, я считаю, что числа типа double имеют ~15 цифр десятичной точности, когда речь идет о точности.

person gct    schedule 17.01.2020
comment
Предпочтительным термином для дробной части (по сравнению с показателем степени) числа с плавающей запятой является «значащая». «Мантисса» — это старое слово, обозначающее дробную часть логарифма. Значимые являются линейными (добавление 10% к 1 увеличивает значение, представленное на 10%). Мантиссы являются логарифмическими (добавление 10% к 1 умножает значение, представленное 10^.1). - person Eric Postpischil; 18.01.2020
comment
Значение двоичного значения IEEE64 составляет 53 бита, а не 52. 52 хранятся в поле мантиссы, а 1 кодируется через поле экспоненты. - person Eric Postpischil; 18.01.2020
comment
Ошибки в операциях binary64 составляют до числа, пропорционального величине результата (½ ULP результата), а не просто пропорционально величине результата. Другими словами, существует известная граница ошибки, пропорциональная величине результата, но это не означает, что ошибка пропорциональна ей. Ошибка может быть равна нулю или любому значению от нуля до границы. - person Eric Postpischil; 18.01.2020
comment
«В вашем случае, поскольку 10 точно, единственная ошибка будет в представлении числа пи. У него будет ошибка ~ 2 ^ -51, и, следовательно, результат тоже будет». не кажется правильным. Автор вопроса использовал 3,1415 в качестве примера, что не является π, но давайте предположим, что они имели в виду π. Затем возникает одна ошибка при преобразовании π в формат с плавающей запятой и вторая ошибка при умножении числа с плавающей запятой на 10. Эти ошибки могут суммироваться или отменяться. Начальная ошибка имеет границу 2^-52 (ULP около 3 составляет 2^-51, граница составляет ½ ULP). Умножение на 10 умножает ошибку и добавляет еще одну. - person Eric Postpischil; 18.01.2020

Предположим, что для одинарной точности 3,1415 равно

0x40490E56

в формате IEEE 754, который является очень популярным, но не единственным используемым форматом.

01000000010010010000111001010110 0 10000000 10010010000111001010110

поэтому двоичная часть равна 1.10010010000111001010110

110010010000111001010110 1100 1001 0000 1110 0101 0110 0xC90E56 * 10 = 0x7DA8F5C

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

01111.10110101000111101011100

чтобы попасть в формат IEEE 754, его нужно сдвинуть в формат 1.mantissa, так что это сдвиг на 3

1.11110110101000111101011

но посмотрите на три бита, отрезанные от 100, особенно 1, так что это означает, что в зависимости от режима округления вы округляете, в этом случае давайте округлим

1.11110110101000111101100

0111 1011 0101 0001 1110 1100

0x7BA1EC

теперь, если я уже вычислил ответ:

0x41FB51EC

0 10000011 11110110101000111101100

мы переместили точку 3, и экспонента отражает это, мантисса соответствует тому, что мы вычислили. мы потеряли один из исходных ненулевых битов справа, но не слишком ли это большая потеря?

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

person old_timer    schedule 17.01.2020

Могу ли я быть уверен, что пятерка переносится на точные цифры?

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

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

Кажется, ваша функция пытается вычислить, насколько точно тип с плавающей запятой может представлять 1/3. Эта точность бесполезна для оценки точности представления других чисел.

потому что числа с плавающей запятой хранятся по основанию два

Хотя это очень распространено, это не всегда верно. Например, некоторые системы используют базу 10.

person eerorika    schedule 17.01.2020