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

Преобразование float для двойной потери точности, но не через ToString

У меня есть следующий код:

float f = 0.3f;
double d1 = System.Convert.ToDouble(f);
double d2 = System.Convert.ToDouble(f.ToString());

Результаты эквивалентны:

d1 = 0.30000001192092896;
d2 = 0.3;

Мне любопытно узнать, почему это?

4b9b3361

Ответ 1

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

Чтобы увидеть это более четко

float f = 0.3f;
double d1 = System.Convert.ToDouble(f);
double d2 = System.Convert.ToDouble(f.ToString("G20"));

string s = string.Format("d1 : {0} ; d2 : {1} ", d1, d2);

вывод

"d1 : 0.300000011920929 ; d2 : 0.300000012 "

Ответ 2

Вы не теряете точность; вы повышаете представление до более точного представления (двойного, 64-битного) от менее точного представления (float, 32-бит в длину). То, что вы получаете в более точном представлении (мимо определенного момента), - это просто мусор. Если бы вы вернули его в float FROM из double, у вас была бы такая же точность, как и раньше.

Что происходит, так это то, что у вас есть 32 бита для вашего поплавка. Затем вы повышаете до двух, добавляя еще 32 бита для представления вашего номера (всего 64). Эти новые биты являются наименее значимыми (самые дальние справа от вашей десятичной точки) и не имеют никакого отношения к фактическому значению, поскольку раньше они были неопределенными. В результате эти новые биты имеют любые значения, которые у них были, когда вы делали свой взлет. Другими словами, они так же неопределенны, как и раньше.

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

Механизм преобразования из строки в float отличается; компилятору необходимо проанализировать семантическое значение символьной строки "0,3f" и выяснить, как это относится к значению с плавающей запятой. Это не может быть сделано с битовым сдвигом, таким как float/double conversion - таким образом, значение, которое вы ожидаете.

Для получения дополнительной информации о том, как работают числа с плавающей запятой, вам может быть интересно проверить эту статью в википедии по стандарту IEEE 754-1985 ( который содержит некоторые удобные изображения и хорошее объяснение механики вещей), и эту статью wiki об обновлениях стандарта в 2008 году.

изменить

Во-первых, как отметил @phoog ниже, повышение от float до double не так просто, как добавление еще 32 бит в пространство, зарезервированное для записи номера. В действительности вы получите добавление 3 бит для экспоненты (всего 11) и еще 29 бит для фракции (всего 52). Добавьте в бит знака, и у вас есть всего 64 бит для двойника.

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

Вернемся к предыдущему, когда ваш код будет компилироваться в бинарный язык машинного языка, компиляторы (по крайней мере, компиляторы C и С++) не добавили бы никаких инструкций ЦП для "очистки" или инициализации значения в памяти, когда вы зарезервировали пространство для переменной. Поэтому, если программист явно не инициализировал переменную до некоторого значения, значения бит, зарезервированных для этого местоположения, будут поддерживать любое значение, которое они имели до того, как вы зарезервировали эту память.

В среде .NET ваш С# или другой язык .NET компилируется на промежуточный язык (CIL, Common Intermediate Language), который затем выполняется Just-In-Time, скомпилированный CLR для выполнения в качестве собственного кода. Там может быть или не быть шаг переменной инициализации, добавленный либо компилятором С#, либо компилятором JIT; Я не уверен.

Вот что я знаю:

  • Я проверил это, выставив float в три разных двойника. Каждый из результатов имел то же значение.
  • Это значение было точно таким же, как и значение @rerun выше: double d1 = System.Convert.ToDouble(f); результат: d1 : 0.300000011920929
  • Я получаю тот же результат, если я использую double d2 = (double)f; Результат: d2 : 0.300000011920929

С тремя из нас, получающими одинаковые значения, похоже, что значение upcast является детерминированным (а не фактически битом для мусора), что указывает на то, что .NET делает то же самое на всех наших машинах. По-прежнему можно сказать, что дополнительные цифры не более или менее точные, чем раньше, потому что 0,3f не точно равен 0,3 - он равен 0,3, до семи цифр точности. Мы ничего не знаем о значениях дополнительных цифр за первые семь.

Ответ 3

Я использую десятичное копирование для правильного результата в этом случае и в том же другом случае

float ff = 99.95f;
double dd = (double)(decimal)ff;