Вызов ToString()
с неточными числами с плавающей запятой дает числа, которые человек мог бы ожидать (например, 4.999999 ... печатается как 5
). Однако при приведении к типу int дробная часть отбрасывается, и результатом является целое число, уменьшенное на 1.
float num = 5f;
num *= 0.01f;
num *= 100f;
num.ToString().Dump(); // produces "5", as expected
((int)num).ToString().Dump(); // produces "4"
Как мне преобразовать float в int, чтобы получить удобное для человека значение, которое производит float.ToString()
?
Я использую этот грязный прием:
int ToInt(float value) {
return (int)(value + Math.Sign(value) * 0.00001);
}
..но, безусловно, должно быть более элегантное решение.
Обновлено: мне хорошо известны причины, по которым поплавки усекаются так, как они есть (4,999 ... до 4 и т.д.). Вопрос заключается в приведении к int
при имитации поведения System.Single.ToString () по умолчанию.
Чтобы лучше проиллюстрировать поведение, которое я ищу:
-4.99f следует преобразовать в -4
4.01f следует преобразовать в 4
4.99f следует преобразовать в 4
4.999999f следует преобразовать в 4
4.9999993f следует преобразовать в 5
Это то же самое поведение, что и у ToString
.
Попробуйте запустить это:
float almost5 = 4.9999993f;
Console.WriteLine(almost5); // "5"
Console.WriteLine((int)almost5); // "4"
Если число с плавающей запятой составляет 4,9375, что вы хотите напечатать: «4» или «5»? Или этого никогда не произойдет, потому что число с плавающей запятой никогда не будет настолько далеким от целого? Насколько далеко оно может быть от целого числа? В какой момент вы хотите, чтобы результат изменился с «5» на «4»?
@FindOutIslamNow: Как вы думаете, OP хочет, чтобы значение чуть больше 5 отображалось как «6»?
Я думаю, вы ищете (int) Math.Round(num)
Используйте Math.Round --- (int)Math.Round(num)).ToString()
. Функция Math.Round округляет значение с плавающей запятой до ближайшего целого числа и округляет средние значения до ближайшего четного числа.
Возможный дубликат Почему арифметика с плавающей запятой в C# неточна?
Я отредактировал вопрос и добавил примеры поведения, которое ищу. Вопрос не в неточности поплавков. Речь идет о том, чтобы изящно бороться с неточностями, как это делает Single.ToString. Я не ищу стандартный Math.Round ().
Итак, вы хотите округлить 4.xxxxxxxxxxxx до ближайшего 0,0000001. Как насчет 49.999993f
, который должен быть 50,00000 или 49,99999?
Console.WriteLine(49.999993f);
печатает 49.99999
. Я не уверен, где находится порог, и это действительно суть вопроса. Как ToString()
выполняет преобразование? Обновлено: я пробовал Single.Epsilon, но это тоже не то.
@blade Преобразует число с плавающей запятой в строку, а затем в целое число, которое игнорирует дробь.
@chux Это определенно сработает, но это тоже очень хакерское решение.
@blade OK, тогда назовите это ссылочной функцией и найдите способы ее улучшить. По крайней мере, ваша цель ясна и недвусмысленна. С almost5 = 4.9999993f
вы испытываете двойное округление. 4.9999993f
не является одним из 2 ^ 32 различных представимых float
. Двумя ближайшими являются 4,9999990463 ... и 4,9999995232 ... с 4,9999995232 ... ближе (1-й раунд). Итак, вы действительно делаете almost5 = 4.9999995232f
, а WriteLine()
, по-видимому, округляет до 7 цифр, то есть 5. (2-й раунд)
@chux Вы уверены, что округлите до 7 знаков после запятой? При печати двойного 4.9999999999
он не округляется. Или 7-значное округление применяется только к числам с плавающей запятой? Если да, то это могло быть решением, хотя я не уверен, насколько хорошо он справляется с неточностями: (int)Math.Round(value * 1000000) / 1000000
?
@blade Я не уверен в деталях C#. Напомним, printing a double 4.9999999999
действительно пытается напечатать 4.99999999989999999173 ..., а не 4.9999999999. (int)Math.Round(...)
не работает для float()
вне диапазона int
.
@chux Это хороший момент. Спасибо за вводные данные, я думаю, что буду придерживаться своего исходного решения, установив значение допустимой погрешности равным 0,000001f. Это работает для каждого поплавка, который я бросаю в него. Разбор строки также будет работать, но он значительно медленнее.
Есть 2 ^ 32 различных float
- вероятно, намного больше, чем «каждый поплавок, который я бросаю в него». Вы можете протестировать каждый float с вашим быстрым решением против медленного подхода «Разбор строки», чтобы проверить его правильность.
0.05f * 100
не совсем 5 из-за округления с плавающей запятой (на самом деле это значение 4,999998 .... при выражении в виде числа с плавающей запятой
Ответ заключается в том, что в случае (int)(.05f * 100)
вы берете значение с плавающей запятой 4.999998 и обрезаете его до целого числа, что дает 4
.
Так что используйте Math.Round
. Функция Math.Round округляет значение с плавающей запятой до ближайшего целого числа и округляет средние значения до ближайшего четного числа.
float num = 5f;
num *= 0.01f;
num *= 100f;
Console.WriteLine(num.ToString());
Console.WriteLine(((int)Math.Round(num)).ToString());
Может быть, вы ищете это:
Convert.ToInt32 (с плавающей запятой)
Похоже, это эквивалент Math.Round()
, чего я не ищу. Я прояснил вопрос.
Пока что лучшим решением является решение вопроса.
int ToInt(float value) {
return (int)(value + Math.Sign(value) * 0.000001f);
}
Это эффективно привязывает значение к ближайшему int, если разница достаточно мала (меньше, чем 0.000001
). Однако эта функция отличается от поведения ToString и немного более терпима к неточностям.
Другое решение, предложенное @chux, - использовать ToString и проанализировать строку обратно. Использование Int32.Parse вызывает исключение, когда число имеет десятичную точку (или запятую), поэтому вам нужно сохранить только целую часть строки, и это может вызвать другие проблемы в зависимости от вашего CultureInfo
по умолчанию.
Как насчет
Math.Ceiling
?