Когда выгодно конвертировать из float в double через decimal

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

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

мы заметили, что при преобразовании поплавков через decimal (см. код ниже) мы получаем более точные результаты, чем при непосредственном преобразовании. Примечание: приложение B также использует двойники внутри и записывает их только в файл как поплавки. Предположим, приложение B имело номер 0.012, записанный в файл как float. Если мы преобразуем его после чтения в decimal, а затем в double мы получаем ровно 0.012, если перевести напрямую, мы получаем 0.0120000001043081.

Это может быть воспроизведена без чтения из файла с заданием:

    float readFromFile = 0.012f;
    Console.WriteLine("Read from file: " + readFromFile); 
    //prints 0.012

    double forUse = readFromFile;
    Console.WriteLine("Converted to double directly: " + forUse);
    //prints 0.0120000001043081

    double forUse1 = (double)Convert.ToDecimal(readFromFile);
    Console.WriteLine("Converted to double via decimal: " + forUse1); 
    //prints 0.012

всегда ли полезно конвертировать из float в double через decimal, и если нет, то при каких условиях это выгодно?

EDIT: приложение B может получить значения, которые оно сохраняет двумя способами:

  1. значение может быть результатом расчета
  2. значение может быть введен Пользователем в виде десятичной дроби (так в приведенном выше примере пользователь набрал 0.012 в поле редактирования, и он был преобразован в double, а затем сохранен в float)

3 ответов


мы получаем именно 0.012

нет, не знаешь. Ни то, ни другое!--2-->, ни double может представлять 3/250 точно. Что вы получаете-это значение, которое отображается в строке форматирования Double.ToString() as "0.012". Но это происходит потому, что форматер не отображает точное значение.

проходя decimal вызывает округление. Скорее всего, гораздо быстрее (не говоря уже о том, что проще понять) просто использовать Math.Round с параметрами округления, которые вы хотите. Если вас волнует количество значащих цифр, см.:


чего бы это ни стоило,0.012f (что означает, что 32-разрядное значение IEEE-754, ближайшее к 0.012), точно

0x3C449BA6

или

0.012000000104308128

и ровно представимыми как System.Decimal. Но!--10--> не даст вам это точное значение -- per документация существует округление шаг.

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


как ни странно, преобразование через decimal (с Convert.ToDecimal(float)) может быть полезным в некоторых обстоятельствах.

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

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

как вы помните из OP, это последовательность шагов:

  1. приложение B имеет двойники, поступающие из двух источников: (a) результаты расчетов; (b) преобразованные из введенных пользователем десятичных чисел.
  2. приложение B записывает свои двойники как поплавки в файл-эффективно выполнение двоичного округления из 52 двоичных цифр ( IEEE 754 одиночный) до 23 двоичных цифр (IEEE 754 двойной).
  3. наше приложение считывает этот float и преобразует его в double одним из двух способов:

    (a) прямое назначение двойного эффективного заполнения 23 - разрядного числа 52-разрядным числом с двоичными нулями (29 нулевых битов);

    (b) через преобразование в десятичное с (double)Convert.ToDecimal(float).

Как правильно заметил Бен Фойгта Convert.ToDecimal(float) (см. MSDN в разделе Примечания) округляет результат до 7 значащих десятичных цифр. В Википедии IEEE 754 article о сингле мы можем прочитать, что precision is 24 bits - equivalent to log10(pow(2,24)) ≈ 7.225 decimal digits. Итак, когда мы делаем преобразование в decimal мы теряем 0.225 десятичной цифры.

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

но (!) если есть дополнительные знания о том, что первоначально (до записи в файл в виде поплавков) двойники были десятичными числами не более 7 цифр, ошибки округления, введенные в десятичном округлении (Шаг 3 b) выше)компенсирует ошибки округления, введенные с двоичным округлением (в шаге 2. выше.)

в программе, чтобы доказать утверждение для общего случая я произвольно генерирую двойные, затем бросаю его в float, затем преобразую его обратно в double (a) напрямую, (b) через decimal, затем я измеряю расстояние между исходным double и double (a) и double (b). Если double (a) ближе к оригиналу, чем double (b), I increment Pro-direct conversion counter, в противоположном случае я увеличиваю Pro-viaDecimal счетчик. Я делаю это в цикле 1 млн. циклы, затем я печатаю отношение Pro-direct к pro-viaDecimal счетчикам. Соотношение получается около 3.7, т. е. примерно в 4 случаях из 5 преобразование через десятичный испортят количество.

доказать случай, когда номера вводятся пользователями я использовал ту же программу с единственным изменением что я применяю Math.Round(originalDouble, N) в парном разряде. Поскольку я получаю originalDoubles из случайного класса, все они будут между 0 и 1, поэтому количество значимых цифр совпадает с количеством цифр после десятичной точки. Я поместил этот метод в цикл от 1 значащей цифры до 15 значащих цифр, набранных пользователем. Затем я нарисовал его на графике. Зависимость (сколько раз прямое преобразование лучше, чем преобразование через decimal) от количества значащих цифр, набранных пользователь. The dependency of (how many times direct conversion is better than via decimal) from the number of significant digits typed by user.

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

вот код, используемый для сравнения:

private static void CompareWhichIsBetter(int numTypedDigits)
{
    Console.WriteLine("Number of typed digits: " + numTypedDigits);
    Random rnd = new Random(DateTime.Now.Millisecond);
    int countDecimalIsBetter = 0;
    int countDirectIsBetter = 0;
    int countEqual = 0;

    for (int i = 0; i < 1000000; i++)
    {
        double origDouble = rnd.NextDouble();
        //Use the line below for the user-typed-in-numbers case.
        //double origDouble = Math.Round(rnd.NextDouble(), numTypedDigits); 

        float x = (float)origDouble;
        double viaFloatAndDecimal = (double)Convert.ToDecimal(x);
        double viaFloat = x;

        double diff1 = Math.Abs(origDouble - viaFloatAndDecimal);
        double diff2 = Math.Abs(origDouble - viaFloat);

        if (diff1 < diff2)
            countDecimalIsBetter++;
        else if (diff1 > diff2)
            countDirectIsBetter++;
        else
            countEqual++;
    }

    Console.WriteLine("Decimal better: " + countDecimalIsBetter);
    Console.WriteLine("Direct better: " + countDirectIsBetter);
    Console.WriteLine("Equal: " + countEqual);
    Console.WriteLine("Betterness of direct conversion: " + (double)countDirectIsBetter / countDecimalIsBetter);
    Console.WriteLine("Betterness of conv. via decimal: " + (double)countDecimalIsBetter / countDirectIsBetter );
    Console.WriteLine();
}

вот другой ответ - я не уверен, что он лучше, чем у Бена (почти наверняка нет), но он должен дать правильные результаты:

float readFromFile = 0.012f;
decimal forUse = Convert.ToDecimal(readFromFile.ToString("0.000"));

пока .ToString("0.000") производит "правильное" число (которое должно быть легко проверить), тогда вы получите то, с чем можете работать, и вам не придется беспокоиться об ошибках округления. Если вам нужно больше точности, просто добавьте больше 0 ' s.

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