float/double Math.Round в С#

float ff = (float)31.15;

double dd = 31.15;

var frst = Math.Round(ff, 1, MidpointRounding.AwayFromZero);

var drst = Math.Round(dd, 1, MidpointRounding.AwayFromZero);

первое: 31,1

дрст: 31,2

Может кто-нибудь объяснить почему?

Ответ 1

Ну, Math.Round хочет double, а не float, вот почему

Math.Round(ff, 1, MidpointRounding.AwayFromZero);

равно

Math.Round((double)ff, 1, MidpointRounding.AwayFromZero);

и если мы проверяем (double)ff значение (double)ff

Console.Write(((double)ff).ToString("R"));

мы увидим ошибки в действии

31.149999618530273

Наконец, Math.Round(31.149999618530273, 1, MidpointRounding.AwayFromZero) == 31.1 как и ожидалось

Ответ 2

В плавающей запятой все числа представляются внутри как дроби, где знаменатель является степенью 2.

(Это похоже на то, как десятичные дроби - это на самом деле дроби со знаменателями степени 10. Таким образом, 31.15 - это просто способ записи дроби 3115/100)

В плавающей точке 31.15 должен быть представлен внутри как двоичное число. Ближайшая двоичная дробь: 1111.1001001100110011001100110011001100110011001100110011001100...repeating

1100 повторяется (повторяется вечно), и поэтому число будет усечено в зависимости от того, хранится ли оно в двойном или числовом выражении. В формате с плавающей точкой оно усекается до 24 цифр, а в двойном - до 53.

Exact:  1111.100100110011001100110011001100110011001100110011001100110011001100...forever
Float:  1111.10010011001100110011
Double: 1111.1001001100110011001100110011001100110011001100110

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

Ответ 3

Потому что (float)31.15 не равно (double)31.15. Арифметика с плавающей точкой почти всегда приводит к ошибкам округления. В частности, округление двойных работает иначе, чем округление поплавка.

В вашем примере 31.15 может быть представлен как 31.144448 при преобразовании в число с float, но 31.1500000000001 при преобразовании в значение типа double.

При округлении первого вы получаете 31.1, а второе - 31.2.

Ответ 4

У вас есть свой ответ здесь !

Перед тем как опубликовать вопрос, вы должны проверить форум, чтобы увидеть, если у вас уже есть ответ на ваш вопрос!

Ответ 5

Уже объяснено, почему у нас есть такая проблема с округлением, но стоит упомянуть, что с этим небольшим приемом вы можете избавиться от него:

float ff = (float)31.15;

double dd = 31.15;

var frst = Math.Round(
    double.Parse(ff.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture),
    1, 
    MidpointRounding.AwayFromZero);

var drst = Math.Round(dd, 1, MidpointRounding.AwayFromZero);

Тогда результат должен быть таким же:

3.2
3.2

поэтому, пока (double)ff double.Parse(ff.ToString()) проблему округления, double.Parse(ff.ToString()) не делает этого, потому что преобразование из float в double избегается.