Двойная печать без потери точности - PullRequest
32 голосов
/ 19 января 2011

Как вы печатаете двойной поток в потоке, чтобы при его чтении вы не теряли точность?

Я пытался:

std::stringstream ss;

double v = 0.1 * 0.1;
ss << std::setprecision(std::numeric_limits<T>::digits10) << v << " ";

double u;
ss >> u;
std::cout << "precision " << ((u == v) ? "retained" : "lost") << std::endl;

Это не сработало, как яожидается.

Но я могу повысить точность (что удивило меня, когда я подумал, что цифры10 - это максимум требуемого).

ss << std::setprecision(std::numeric_limits<T>::digits10 + 2) << v << " ";
                                                 //    ^^^^^^ +2

Это связано с количеством значащих цифр и первымдва не учитываются в (0,01).

Так кто-нибудь смотрел на точное представление чисел с плавающей запятой?Какое точное магическое заклинание на потоке мне нужно сделать?

После некоторых экспериментов:

Проблема была в моей первоначальной версии.После десятичной запятой в строке были незначащие цифры, которые влияли на точность.

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

ss << std::scientific
   << std::setprecision(std::numeric_limits<double>::digits10 + 1)
   << v;

Это все еще неОбъясните необходимость в +1, хотя.

Также, если я распечатаю число с большей точностью, я получу более точную распечатку!

std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10) << v << "\n";
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1) << v << "\n";
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits) << v << "\n";

В результате:

1.000000000000000e-02
1.0000000000000002e-02
1.00000000000000019428902930940239457413554200000000000e-02

Основано на ответе @Stephen Canon, приведенном ниже:

Мы можем точно распечатать, используя форматтер printf (), "% a" или "% A".Для достижения этого в C ++ нам нужно использовать фиксированные и научные манипуляторы (см. N3225: 22.4.2.2.2p5 Таблица 88)

std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
std::cout << v;

На данный момент я определил:

template<typename T>
std::ostream& precise(std::ostream& stream)
{
    std::cout.flags(std::ios_base::fixed | std::ios_base::scientific);
    return stream;
}

std::ostream& preciselngd(std::ostream& stream){ return precise<long double>(stream);}
std::ostream& precisedbl(std::ostream& stream) { return precise<double>(stream);}
std::ostream& preciseflt(std::ostream& stream) { return precise<float>(stream);}

Далее: Как мы справляемся с NaN / Inf?

Ответы [ 7 ]

17 голосов
/ 19 января 2011

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

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

Арифметика с плавающей точкой технически идеально точна. Каждый расчет имеет один и только один возможный результат. - это проблема, и большинство десятичных дробей имеют повторения в формате base-2, которые повторяются. Фактически в последовательности 0.01, 0.02, ... 0.99 только 3 значения имеют точные двоичные представления. (0,25, 0,50 и 0,75.) Есть 96 значений, которые повторяются и, следовательно, явно не представлены точно.

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

  • Запишите их в двоичном виде. В наши дни каждый реализует формат IEEE-754, так что, пока вы выбираете порядок байтов и записываете или читаете только этот порядок байтов, числа будут переносимыми.
  • Запишите их как 64-битные целочисленные значения. Здесь вы можете использовать обычное основание 10. (Поскольку вы представляете 64-разрядное целое число с псевдонимом, а не 52-разрядную дробь.)

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

14 голосов
/ 19 января 2011

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

Использование шестнадцатеричной плавающей запятойвместо.В C:

printf("%a\n", yourNumber);

C ++ 0x предоставляет манипулятор hexfloat для iostreams, который делает то же самое (на некоторых платформах использование модификатора std::hex дает тот же результат, но это непереносимое предположение).

Использование шестнадцатеричной плавающей запятой предпочтительно по нескольким причинам.

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

9 голосов
/ 24 ноября 2012

Меня заинтересовал этот вопрос, потому что я пытаюсь (де) сериализовать свои данные в JSON и из него.

Я думаю, что у меня есть более четкое объяснение (с меньшим количеством ручных отказов), почему 17 десятичных цифр достаточно, чтобы восстановить исходное число без потерь:

enter image description here

Представьте себе 3 числовые линии:
1. для оригинальной базы 2 числа
2. для округленного основания 10 представлений
3. для восстановленного номера (так же, как # 1, потому что оба в базе 2)

Когда вы конвертируете в базу 10 графически, вы выбираете тик на 2-й числовой строке, ближайшей к тику на 1-й. Аналогично, когда вы воссоздаете оригинал из округленного базового значения 10.

Критическое наблюдение, которое я имел, состояло в том, что для точной реконструкции базовый размер шага 10 (квант) должен быть <квант основания 2. В противном случае вы неизбежно получите плохую реконструкцию, показанную красным. </p>

Возьмем конкретный случай, когда показатель степени равен 0 для представления base2. Тогда квант base2 будет 2 ^ -52 ~ = 2,22 * 10 ^ -16. Ближайший квант основания 10 меньше 10 ^ -16. Теперь, когда мы знаем требуемый квант основания 10, сколько цифр потребуется для кодирования всех возможных значений? Учитывая, что мы рассматриваем только случай экспоненты = 0, динамический диапазон значений, которые мы должны представить, равен [1.0, 2.0). Следовательно, потребуется 17 цифр (16 цифр для дробной части и 1 цифра для целой части).

Для показателей, отличных от 0, мы можем использовать ту же логику:

    exponent    base2 quant.   base10 quant.  dynamic range   digits needed
    ---------------------------------------------------------------------
    1              2^-51         10^-16         [2, 4)           17
    2              2^-50         10^-16         [4, 8)           17
    3              2^-49         10^-15         [8, 16)          17
    ...
    32             2^-20         10^-7        [2^32, 2^33)       17
    1022          9.98e291      1.0e291    [4.49e307,8.99e307)   17

Хотя таблица не является исчерпывающей, она показывает, что достаточно 17 цифр.

Надеюсь, вам понравится мое объяснение.

5 голосов
/ 19 января 2011

Двойной имеет точность 52 двоичных разрядов или 15,95 десятичных разрядов. См. http://en.wikipedia.org/wiki/IEEE_754-2008.. Чтобы записать полную точность двойного во всех случаях, вам нужно как минимум 16 десятичных цифр. [Но см. Четвертое редактирование ниже].

Кстати, это значащие цифры.

Ответ на изменения OP:

Время выполнения с плавающей запятой в десятичной строке выводит больше цифр, чем значимо. Дабл может содержать только 52 бита значений (на самом деле 53, если считать «скрытый» 1, который не сохраняется). Это означает, что разрешение не более 2 ^ -53 = 1.11e-16.

Например: 1 + 2 ^ -52 = 1.0000000000000002220446049250313. , , .

Эти десятичные цифры .0000000000000002220446049250313. , , , являются наименьшим двоичным «шагом» в двойном при преобразовании в десятичное число.

«Шаг» внутри двойника:

.000000000000000000000000000000000000000000000000000000000000000001 в двоичном формате.

Обратите внимание, что двоичный шаг является точным, а десятичный - неточным.

Отсюда и десятичное представление выше,

1,0000000000000002220446049250313. , ,

является неточным представлением точного двоичного числа:

1.0000000000000000000000000000000000000000000000000001.

Третье редактирование:

Следующее возможное значение для двойного числа, которое в точном двоичном виде:

1.0000000000000000000000000000000000000000000000000010

преобразует неточно в десятичном виде в

1.0000000000000004440892098500626. , , .

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

Четвертое редактирование:

Хотя в двойном запоминается не более 16 значащих десятичных цифр, иногда необходимо 17 десятичных цифр для представления числа . Причина связана с нарезкой цифр .

Как я упоминал выше, в двойном числе 52 + 1 двоичных цифр. «+ 1» является предполагаемым ведущим 1 и не является ни сохраненным, ни значимым. В случае целого числа эти 52 двоичных цифры образуют число от 0 до 2 ^ 53 - 1. Сколько десятичных цифр необходимо для хранения такого числа? Ну, log_10 (2 ^ 53 - 1) составляет около 15,95. Таким образом, необходимо не более 16 десятичных цифр. Давайте обозначим эти d_0 до d_15.

Теперь рассмотрим, что числа с плавающей запятой IEEE также имеют двоичный показатель степени. Что происходит, когда мы увеличиваем экспонату, скажем, на 2? Мы умножили наше 52-битное число, каким бы оно ни было, на 4. Теперь вместо наших 52 двоичных цифр, идеально совпадающих с нашими десятичными цифрами от d_0 до d_15, мы имеем несколько значимых двоичных цифр, представленных в d_16. Однако, поскольку мы умножили на что-то меньше 10, у нас все еще есть значимые двоичные цифры, представленные в d_0. Таким образом, наши десятичные цифры 15,95 теперь занимают от d_1 до d_15, плюс некоторые старшие биты d_0 и некоторые младшие биты d_16. Вот почему иногда требуется 17 десятичных цифр для представления двойного IEEE.

Пятое редактирование

Исправлены числовые ошибки

3 голосов
/ 20 января 2011

Самый простой способ (для двойного стандарта IEEE 754) гарантировать преобразование в обе стороны - всегда использовать 17 значащих цифр.Но у этого есть недостаток, заключающийся в том, что иногда нужно включать ненужные шумовые цифры (0,1 → «0,10000000000000001»).

Подход, который мне подходит, заключается в sprintf числе с 15-значной точностью, затем проверьте, если atofвозвращает вам первоначальное значение.Если это не так, попробуйте 16 цифр.Если , что не работает, используйте 17.

Возможно, вы захотите попробовать алгоритм Дэвида Гея (используется в Python 3.1 для реализации float.__repr__).

1 голос
/ 31 января 2011

Спасибо ThomasMcLeod за указание на ошибку в вычислении моей таблицы

Гарантировать конвертацию в обе стороны с использованием 15, 16 или 17 цифр можно только в сравнительно небольшом числе случаев. Число 15,95 происходит от взятия 2 ^ 53 (1 неявный бит + 52 бита в значимом / "мантиссе"), которое получается целым числом в диапазоне от 10 ^ 15 до 10 ^ 16 (ближе к 10 ^ 16).

Рассмотрим значение двойной точности x с показателем степени 0, т. Е. Оно попадает в диапазон с плавающей запятой 1.0 <= x <2.0. Неявный бит помечает 2 ^ 0 компонент (часть) x. Самый старший явный бит значимого и будет обозначать следующий младший показатель степени (от 0) <=> -1 => 2 ^ -1 или компонент 0,5.

Следующий бит 0,25, биты после 0,125, 0,0625, 0,03125, 0,015625 и т. Д. (См. Таблицу ниже). Таким образом, значение 1.5 будет представлено двумя компонентами, сложенными вместе: неявный бит, обозначающий 1,0, и самый высокий явный бит и обозначающий 0,5.

Это показывает, что от неявного бита вниз у вас есть 52 дополнительных, явных бита для представления возможных компонентов, где наименьший равен 0 (экспонента) - 52 (явные биты в значении) = -52 => 2 ^ -52, что в соответствии с приведенная ниже таблица ... ну, вы можете убедиться, что она содержит чуть более 15,95 значащих цифр (37, если быть точным). Другими словами, наименьшее число в диапазоне 2 ^ 0, то есть само! = 1.0, равно 2 ^ 0 + 2 ^ -52, что равно 1.0 + число рядом с 2 ^ -52 (ниже) = (точно) 1.0000000000000002220446049250313080847263336181640625, значение, которое я считаю 53 значащими цифрами. При «точности» форматирования из 17 цифр число будет отображаться как 1.0000000000000002, и это будет зависеть от правильного преобразования библиотеки.

Так что, может быть, «преобразование в обе стороны в 17 цифр» на самом деле не является действительной (достаточной) концепцией.

2^ -1 = 0.5000000000000000000000000000000000000000000000000000
2^ -2 = 0.2500000000000000000000000000000000000000000000000000
2^ -3 = 0.1250000000000000000000000000000000000000000000000000
2^ -4 = 0.0625000000000000000000000000000000000000000000000000
2^ -5 = 0.0312500000000000000000000000000000000000000000000000
2^ -6 = 0.0156250000000000000000000000000000000000000000000000
2^ -7 = 0.0078125000000000000000000000000000000000000000000000
2^ -8 = 0.0039062500000000000000000000000000000000000000000000
2^ -9 = 0.0019531250000000000000000000000000000000000000000000
2^-10 = 0.0009765625000000000000000000000000000000000000000000
2^-11 = 0.0004882812500000000000000000000000000000000000000000
2^-12 = 0.0002441406250000000000000000000000000000000000000000
2^-13 = 0.0001220703125000000000000000000000000000000000000000
2^-14 = 0.0000610351562500000000000000000000000000000000000000
2^-15 = 0.0000305175781250000000000000000000000000000000000000
2^-16 = 0.0000152587890625000000000000000000000000000000000000
2^-17 = 0.0000076293945312500000000000000000000000000000000000
2^-18 = 0.0000038146972656250000000000000000000000000000000000
2^-19 = 0.0000019073486328125000000000000000000000000000000000
2^-20 = 0.0000009536743164062500000000000000000000000000000000
2^-21 = 0.0000004768371582031250000000000000000000000000000000
2^-22 = 0.0000002384185791015625000000000000000000000000000000
2^-23 = 0.0000001192092895507812500000000000000000000000000000
2^-24 = 0.0000000596046447753906250000000000000000000000000000
2^-25 = 0.0000000298023223876953125000000000000000000000000000
2^-26 = 0.0000000149011611938476562500000000000000000000000000
2^-27 = 0.0000000074505805969238281250000000000000000000000000
2^-28 = 0.0000000037252902984619140625000000000000000000000000
2^-29 = 0.0000000018626451492309570312500000000000000000000000
2^-30 = 0.0000000009313225746154785156250000000000000000000000
2^-31 = 0.0000000004656612873077392578125000000000000000000000
2^-32 = 0.0000000002328306436538696289062500000000000000000000
2^-33 = 0.0000000001164153218269348144531250000000000000000000
2^-34 = 0.0000000000582076609134674072265625000000000000000000
2^-35 = 0.0000000000291038304567337036132812500000000000000000
2^-36 = 0.0000000000145519152283668518066406250000000000000000
2^-37 = 0.0000000000072759576141834259033203125000000000000000
2^-38 = 0.0000000000036379788070917129516601562500000000000000
2^-39 = 0.0000000000018189894035458564758300781250000000000000
2^-40 = 0.0000000000009094947017729282379150390625000000000000
2^-41 = 0.0000000000004547473508864641189575195312500000000000
2^-42 = 0.0000000000002273736754432320594787597656250000000000
2^-43 = 0.0000000000001136868377216160297393798828125000000000
2^-44 = 0.0000000000000568434188608080148696899414062500000000
2^-45 = 0.0000000000000284217094304040074348449707031250000000
2^-46 = 0.0000000000000142108547152020037174224853515625000000
2^-47 = 0.0000000000000071054273576010018587112426757812500000
2^-48 = 0.0000000000000035527136788005009293556213378906250000
2^-49 = 0.0000000000000017763568394002504646778106689453125000
2^-50 = 0.0000000000000008881784197001252323389053344726562500
2^-51 = 0.0000000000000004440892098500626161694526672363281250
2^-52 = 0.0000000000000002220446049250313080847263336181640625
0 голосов
/ 27 апреля 2011

@ ThomasMcLeod: Я думаю, что правило значащих цифр исходит из моей области, физики, и означает нечто более тонкое:

Если у вас есть измерение, которое дает вам значение 1,52, и вы не можете прочитать более подробную информациюмасштаб и скажем, что вы должны добавить к нему другое число (например, другое измерение, потому что масштаб этого был слишком мал), скажем, 2, тогда результат (очевидно) имеет только 2 десятичных знака, то есть 3,52.Но также, если вы добавите 1.1111111111 к значению 1.52, вы получите значение 2.63 (и больше ничего!).

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

Тем не менее, это конкретное правило предназначено только для сложения (для сложения: ошибка результата равна сумме двух ошибок - так что если вы измерите только одну ошибку, хотя удачу, тамВаша точность ...).

Как получить другие правила: Допустим, a - это измеренное число, а δa - ошибка.Допустим, ваша исходная формула была: f: = ma Допустим, вы также измеряете m с ошибкой δm (пусть это будет положительной стороной).Тогда фактический предел: f_up = (m + δm) (a + δa) и f_down = (m-δm) (a-δa) Итак, f_up = m a + δm δa + (δm a + m δa) f_down = ma + δm δa- (δm a + m δa) Следовательно, теперь значащие цифры еще меньше: f_up ~ m a + (δm a + m δa) f_down ~ m a- (δm a + m δa) и, следовательно, δf = δma + m δa Если вы посмотрите на относительную ошибку, вы получите: δf / f = δm / m + δa / a

Для деления это δf / f = δm / m-δa / a

Надеюсь, что вы поняли суть, и надеюсь, что я не совершил слишком много ошибок, уже поздно: -)

tl, dr: Значимые цифры означают, сколько цифр в выходных данных на самом деле пришлоцифры на вашем входе (в реальном мире, а не искаженное изображение, которое имеют числа с плавающей запятой).Если ваши измерения были 1 с ошибкой «нет» и 3 с ошибкой «нет» и предполагается, что функция равна 1/3, тогда да, все бесконечные цифры являются действительными значащими цифрами.В противном случае обратная операция не будет работать, поэтому, очевидно, они должны быть такими.

Если правило значащих цифр означает что-то совершенно другое в другом поле, продолжайте: -)

...