Как провести вычисление только в рамках типа float, а не dou
От: oziro Нигерия  
Дата: 01.12.16 12:41
Оценка:
Привет!

Есть такие функции:
        static double GetDblEpsilon()
        {
            double d = 1;
            while (1.0 + d / 2 != 1.0)
            {
                d = d / 2;
            }
            return d;
        }

        static float GetFltEpsilon()
        {
            float d = 1f;
            while (1.0f + d / 2f != 1.0f)
            {
                d = d / 2f;
            }
            return d;
        }

...
            Console.WriteLine(GetDblEpsilon());
            Console.WriteLine(GetFltEpsilon());


Выводят
2.22044604925031E-16
2.220446E-16


Хотя по моим понятиям должно выводить
2.22044604925031E-16
1.192093E-07


Почему и как добиться вычисления в рамках типа float во второй функции, а не double c привидением к float?

(Если кто не понял, это попытка получить значения DBL_EPSILON и FLT_EPSILON, которых по какой-то причине не завезли в дотнет)
Отредактировано 01.12.2016 15:14 VladD2 . Предыдущая версия .
Re: Epsilon
От: Qbit86 Кипр
Дата: 01.12.16 12:49
Оценка:
Здравствуйте, oziro, Вы писали:

O>(Если кто не понял, это попытка получить значения DBL_EPSILON и FLT_EPSILON, которых по какой-то причине не завезли в дотнет)


Console.WriteLine(float.Epsilon);
Console.WriteLine(double.Epsilon);
Глаза у меня добрые, но рубашка — смирительная!
Re[2]: Epsilon
От: oziro Нигерия  
Дата: 01.12.16 12:51
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, oziro, Вы писали:


O>>(Если кто не понял, это попытка получить значения DBL_EPSILON и FLT_EPSILON, которых по какой-то причине не завезли в дотнет)


Q>
Console.WriteLine(float.Epsilon);
Q>Console.WriteLine(double.Epsilon);


Я же пометил, DBL_EPSILON это совсем не то, что double.Epsilon. Где-то, примерно так, на 300 порядков, всего лишь.
Отредактировано 01.12.2016 13:11 oziro . Предыдущая версия .
Re: Как провести вычисление только в рамках типа float, а не double
От: Sharov Россия  
Дата: 01.12.16 12:53
Оценка:
Здравствуйте, oziro, Вы писали:

O>(Если кто не понял, это попытка получить значения DBL_EPSILON и FLT_EPSILON, которых по какой-то причине не завезли в дотнет)


https://msdn.microsoft.com/ru-ru/library/system.double.epsilon(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.single.epsilon(v=vs.110).aspx
Кодом людям нужно помогать!
Re: Как провести вычисление только в рамках типа float, а не
От: kov_serg Россия  
Дата: 01.12.16 12:56
Оценка: 12 (2)
Здравствуйте, oziro, Вы писали:

O>Привет!

O>(Если кто не понял, это попытка получить значения DBL_EPSILON и FLT_EPSILON, которых по какой-то причине не завезли в дотнет)
Просто проводи вычисления с типом float и все промежуточные результаты складывай во временные переменные

static float eps() {
    float x = 1/3f, y = 3*x-1; 
    return 2*y;
}
static float eps1() {
    float x = 1,y = 1,t;
    for(;;) {
        t = x + y;
        if (t == x) return y;
        y /= 2;
    }
}
Отредактировано 01.12.2016 12:59 kov_serg . Предыдущая версия .
Re[2]: Как провести вычисление только в рамках типа float, а
От: oziro Нигерия  
Дата: 01.12.16 13:02
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Просто проводи вычисления с типом float и все промежуточные результаты складывай во временные переменные



Шикарно, это колдунство!

        static float GetFltEpsilon()
        {
            float d = 1;
            float f1 = 1.0f + d / 2.0f;
            while (f1 != 1.0f)
            {
                d = d / 2f;
                f1 = 1.0f + d / 2.0f;
            }
            return d;
        }
Отредактировано 01.12.2016 13:03 oziro . Предыдущая версия .
Re[2]: Как провести вычисление только в рамках типа float, а не
От: oziro Нигерия  
Дата: 01.12.16 13:08
Оценка:
Подскажите, пожалуйста

    internal static class Utils
    {
        private static double GetDblEpsilon()
        {
            double d = 1;
            while (1.0 + d / 2 != 1.0)
            {
                d = d / 2;
            }
            return d;
        }

        internal static readonly double DBL_EPSILON = GetDblEpsilon();

        private static float GetFltEpsilon()
        {
            float d = 1;
            float f1 = 1.0f + d / 2.0f;
            while (f1 != 1.0f)
            {
                d = d / 2f;
                f1 = 1.0f + d / 2f;
            }
            return d;
        }

        internal static readonly float FLT_EPSILON = GetFltEpsilon();
    }


Будет ли тут Utils.DBL_EPSILON единожды инициализирована и как бы константа?

Спасибо
Re[3]: Как провести вычисление только в рамках типа float, а не
От: Sharov Россия  
Дата: 01.12.16 13:35
Оценка: 2 (1)
Здравствуйте, oziro, Вы писали:

O>Подскажите, пожалуйста


O>
O>    internal static class Utils
O>    {
O>        private static double GetDblEpsilon()
O>        {
O>            double d = 1;
O>            while (1.0 + d / 2 != 1.0)
O>            {
O>                d = d / 2;
O>            }
O>            return d;
O>        }

O>        internal static readonly double DBL_EPSILON = GetDblEpsilon();

O>        private static float GetFltEpsilon()
O>        {
O>            float d = 1;
O>            float f1 = 1.0f + d / 2.0f;
O>            while (f1 != 1.0f)
O>            {
O>                d = d / 2f;
O>                f1 = 1.0f + d / 2f;
O>            }
O>            return d;
O>        }

O>        internal static readonly float FLT_EPSILON = GetFltEpsilon();
O>    }

O>


O>Будет ли тут Utils.DBL_EPSILON единожды инициализирована и как бы константа?


Да.
Кодом людям нужно помогать!
Re[3]: Как провести вычисление только в рамках типа float, а
От: Sinix  
Дата: 01.12.16 13:36
Оценка: 29 (3)
Здравствуйте, oziro, Вы писали:

_>>Просто проводи вычисления с типом float и все промежуточные результаты складывай во временные переменные

O>Шикарно, это колдунство!

только не в временные переменные, а в массив, или в поля класса.
Это не колдунство, это спецификация CLI (врочем, по Кларку она неотличима от магии):

Storage locations for floating-point numbers (statics, array elements, and fields of classes) are of fixed size. The supported storage sizes are float32 and float64. Everywhere else (on the evaluation stack, as arguments, as return types, and as local variables) floating-point numbers are represented using an internal floating-point type. In each such instance, the nominal type of the variable or expression is either float32 or float64, but its value can be represented internally with additional range and/or precision. The size of the internal floating-point representation is implementation-dependent, can vary, and shall have precision at least as great as that of the variable or expression being represented.

Если повезёт — приводит к спецэффектам
Автор: Sinix
Дата: 25.06.14
.

А, да, не забываем про ещё более милый нюанс. Подробности.
Re[2]: Как провести вычисление только в рамках типа float, а не
От: cures Россия cures.narod.ru
Дата: 01.12.16 19:26
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Просто проводи вычисления с типом float и все промежуточные результаты складывай во временные переменные


А если с полной оптимизацией собрать, не поломается? Там ниже в классы и массивы советуют пихать, в тайной надежде, что оптимизатор туда не дотянется, но тоже ненадёжно. В gcc для этого есть ключ -ffloat-store, а в диезах может быть никому такое не требуется.
Re[3]: Как провести вычисление только в рамках типа float, а не
От: Sinix  
Дата: 01.12.16 19:32
Оценка:
Здравствуйте, cures, Вы писали:

C>Там ниже в классы и массивы советуют пихать, в тайной надежде, что оптимизатор туда не дотянется, но тоже ненадёжно.

Не дотянется, даже с .net native. Единственно, не ручаюсь, что положение не поменяется где-нибудь через пару релизов.

Кстати, спросить-то забыл. Что за задача такая, что "лишняя" точность мешает?
Re[3]: Как провести вычисление только в рамках типа float, а
От: Sinix  
Дата: 01.12.16 19:37
Оценка:
Здравствуйте, oziro, Вы писали:

O>Будет ли тут Utils.DBL_EPSILON единожды инициализирована и как бы константа?


Будет, причём именно как константа
Автор: Sinix
Дата: 27.03.16
по факту.

Другой вопрос — что вы с этой константой собираетесь делать?
Сравнивать числа так по очевидной причине смысла нет.

UPD особенно с учётом вот этого:

If you create a custom algorithm that determines whether two floating-point numbers can be considered equal, we do not recommend that you base your algorithm on the value of the Epsilon constant to establish the acceptable absolute margin of difference for the two values to be considered equal. (Typically, that margin of difference is many times greater than Epsilon.) For information about comparing two double-precision floating-point values, see Double and Equals(Double).

Platform Notes

On ARM systems, the value of the Epsilon constant is too small to be detected, so it equates to zero. You can define an alternative epsilon value that equals 2.2250738585072014E-308 instead.


Кэп: попробуйте догадаться откуда
Отредактировано 01.12.2016 19:41 Sinix . Предыдущая версия .
Re[4]: Как провести вычисление только в рамках типа float, а
От: oziro Нигерия  
Дата: 02.12.16 05:37
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Другой вопрос — что вы с этой константой собираетесь делать?


DBL_EPSILON — это не Double.Epsilon.

DBL_EPSILON the minimum positive number such that 1.0 + DBL_EPSILON != 1.0.

Зря его нет в библиотеке, он равен для double 2.2204460492503131E-16

А алгоритм мой — понятен, по крайней мере мне.

        static bool Equal(double a, double b, uint precisionFactor)
        {
            //in case they are Infinities (then epsilon check does not work)
            if (a == b)
                return true;
            double epsilon = precisionFactor * DBL_EPSILON * (Math.Abs(a) + Math.Abs(b));
            return Math.Abs(a - b) <= epsilon;
        }


precisionFactor берем 16, это 4 бита или примерно 1 наименее значащая десятичная цифра мантиссы. Тем самым довольно универсальный способ сравнения.

Ну, при сравнении с нулем есть нюансы, понятно, сделал специализированные функции.

Но я радикально считаю что Double.Epsilon (и явовскому MIN_NORMAL) не место тут совершенно, не пойму, почему их пихают по всему интернету.

Кстати, решил не считать значения DBL_EPSILON как в первом сообщении, и вбить руками. Меньше кода меньше проблем. Можно вообще тупо 1e-15 задать, по идее тоже норм будет.
Re[5]: Как провести вычисление только в рамках типа float, а
От: Sinix  
Дата: 02.12.16 06:41
Оценка:
Здравствуйте, oziro, Вы писали:

O>Зря его нет в библиотеке, он равен для double 2.2204460492503131E-16

Я в курсе. Проблема в том, что корректное с математической точки зрения сравнение на практике работает с сюрпризами.
        static double GetDblEpsilon()
        {
            double d = 1;
            while (1.0 + d / 2 != 1.0)
            {
                d = d / 2;
            }
            return d;
        }

        private static readonly double DBL_EPSILON = GetDblEpsilon();
        static bool Equal(double a, double b, uint precisionFactor)
        {
            //in case they are Infinities (then epsilon check does not work)
            if (a == b)
                return true;
            double epsilon = precisionFactor * DBL_EPSILON * (Math.Abs(a) + Math.Abs(b));
            return Math.Abs(a - b) <= epsilon;
        }

        static void Main(string[] args)
        {
            var a = 1e-320;
            var b = 0d;
            Console.WriteLine(a.ToString("R"));
            Console.WriteLine(b.ToString("R"));
            Console.WriteLine(Equal(a, b, 1024)); // false

            Console.WriteLine();

            a = 1;
            b = 1 * (1 + 1e-15);
            Console.WriteLine(a.ToString("R"));
            Console.WriteLine(b.ToString("R"));
            Console.WriteLine(Equal(a, b, 1024)); // true
        }

сравнение с чувствительностью к погрешностям в 10-320, которое при этом игнорит разницу в 10-15 — не та штука, которую стоит иметь в бизнес-коде.

Впрочем, вариант в MSDN:
        static bool IsApproximatelyEqual(double value1, double value2, double epsilon)
        {
            // If they are equal anyway, just return True.
            if (value1.Equals(value2))
                return true;

            // Handle NaN, Infinity.
            if (Double.IsInfinity(value1) | Double.IsNaN(value1))
                return value1.Equals(value2);
            else if (Double.IsInfinity(value2) | Double.IsNaN(value2))
                return value1.Equals(value2);

            // Handle zero to avoid division by zero
            double divisor = Math.Max(value1, value2);
            if (divisor.Equals(0))
                divisor = Math.Min(value1, value2);

            return Math.Abs(value1 - value2) / divisor <= epsilon;
        }

ещё хуже.
            var a = 1e-320;
            var b = 0d;
            Console.WriteLine(IsApproximatelyEqual(a, b, 0.0000001));  // false
            Console.WriteLine(IsApproximatelyEqual(-a, b, 0.0000001)); // true



Угу, сравнивать double могут не только лишь все.
Re[6]: Как провести вычисление только в рамках типа float, а
От: oziro Нигерия  
Дата: 02.12.16 07:18
Оценка: 37 (2) +1
Здравствуйте, Sinix, Вы писали:

S>Угу, сравнивать double могут не только лишь все.


Ага, заморочки присутствуют.

Вообще, у меня
        static bool Equal(double a, double b)
        {            
            return Equal(a, b, 16); // вышеупомянутся Equal()
        }


используется по дефолту во всех местах.

А для нуля — особая ситуация.

        bool IsZero(double a, double estimateValue)
        {
            double epsilon = 16 * DBL_EPSILON * Math.Abs(estimateValue);
            return Math.Abs(a) <= epsilon;
        }


Т.к. в одной ситуации сравнение 1e-320 c нулем должно дать True, а другом False, в зависимости от самих вычислений. Поэтому я передаю в функцию оценочное значение.

и тогда
    False == IsZero(1e-320, 1e-310); // 1e-320 != 0.  (1e-310 - напрмер, одно из значений, участвующих в вычислениях)
    True == IsZero(1e-320, 1e-100); // 1e-320 == 0 (1e-100 - аналогично, примерное значение, участвующее в вычислених)


Короче, с нулем надо сравнивать с указанной оценкой — это, наверно, всегда. Ну, или делать умолчание на диапазон типа 1e-9 .. 1e9 , но всегда понимать, что тогда оно не будет работать во многих случаях, а это верные грабли, потому что забудут и будет использовать. Поэтому IsZero с доп. параметром-оценкой.

UPD Я, конечно, ошибся. 1e-320 может быть представлено в double Fixed
Отредактировано 02.12.2016 7:26 oziro . Предыдущая версия . Еще …
Отредактировано 02.12.2016 7:25 oziro . Предыдущая версия .
Отредактировано 02.12.2016 7:19 oziro . Предыдущая версия .
Re[7]: Как провести вычисление только в рамках типа float, а
От: Sinix  
Дата: 02.12.16 08:29
Оценка: +1 :)
Здравствуйте, oziro, Вы писали:

O>Короче, с нулем надо сравнивать с указанной оценкой — это, наверно, всегда. Ну, или делать умолчание на диапазон типа 1e-9 .. 1e9 , но всегда понимать, что тогда оно не будет работать во многих случаях, а это верные грабли, потому что забудут и будет использовать. Поэтому IsZero с доп. параметром-оценкой.


Ну да. На практике это всё сводится к эвристикам, которые отлично работают с _конкретными_ сценариями, и фейлятся на всех остальных.

К примеру, для фиксированного диапазона значений с примерно одним порядком величин может сработать абсолютная погрешность где-нибудь в одну миллионную от max value.

Где-то приходится вводить комбо из относительной и абсолютных погрешностей, для каких-то значений в итоге проще может оказаться с decimal работать и округлять до n-го знака после запятой…

Пожалуй, единственное, чего я ещё не видел — схемы сравнения, которая бы устраивала всех
Отредактировано 02.12.2016 8:30 Sinix . Предыдущая версия .
Re[4]: Как провести вычисление только в рамках типа float, а не
От: cures Россия cures.narod.ru
Дата: 02.12.16 14:02
Оценка: 23 (1)
Здравствуйте, Sinix, Вы писали:

S>Кстати, спросить-то забыл. Что за задача такая, что "лишняя" точность мешает?


Это наверное не совсем ко мне, скорее к топикстартеру вопрос
Из своего опыта, видел такое в математических библиотеках, для определения того, с какой точностью в принципе имеет смысл вести вычисления. Например, в QR-алгоритме надо понимать, до какого уровня можно занулять получающиеся поддиагональные элементы.
В нашем коде -ffloat-store используется для (побитовой) воспроизводимости результата на разных платформах, это нужно при проверке работоспособности библиотек и аппаратуры.
Re[5]: Как провести вычисление только в рамках типа float, а не
От: oziro Нигерия  
Дата: 02.12.16 14:17
Оценка:
Здравствуйте, cures, Вы писали:

C>Здравствуйте, Sinix, Вы писали:


S>>Кстати, спросить-то забыл. Что за задача такая, что "лишняя" точность мешает?


C>Это наверное не совсем ко мне, скорее к топикстартеру вопрос


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

А на свои вопросы я ответы получил, всем спасибо!
Re[4]: Как провести вычисление только в рамках типа float, а
От: Sharov Россия  
Дата: 05.12.16 11:48
Оценка:
Здравствуйте, Sinix, Вы писали:

Касательно последних двух ссылок: я правильно понимаю, что в своем представлении float\double могут использовать до 80 бит, которые будут обрезаны до 32\64 бит при попытке сохранения в память?

Well, big mistake and the source of an enormous number of questions at SO. The idea was scrapped when Intel implemented the next generation floating point hardware, the XMM and YMM registers are 64-bit. True registers, not a stack. The x64 jitter uses them. Making your program running in 64-bit mode produce different results from when it runs in 32-bit mode. It will take another ten years before that stops hurting


Круть какая, я и не знал, что там все так весело. Особенно легаси x87...
Кодом людям нужно помогать!
Re[5]: Как провести вычисление только в рамках типа float, а
От: Sinix  
Дата: 05.12.16 11:57
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Касательно последних двух ссылок: я правильно понимаю, что в своем представлении float\double могут использовать до 80 бит, которые будут обрезаны до 32\64 бит при попытке сохранения в память?

Угу. Только "до 80 и более". Мало ли какое железо попадётся
И не при попытке сохранения вообще, а в оговоренные в CLI spec места.

S>Круть какая, я и не знал, что там все так весело.


Ну да. Ещё пару цитат оттуда же:

Rationale: This design allows the CLI to choose a platform-specific high-performance representation for floating-point numbers until they are placed in storage locations. For example, it might be able to leave floating-point variables in hardware registers that provide more precision than a user has requested. At the same time, CIL generators can force operations to respect language-specific rules for representations through the use of conversion instructions.

и

Typical problems: It’s a language choice

Note that with current spec, it’s still a language choice to give ‘predictability’. The language may insert conv.r4 or conv.r8 instructions after every FP operation to get a ‘predictable’ behavior. Obviously, this is really expensive, and different languages have different compromises. C#, for example, does nothing, if you want narrowing, you will have to insert (float) and (double) casts by hand


Ну, или примечание к double.Epsilon:

Platform Notes

On ARM systems, the value of the Epsilon constant is too small to be detected, so it equates to zero. You can define an alternative epsilon value that equals 2.2250738585072014E-308 instead.


как-то так, да.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.