Как ограничить количество знаков после запятой в c windows forms

Есть ли опция у TextBox позволяющая напрямую ограничить количество знаков после запятой у его содержимого, не округляя постоянно значения, которые будут на него выводиться? Т.е. например на загрузке

Не надо ничего округлять, особенно если эти данные используются в дальнейших вычислениях, у дробных типов и так хватает проблем с представлением десятичных дробных чисел. Decimal проблем с представлением не имеет, он вроде как десятичный, но у него сильно ограничен диапазон значений (даже в сравнении с float) и он не используется математическими функциями, поэтому вместо округлений, появляются постоянные приведения.

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

double val = 01234.56789;
textBox1.Text = val.ToString("F2");//1234,57

Будет выведена целая часть целиком и ровно 2 знака после запятой. Осторожно с большими степенями, 1e100.ToString("F2"), например, сформирует строку длиной 103 знака (100 на целую часть + разделитель дробной части + 2 знака дробной части). В случае больших степеней лучше подобрать другой формат, подробное описание возможных форматов тут и тут. Формат указывается обычной строкой, поэтому вы можете формировать ее динамически, например меняя точность вывода по необходимости.

Для ввода чисел, см. соседний ответ.

NSluggard

0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

1

24.12.2014, 18:26. Показов 17844. Ответов 28

Метки нет (Все метки)


Друзья, помогите допилить обработчик, мне нужно ограничить ввод в textbox до двух знаков после запятой, как сюда запихнуть сравнение типа «если длина после запятой больше», голову уже сломал

C#
1
2
3
4
5
6
7
8
9
10
private void textBox6_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!(Char.IsDigit(e.KeyChar)) && !((e.KeyChar == '.' || e.KeyChar == ',') && (textBox6.Text.IndexOf(".") == -1) && (textBox6.Text.IndexOf(",") == -1) && (textBox6.Text.Length != 0)))
            {
                if (e.KeyChar != (char)Keys.Back)
                {
                    e.Handled = true;
                }
            }            
        }

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



zna926

546 / 477 / 315

Регистрация: 24.09.2013

Сообщений: 3,345

Записей в блоге: 1

25.12.2014, 15:56

2

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Forms Code
 
{    public partial class Form1 : Form
    {
      public int l, n;
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            n = textBox1.Text.IndexOf(",");
 
            if (n > 0 && textBox1.Text.Length > n + 3)
            {
                textBox1.Text = textBox1.Text.Substring(0, n + 3);
                l = textBox1.Text.Length;
            }
            else if (n > 0 && textBox1.Text.Length == l+1)
               textBox1.Text = textBox1.Text.Substring(1, l);
        }
    }
 
}



1



0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

25.12.2014, 16:29

 [ТС]

3

Спасибо, у меня сейчас подобная конструкция используется.
Хочется чего-то более оптимизированного.



0



tezaurismosis

Администратор

Эксперт .NET

9356 / 4638 / 755

Регистрация: 17.04.2012

Сообщений: 9,490

Записей в блоге: 14

25.12.2014, 17:04

4

C#
1
2
3
4
string part = textBox1.Text.Split(',')[1];
if (part.Length >= 2) {
    e.Handled = true;
}



1



0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

25.12.2014, 18:04

 [ТС]

5

tezaurismosis, спасибо за наводку! к сожалению, тоже не вариант, нужно условие писать на проверку точки в textBox, после заполнении двух знаков после ноля, конечное число не редактируется



0



Just_Neshta

4 / 4 / 0

Регистрация: 10.11.2014

Сообщений: 27

25.12.2014, 21:02

6

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

C#
1
Regex check_ip = new Regex(@"^d{1,3}.d{1,3}.d{1,3}.d{1,3}$");

Для проверки IP.
Попробуй поизменяй, может выйдет что-нибудь толковое.



0



NSluggard

0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

25.12.2014, 21:53

 [ТС]

7

Just_Neshta, спасибо за совет (почему-то плюсик/спасибо поставить вам не получается?!), регулярные выражения пробовал,
я «возлагал» на них надежду при решении задачи разделения числа на разряды (триады), ну и за одно ограничение ввода до двух знаков после запятой, промучившись какое-то время с ними, я понял, что для этой задачи проще цикл написать. Пользователя ориентировать не получилось:

C#
1
2
textBox3.Text = String.Format("{0:#,##0.00;(#,##0.00);Zero}", Convert.ToDouble(textBox3.Text));
textBox3.Select(textBox3.Text.Length, 0); //курсор в конец текстбокса

курсор съезжает, textBox1.Select(textBox1.Text.Length, 0); возвращает курсор, но в конец дробной части, при вводе числа, сразу дробная часть появляется, к тому же, почему-то при вводе 18 символа в основной части вместо вводимых чисел подставляются ноли. В общем, решение простое в одну строчку (если закрыть глаза на проверку пустого значения в боксе), но похоже не рабочее или у меня фантазия кончилась.



0



2146 / 1283 / 516

Регистрация: 04.03.2014

Сообщений: 4,092

26.12.2014, 14:02

8

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



0



BozKurt

306 / 283 / 102

Регистрация: 06.05.2014

Сообщений: 861

26.12.2014, 14:37

9

Лучший ответ Сообщение было отмечено NSluggard как решение

Решение

Не по теме:

Metall_Version, ну вот хочется человеку, почему бы и нет? :)

По делу…
Double — это тип который предоставляет точность только для пятнадцати знаков, что в целой, что в дробной части — поэтому и видишь нули при вводе большего числа цифр.
Если в TextBox‘е кроме одного числа вводиться больше ничего не предполагается, то вот такой код на событие TextChanged решит проблему:

C#
1
2
textBox1.Text = Regex.Replace(textBox1.Text, @"(d+(,|.)d{2})d+", @"$1");
textBox1.Select(textBox1.Text.Length, 0);

В качестве разделителя можно использовать как запятую, так и точку. Проверки на то, что это именно дробное число, а не что-то другое (например: blabla123,456blabla) — нет. Но это легко решается при помощи Double.TryParse (с опять же таки, ограничением точности в 15 знаков).



1



0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

26.12.2014, 14:58

 [ТС]

10

BozKurt, спасибо за совет! но почему-то у меня ничего не происходит, пошел курить маску «(d+(,|.)d{2})d+», @»$1″
оОо вот это я тупанул с double — спасибо, что обратили внимание



0



306 / 283 / 102

Регистрация: 06.05.2014

Сообщений: 861

26.12.2014, 15:01

11

Цитата
Сообщение от NSluggard
Посмотреть сообщение

у меня ничего не происходит

TextBox — точно под индексом «один»? Просто я на форму только один бросал, у тебя по всей видимости textBox6.



0



NSluggard

0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

26.12.2014, 15:10

 [ТС]

12

Metall_Version, numericUpDown — количество знаков там конечно выставляется, но вписать самостоятельно можно любое количество

Добавлено через 2 минуты
BozKurt, четвертый

C#
1
2
3
4
5
6
7
using System.Text.RegularExpressions;
//...
private void textBox4_TextChanged(object sender, EventArgs e)
        {
            textBox4.Text = Regex.Replace(textBox4.Text, @"(d+,d{2})d+", @"$1");
            textBox4.Select(textBox4.Text.Length, 0);
        }



0



306 / 283 / 102

Регистрация: 06.05.2014

Сообщений: 861

26.12.2014, 15:12

13

И не работает?
P.S. Я поправил паттрен чуть позже, добавив возможность использовать ещё и точку в качестве разделителя, для такого доступна только запятая.



1



0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

26.12.2014, 15:13

 [ТС]

14

BozKurt, разобрался — заработало!!! СПАСИБО!!!
в маску разделение на разряды не поможете добавить?



0



BozKurt

306 / 283 / 102

Регистрация: 06.05.2014

Сообщений: 861

26.12.2014, 16:50

15

Лучший ответ Сообщение было отмечено NSluggard как решение

Решение

NSluggard, разделение на разряды с помощью регулярок, да ещё и в одной — вероятно невозможная задача.
Вот такой вариант только в голову пришёл:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void TextBox_TextChanged(object sender, EventArgs e)
{
    TextBox textBox = (TextBox)sender;
    StringBuilder number = new StringBuilder(textBox.Text);
    string result = textBox.Text;
    if (number.Length > 3 && !result.Contains('.') && !result.Contains(','))
    {
        number = number.Replace(" ", String.Empty);
        int indexPoint = result.LastIndexOfAny(new char[] { ',', '.' });
        for (int i = (indexPoint < 0 ? number.Length - 1 : indexPoint - 1); i >= 3; i -= 3)
            number.Insert(i - 2, ' ');
        result = number.ToString();
    }
    else
        result = Regex.Replace(number.ToString(), @"([d ]+(,|.)d{2})d+", @"$1");
    textBox.Text = result;
    textBox.Select(textBox.Text.Length, 0);
}

Универсальное событие. Вешаешь на TextChanged нужных тебе TextBox‘ов и они все будет вести себя одинаково (ничего заменять не нужно).



2



0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

26.12.2014, 17:55

 [ТС]

16

BozKurt, ничего себе, wow — вот это круто!
Вы только что решили мою третью проблему — универсальность для кучи текстбоксов, это же все можно в один класс загнать, а в обработчике только ссылку разместить!) Вот это дух — рождества, вот это — ООП! Готов проставится!
Подскажите пожалуйста, как подобный прием (не явное обращение к объекту) называется? Очень хочется почитать в этом направлении, эта тема заинтриговала меня давно, но моих «познаний» (в особенности «как создается объект» в ООП) хватило только на то, что бы не задавать этот «идиотский» вопрос ответ на который, как я полагал, «никак».



0



306 / 283 / 102

Регистрация: 06.05.2014

Сообщений: 861

26.12.2014, 18:23

17

В любое событие Windows Form первым аргументом передаётся сам объект который это самое событие вызвал, он приводится к типу object. Чтобы не задумываться о том, как именно называется объект, который вызвал событие, можно привести первый аргумент (sender) к нужному тебе типу, в данном случае это TextBox и я точно знаю, что ничего другое это событие не вызовет, т.е. если это событие подключить к любому другому контролу отличному от TextBox, даже к RichTextBox‘у, его выполнение вызовет исключение уже в первой строке. Поэтому ничего «неявного» в приведённом коде нет, object приводится к конкретному типу, который программист точно должен знать.



1



0 / 0 / 0

Регистрация: 24.12.2014

Сообщений: 9

26.12.2014, 19:59

 [ТС]

18

Огромное спасибо за разъяснение!



0



BozKurt

26.12.2014, 20:03



0



pirat2k

11 / 11 / 8

Регистрация: 18.09.2012

Сообщений: 514

25.09.2015, 11:35

20

вот мое решение. тут не только ввод после запятой

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  //конвертирование запятой в точку
            {
                if (e.KeyChar == ',')
                {
                    e.KeyChar = '.';
                }
                
                         //ограничение ввода
                if (e.KeyChar < '0' | e.KeyChar > '9' && e.KeyChar != (char)Keys.Back && e.KeyChar != '.')
                {
 
                    e.Handled = true;
                }
                //запрет ввода разделителя первым символом.
                if (textBox4.SelectionStart == 0 & e.KeyChar == '.')
                {
                    e.Handled = true;
                }
                //запрет таких значений как 03.xx 09999.xx После 0 должа быть точка. 
                if (textBox4.Text == "0") 
                {
                    if (e.KeyChar!='.'& e.KeyChar!=(char)Keys.Back)
                    {
                        e.Handled = true;
                    }
                }
                   //запрет на ввод таких значений как 97.980, 0.33333. Должно быть 8.30, 5.09
                if (textBox4.Text.IndexOf('.') > 0)
                {
                    if (textBox4.Text.Substring(textBox4.Text.IndexOf('.')).Length > 2)
                    {
                        if (e.KeyChar != (char)Keys.Back)
                        {
                            e.Handled = true;
                        }
                    }
                }
               //Ввод только 1 разделителя
                if (e.KeyChar == '.')
                {
                    if (textBox4.Text.IndexOf('.') != -1)
                    {
                        e.Handled = true;
                    }
 
                }
 
            }
 
 
        }
    }
}



3



IT_Exp

Эксперт

87844 / 49110 / 22898

Регистрация: 17.06.2006

Сообщений: 92,604

25.09.2015, 11:35

20

This i what I am trying to achieve:

If a double has more than 3 decimal places, I want to truncate any decimal places beyond the third. (do not round.)

Eg.: 12.878999 -> 12.878

If a double has less than 3 decimals, leave unchanged

Eg.:   125   -> 125
       89.24 -> 89.24

I came across this command:

double example = 12.34567;
double output = Math.Round(example, 3);

But I do not want to round. According to the command posted above,
12.34567 -> 12.346

I want to truncate the value so that it becomes: 12.345

Blake Yarbrough's user avatar

asked Sep 28, 2010 at 15:09

Ayush's user avatar

1

Doubles don’t have decimal places — they’re not based on decimal digits to start with. You could get «the closest double to the current value when truncated to three decimal digits», but it still wouldn’t be exactly the same. You’d be better off using decimal.

Having said that, if it’s only the way that rounding happens that’s a problem, you can use Math.Truncate(value * 1000) / 1000; which may do what you want. (You don’t want rounding at all, by the sounds of it.) It’s still potentially «dodgy» though, as the result still won’t really just have three decimal places. If you did the same thing with a decimal value, however, it would work:

decimal m = 12.878999m;
m = Math.Truncate(m * 1000m) / 1000m;
Console.WriteLine(m); // 12.878

EDIT: As LBushkin pointed out, you should be clear between truncating for display purposes (which can usually be done in a format specifier) and truncating for further calculations (in which case the above should work).

answered Sep 28, 2010 at 15:12

Jon Skeet's user avatar

Jon SkeetJon Skeet

1.4m851 gold badges9044 silver badges9133 bronze badges

10

I can’t think of a reason to explicitly lose precision outside of display purposes. In that case, simply use string formatting.

double example = 12.34567;

Console.Out.WriteLine(example.ToString("#.000"));

answered Sep 28, 2010 at 15:20

Chris Martin's user avatar

Chris MartinChris Martin

1,85116 silver badges17 bronze badges

3

double example = 3.1416789645;
double output = Convert.ToDouble(example.ToString("N3"));

answered Feb 5, 2014 at 20:31

Merin Nakarmi's user avatar

Merin NakarmiMerin Nakarmi

3,0783 gold badges31 silver badges40 bronze badges

1

Multiply by 1000 then use Truncate then divide by 1000.

answered Sep 28, 2010 at 15:12

Josh's user avatar

JoshJosh

67.3k14 gold badges140 silver badges154 bronze badges

1

If your purpose in truncating the digits is for display reasons, then you just just use an appropriate formatting when you convert the double to a string.

Methods like String.Format() and Console.WriteLine() (and others) allow you to limit the number of digits of precision a value is formatted with.

Attempting to «truncate» floating point numbers is ill advised — floating point numbers don’t have a precise decimal representation in many cases. Applying an approach like scaling the number up, truncating it, and then scaling it down could easily change the value to something quite different from what you’d expected for the «truncated» value.

If you need precise decimal representations of a number you should be using decimal rather than double or float.

answered Sep 28, 2010 at 15:17

LBushkin's user avatar

LBushkinLBushkin

128k32 gold badges213 silver badges261 bronze badges

0

You can use:

double example = 12.34567;
double output = ( (double) ( (int) (example * 1000.0) ) ) / 1000.0 ;

answered Sep 28, 2010 at 15:15

Pablo Santa Cruz's user avatar

Pablo Santa CruzPablo Santa Cruz

174k32 gold badges239 silver badges291 bronze badges

1

Good answers above- if you’re looking for something reusable here is the code. Note that you might want to check the decimal places value, and this may overflow.

public static decimal TruncateToDecimalPlace(this decimal numberToTruncate, int decimalPlaces)
{
    decimal power = (decimal)(Math.Pow(10.0, (double)decimalPlaces));

    return Math.Truncate((power * numberToTruncate)) / power;
}

answered Sep 28, 2010 at 15:26

Mike M.'s user avatar

Mike M.Mike M.

12.1k1 gold badge22 silver badges28 bronze badges

In C lang:

double truncKeepDecimalPlaces(double value, int numDecimals)
{
    int x = pow(10, numDecimals);
    return (double)trunc(value * x) / x;
}

answered Mar 6, 2014 at 12:11

alsantos123's user avatar

This i what I am trying to achieve:

If a double has more than 3 decimal places, I want to truncate any decimal places beyond the third. (do not round.)

Eg.: 12.878999 -> 12.878

If a double has less than 3 decimals, leave unchanged

Eg.:   125   -> 125
       89.24 -> 89.24

I came across this command:

double example = 12.34567;
double output = Math.Round(example, 3);

But I do not want to round. According to the command posted above,
12.34567 -> 12.346

I want to truncate the value so that it becomes: 12.345

Blake Yarbrough's user avatar

asked Sep 28, 2010 at 15:09

Ayush's user avatar

1

Doubles don’t have decimal places — they’re not based on decimal digits to start with. You could get «the closest double to the current value when truncated to three decimal digits», but it still wouldn’t be exactly the same. You’d be better off using decimal.

Having said that, if it’s only the way that rounding happens that’s a problem, you can use Math.Truncate(value * 1000) / 1000; which may do what you want. (You don’t want rounding at all, by the sounds of it.) It’s still potentially «dodgy» though, as the result still won’t really just have three decimal places. If you did the same thing with a decimal value, however, it would work:

decimal m = 12.878999m;
m = Math.Truncate(m * 1000m) / 1000m;
Console.WriteLine(m); // 12.878

EDIT: As LBushkin pointed out, you should be clear between truncating for display purposes (which can usually be done in a format specifier) and truncating for further calculations (in which case the above should work).

answered Sep 28, 2010 at 15:12

Jon Skeet's user avatar

Jon SkeetJon Skeet

1.4m851 gold badges9044 silver badges9133 bronze badges

10

I can’t think of a reason to explicitly lose precision outside of display purposes. In that case, simply use string formatting.

double example = 12.34567;

Console.Out.WriteLine(example.ToString("#.000"));

answered Sep 28, 2010 at 15:20

Chris Martin's user avatar

Chris MartinChris Martin

1,85116 silver badges17 bronze badges

3

double example = 3.1416789645;
double output = Convert.ToDouble(example.ToString("N3"));

answered Feb 5, 2014 at 20:31

Merin Nakarmi's user avatar

Merin NakarmiMerin Nakarmi

3,0783 gold badges31 silver badges40 bronze badges

1

Multiply by 1000 then use Truncate then divide by 1000.

answered Sep 28, 2010 at 15:12

Josh's user avatar

JoshJosh

67.3k14 gold badges140 silver badges154 bronze badges

1

If your purpose in truncating the digits is for display reasons, then you just just use an appropriate formatting when you convert the double to a string.

Methods like String.Format() and Console.WriteLine() (and others) allow you to limit the number of digits of precision a value is formatted with.

Attempting to «truncate» floating point numbers is ill advised — floating point numbers don’t have a precise decimal representation in many cases. Applying an approach like scaling the number up, truncating it, and then scaling it down could easily change the value to something quite different from what you’d expected for the «truncated» value.

If you need precise decimal representations of a number you should be using decimal rather than double or float.

answered Sep 28, 2010 at 15:17

LBushkin's user avatar

LBushkinLBushkin

128k32 gold badges213 silver badges261 bronze badges

0

You can use:

double example = 12.34567;
double output = ( (double) ( (int) (example * 1000.0) ) ) / 1000.0 ;

answered Sep 28, 2010 at 15:15

Pablo Santa Cruz's user avatar

Pablo Santa CruzPablo Santa Cruz

174k32 gold badges239 silver badges291 bronze badges

1

Good answers above- if you’re looking for something reusable here is the code. Note that you might want to check the decimal places value, and this may overflow.

public static decimal TruncateToDecimalPlace(this decimal numberToTruncate, int decimalPlaces)
{
    decimal power = (decimal)(Math.Pow(10.0, (double)decimalPlaces));

    return Math.Truncate((power * numberToTruncate)) / power;
}

answered Sep 28, 2010 at 15:26

Mike M.'s user avatar

Mike M.Mike M.

12.1k1 gold badge22 silver badges28 bronze badges

In C lang:

double truncKeepDecimalPlaces(double value, int numDecimals)
{
    int x = pow(10, numDecimals);
    return (double)trunc(value * x) / x;
}

answered Mar 6, 2014 at 12:11

alsantos123's user avatar

NovichekTyrnira

gvadelypa

@NovichekTyrnira

Начинающий гений C# индустрии

Как мне вывести b И c не округляя их?
В коде ниже переменные b и с округляются

using System;
using System.IO;
class Program
{
    static void Main()
    {
        double x = Convert.ToDouble(Console.ReadLine());
        double b = Math.PI * Math.Sqrt(x);
        double c = 2 * Math.PI * x;
        Console.WriteLine("{0: 0.000}",b);
        Console.WriteLine("{0: 0.000}",c);
    }
}


  • Вопрос задан

    более двух лет назад

  • 18813 просмотров

Судя по всему, стандартными средствами форматирования никак

Можно так

Console.WriteLine("{0:f4}", Math.Truncate(10000 * Math.PI) / 10000);

Можно так

Console.WriteLine(TruncateWithPrecision(Math.PI , 4));

static string TruncateWithPrecision(double value, int precision)
{
    if (precision < 0) throw new ArgumentOutOfRangeException(nameof(precision));

    var prepared = string.Format($"{{0:f{precision + 1}}}", value);
    return prepared.Substring(0, prepared.Length - (precision == 0 ? 2 : 1));
}

Пригласить эксперта

Здрасьте, я ещё совсем новичок в сфере программирования, но хотелось бы оставить свой ответ на случай, если у такого же неопытного юнца, как я, возникнет какая-то схожая проблема
Перед выводом, после всех исчислений, можно воспользоваться таким методом:
double c = Math.Round(c, 2);


  • Показать ещё
    Загружается…

06 февр. 2023, в 12:36

20000 руб./за проект

06 февр. 2023, в 12:35

7000 руб./за проект

06 февр. 2023, в 12:30

8000 руб./за проект

Минуточку внимания

Always use the printf family of functions for this. Even if you want to get the value as a float, you’re best off using snprintf to get the rounded value as a string and then parsing it back with atof:

#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

double dround(double val, int dp) {
    int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", dp, val);
    char *buffer = malloc(charsNeeded);
    snprintf(buffer, charsNeeded, "%.*f", dp, val);
    double result = atof(buffer);
    free(buffer);
    return result;
}

I say this because the approach shown by the currently top-voted answer and several others here —
multiplying by 100, rounding to the nearest integer, and then dividing by 100 again — is flawed in two ways:

  • For some values, it will round in the wrong direction because the multiplication by 100 changes the decimal digit determining the rounding direction from a 4 to a 5 or vice versa, due to the imprecision of floating point numbers
  • For some values, multiplying and then dividing by 100 doesn’t round-trip, meaning that even if no rounding takes place the end result will be wrong

To illustrate the first kind of error — the rounding direction sometimes being wrong — try running this program:

int main(void) {
    // This number is EXACTLY representable as a double
    double x = 0.01499999999999999944488848768742172978818416595458984375;

    printf("x: %.50fn", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.50fn", res1);
    printf("Rounded with round, then divided: %.50fn", res2);
}

You’ll see this output:

x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406

Note that the value we started with was less than 0.015, and so the mathematically correct answer when rounding it to 2 decimal places is 0.01. Of course, 0.01 is not exactly representable as a double, but we expect our result to be the double nearest to 0.01. Using snprintf gives us that result, but using round(100 * x) / 100 gives us 0.02, which is wrong. Why? Because 100 * x gives us exactly 1.5 as the result. Multiplying by 100 thus changes the correct direction to round in.

To illustrate the second kind of error — the result sometimes being wrong due to * 100 and / 100 not truly being inverses of each other — we can do a similar exercise with a very big number:

int main(void) {
    double x = 8631192423766613.0;

    printf("x: %.1fn", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.1fn", res1);
    printf("Rounded with round, then divided: %.1fn", res2);
}

Our number now doesn’t even have a fractional part; it’s an integer value, just stored with type double. So the result after rounding it should be the same number we started with, right?

If you run the program above, you’ll see:

x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0

Oops. Our snprintf method returns the right result again, but the multiply-then-round-then-divide approach fails. That’s because the mathematically correct value of 8631192423766613.0 * 100, 863119242376661300.0, is not exactly representable as a double; the closest value is 863119242376661248.0. When you divide that back by 100, you get 8631192423766612.0 — a different number to the one you started with.

Hopefully that’s a sufficient demonstration that using roundf for rounding to a number of decimal places is broken, and that you should use snprintf instead. If that feels like a horrible hack to you, perhaps you’ll be reassured by the knowledge that it’s basically what CPython does.

Always use the printf family of functions for this. Even if you want to get the value as a float, you’re best off using snprintf to get the rounded value as a string and then parsing it back with atof:

#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

double dround(double val, int dp) {
    int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", dp, val);
    char *buffer = malloc(charsNeeded);
    snprintf(buffer, charsNeeded, "%.*f", dp, val);
    double result = atof(buffer);
    free(buffer);
    return result;
}

I say this because the approach shown by the currently top-voted answer and several others here —
multiplying by 100, rounding to the nearest integer, and then dividing by 100 again — is flawed in two ways:

  • For some values, it will round in the wrong direction because the multiplication by 100 changes the decimal digit determining the rounding direction from a 4 to a 5 or vice versa, due to the imprecision of floating point numbers
  • For some values, multiplying and then dividing by 100 doesn’t round-trip, meaning that even if no rounding takes place the end result will be wrong

To illustrate the first kind of error — the rounding direction sometimes being wrong — try running this program:

int main(void) {
    // This number is EXACTLY representable as a double
    double x = 0.01499999999999999944488848768742172978818416595458984375;

    printf("x: %.50fn", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.50fn", res1);
    printf("Rounded with round, then divided: %.50fn", res2);
}

You’ll see this output:

x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406

Note that the value we started with was less than 0.015, and so the mathematically correct answer when rounding it to 2 decimal places is 0.01. Of course, 0.01 is not exactly representable as a double, but we expect our result to be the double nearest to 0.01. Using snprintf gives us that result, but using round(100 * x) / 100 gives us 0.02, which is wrong. Why? Because 100 * x gives us exactly 1.5 as the result. Multiplying by 100 thus changes the correct direction to round in.

To illustrate the second kind of error — the result sometimes being wrong due to * 100 and / 100 not truly being inverses of each other — we can do a similar exercise with a very big number:

int main(void) {
    double x = 8631192423766613.0;

    printf("x: %.1fn", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.1fn", res1);
    printf("Rounded with round, then divided: %.1fn", res2);
}

Our number now doesn’t even have a fractional part; it’s an integer value, just stored with type double. So the result after rounding it should be the same number we started with, right?

If you run the program above, you’ll see:

x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0

Oops. Our snprintf method returns the right result again, but the multiply-then-round-then-divide approach fails. That’s because the mathematically correct value of 8631192423766613.0 * 100, 863119242376661300.0, is not exactly representable as a double; the closest value is 863119242376661248.0. When you divide that back by 100, you get 8631192423766612.0 — a different number to the one you started with.

Hopefully that’s a sufficient demonstration that using roundf for rounding to a number of decimal places is broken, and that you should use snprintf instead. If that feels like a horrible hack to you, perhaps you’ll be reassured by the knowledge that it’s basically what CPython does.

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

Следующая функция возвращает true, если входное значения является действительным, в противном случае она возвращает значение false:

class ValidAmount
{
	const int KEYCODE_FOR_DOT = 190;
	public static bool IsValidMoneyInput(String previousInput, String key, int keyCode)
	{
		if (!String.IsNullOrWhiteSpace(previousInput))
		{
			if (previousInput.Contains("."))
			{
				if (keyCode == KEYCODE_FOR_DOT)
				{
					return false;
				}
				else
				{
					String[] strings = previousInput.Split('.');
					if (strings[1].Length > 1)
					{
						return false;
					}
				}
			}
		}
		return true;
	}
}

Для использования данной функции, вам нужно навесить обработчик на событие Key_Down для текстового поля (TextBox) и поместить в него следующий код:

private void Amounnt_OnKeyDown(object sender, KeyEventArgs e)
{
	e.Handled = !ValidAmount.IsValidMoneyInput(AmountToSend.Text, e.Key.ToString(), e.PlatformKeyCode);
}

Оригинал

Теги: перевод, c#

Редактировать

VK Facebook Twitter Telegram Email

Понравилась статья? Поделить с друзьями:
  • Как определить ssd при установке windows
  • Как определить raid массив в windows
  • Как ограничить исходящий трафик в windows 10
  • Как ограничить использование цп программами на windows 10
  • Как ограничить использование интернета на компьютере windows