Подтвердить что ты не робот

Печать двойная без потери точности

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

Я пробовал:

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". Для достижения этого на С++ нам нужно использовать фиксированные и научные манипуляторы (см. 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?

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

Неправильно сказать, что "с плавающей точкой является неточной", хотя я признаю это полезным упрощением. Если бы мы использовали базовые 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 бит точности.) Но для резервного копирования или восстановления, где бит-бит-точность может сравниваться автоматически, это очевидно, не является идеальным.

Ответ 3

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

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

enter image description here

Представьте себе 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 цифр являются достаточными.

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

Ответ 4

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

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

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

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

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

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

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

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

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

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

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 double.

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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

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

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

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

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

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

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