2011-01-19 2 views
31

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

Я пробовал:

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; 

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

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

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 ответ ниже:

Мы можем напечатать точно с помощью форматировщик Е(), "% с" или «% А». Для достижения этой цели в 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?

+0

Почему вы включаете пробел после 'v' при выводе на' ss'? – chrisaycock

+0

@chrisaycock: Нет причин. Вырезать и вставить ошибку. –

+1

Всегда будет потеря точности, за исключением тех значений, которые имеют двоичный деноменант. Вопрос должен быть * Сколько точности должно быть сохранено? * –

ответ

14

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

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

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

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

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

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

+0

Это спецификатор этого типа присутствует во всех временных рядах? Я имею в Visual C++, но некоторые ссылки не имеют его. http://www.cplusplus.com/reference/clibrary/cstdio/printf/ – ThomasMcLeod

+1

Спецификатор '% a' был в стандарте C на 11 лет; любая платформа, которая все еще не поддерживает ее, не может претендовать на «C». 'hexfloat' был добавлен в C++ 0x (я считаю, что я не парень C++), поэтому его использование может быть несколько менее переносимым. –

+0

Вы можете получить% форматирования, указав фиксированное и научное форматирование. –

5

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

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

Ответ на OP редактирует:

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

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

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

"шаг" внутри двойной является:

.0000000000000000000000000000000000000000000000000001 в двоичной системе.

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

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

1,0000000000000002220446049250313. , ,

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

1,0000000000000000000000000000000000000000000000000001.

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

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

1,0000000000000000000000000000000000000000000000000010

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

1.0000000000000004440892098500626. , , ,

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

Четвертый Edit:

Хотя двойные магазины в большинстве 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 double.

Пятый Редактировать

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

+0

Когда я использую научную нотацию и точность, это работает точно так же, как вы описываете. '(numeric_limits :: digits10 + 1) == 16'. И в моем исходном коде это означает, что точность не потеряна. Но когда я печатаю с 53 цифрами, это указывает на то, что есть больше точности, чем я использовал (см. Править выше). Я не понимаю расхождения. –

16

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

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

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

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

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

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

+0

Я думаю, что вы имели в виду «релевантные» для «правильных» :) – MSN

+2

@MSN: Нет, «правильно», правильно, правильно. Плавающая точка * часто * неточна, но так много целочисленных алгоритмов. Вполне возможно писать точные алгоритмы в плавающей запятой (на самом деле, это большая часть того, что мне платят). –

+0

@ Stephen, ну, это также не имеет отношения к обсуждению. Думаю, было бы более «правильным» сказать, что контекст определяет точность, а не представление. – MSN

3

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

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

Вы могли бы хотеть попробовать David Gay's algorithm (используется в Python 3.1 для реализации float.__repr__).

+0

Спасибо за это. Это очень хорошая идея. – JohnB

+2

Существует интересная аномалия в процессе «попробуйте 15, а затем затем 17», который может пропустить 16-значную строку с круглым отключением - см. Мою статью http://www.exploringbinary.com/the-shortest-decimal-string -that-round-trip-may-not-be-the-ближайшее/ –

1

Благодаря 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 
+1

Во-первых, математика преобразования неверна. Например, 2^-7 составляет 0,0078125, а не 0,0070125, как вы уже опубликовали.Во-вторых, даже если цифры на последней строке были правильными, они ** не имеют значения. ** Они являются базовыми артефактами преобразования. См. Мой пост выше. – ThomasMcLeod

+0

@ThomasMcLeod: Спасибо, что указали ошибки. Что касается вашего заявления «они не значимы», я прошу различаться. В подавляющем большинстве случаев они не будут значительными, но в нескольких случаях. Мой пост попытался указать на сложности округления и преобразования, указав количество фактически задействованных цифр. –

+0

@Olof, как вы определяете значение? Если мы разделим 1 на 3, то получим 0.3333333333333333 ..., но это не значит, что у нас есть бесконечные значащие цифры. Основное правило: результат математической операции никогда не может иметь более значительных цифр, чем количество значащих цифр любого числового ввода для этой операции. – ThomasMcLeod

0

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

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

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

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

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

Для разделения это ; F/f = δm/m-δa/a

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

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

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

9

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

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

enter image description here

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

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

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

Возьмите конкретный случай, когда показатель степени 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 цифр достаточно.

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

+1

http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html –

+0

Спасибо за голосование. Я получил некоторое понимание. Базовый квант 10 должен быть <= базовый 2 квант, потому что это единственный способ гарантировать, что для каждой точки на базовой линии номер 2, ближайшая база 10 тиков находится в пределах 1/2 шага! Это обеспечивает точное преобразование. –

Смежные вопросы