В данном уроке мы разработаем с вами калькулятор на C#. Программа будет написана согласно принципам объектно-ориентированного программирования (ООП): будет определен интерфейс для методов, реализующих функционал клавиш математических операций калькулятора, от интерфейса выполним наследование и напишем соответствующий класс и методы. Калькулятор, помимо базовых операций сложения, вычитания, умножения и деления, будет предоставлять возможность выполнять операции: извлечения квадратного корня, извлечения корня произвольной степени, возведения в квадрат, возведения в произвольную степень, вычисления факториала, а также работу с регистром памяти (MRC).
Создание пользовательского интерфейса калькулятора
Создадим в Visual Studio проект на Visual C# Windows Forms. Добавим на форму элемент GroupBox, в который поместим Label. В свойстве Dock, элемента Label, необходимо указать Right, чтобы Label был привязан к правому краю. Связка данных элементов управления будет реализовывать дисплей калькулятора.
Калькулятор также содержит кнопки. Всего их 28 штук. Пользовательский интерфейс представлен на рисунке 1.
Рисунок 1. Интерфейс калькулятора
Кнопка «+/-» меняет знак операнда на противоположный.
Кнопка MRC, а также кнопки M+, M-, M×, M÷, реализуют отдельный регистр памяти калькулятора и команды для управления им. Что такое MRC можно прочитать — здесь.
Программирование калькулятора на C#
Реализация интерфейса класса
Поскольку наш калькулятор будет написан в рамках парадигмы ООП (объектно-ориентированного программирования), то начнем кодирование с описания структуры интерфейса для класса, реализующего математические операции программы.
Добавим в проект класс InterfaceCalc.cs и определим в созданном файле интерфейс InterfaceCalc.
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 |
//интерфейс public interface InterfaceCalc { //а — первый аргумент, b — второй void Put_A(double a); //сохранить а void Clear_A(); double Multiplication(double b); double Division(double b); double Sum(double b); double Subtraction(double b); //вычитание double SqrtX(double b); double DegreeY(double b); double Sqrt(); double Square(); double Factorial(); double MemoryShow(); //показать содержимое регистра памяти void Memory_Clear(); //стереть содержимое регистра памяти //* / + — к регистру памяти void M_Multiplication(double b); void M_Division(double b); void M_Sum(double b); void M_Subtraction(double b); //вычитание } |
Для выполнения математических операций понадобится два операнда: a и b (например, a + b). Операнд a придется хранить в памяти калькулятора, пока пользователь будет вводить второй аргумент операции. Для сохранения числа a объявим прототип метода void Put_A(double a), для очистки — void Clear_A(). Для умножения, деления, сложения и вычитания чисел a и b соответственно понадобятся методы: double Multiplication(double b), double Division(double b), double Sum(double b), double Subtraction(double b). Вычисление корня степени b из a: double SqrtX(double b). Возведение числа a в степень b: double DegreeY(double b). Вычисление квадратного корня: double Sqrt(). Возведение числа a в квадрат: double Square(). Вычисление факториала a!: double Factorial(). Теперь объявления методов для работы с регистром памяти (MRC) калькулятора. Показать содержимое памяти и очистить его: double MemoryShow(), void Memory_Clear(). M×, M÷, M+ и M- к регистру соответственно: void M_Multiplication(double b), void M_Division(double b), void M_Sum(double b), void M_Subtraction(double b).
Создание класса, реализующего интерфейс InterfaceCalc
Теперь добавим в калькулятор класс, который будет реализовывать написанный ранее интерфейс. Для этого в проекте создадим класс Calc : InterfaceCalc. Как вы видите, здесь используется наследование (оператор «двоеточие»). В данном классе напишем реализацию всех методов, требуемых спецификацией нашего интерфейса.
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
public class Calc : InterfaceCalc { private double a = 0; private double memory = 0; public void Put_A(double a) { this.a = a; } public void Clear_A() { a = 0; } public double Multiplication(double b) { return a * b; } public double Division(double b) { return a / b; } public double Sum(double b) { return a + b; } public double Subtraction(double b) //вычитание { return a — b; } public double SqrtX(double b) { return Math.Pow(a, 1 / b); } public double DegreeY(double b) { return Math.Pow(a, b); } public double Sqrt() { return Math.Sqrt(a); } public double Square() { return Math.Pow(a, 2.0); } public double Factorial() { double f = 1; for (int i = 1; i <= a; i++) f *= (double)i; return f; } //показать содержимое регистра мамяти public double MemoryShow() { return memory; } //стереть содержимое регистра мамяти public void Memory_Clear() { memory = 0.0; } //* / + — к регистру памяти public void M_Multiplication(double b) { memory *= b; } public void M_Division(double b) { memory /= b; } public void M_Sum(double b) { memory += b; } public void M_Subtraction(double b) { memory -= b; } } |
Поле private double memory — будет содержать регистр памяти (MRC).
Реализация работы калькулятора
Перейдем к написанию кода в классе Form1.
Объявим два поля:
Это классовая переменная C для класса Calc и целое число k. С помощью k будем считать количество нажатий кнопки MRC, чтобы при первом нажатии выводить значение регистра памяти на экран, а при последующем повторном — стирать значение регистра.
Далее. Нам понадобится ряд методов, которые будут обеспечивать правильное функционирование калькулятора.
Дисплеем в нашем калькуляторе является Label. Его содержимое — это строка текста. Нажатие на цифровые клавиши — это конкатенация текущего значения строки с символом клавиши. Необходимо исключить дублирование нулей в левой части строки (это делают 2-й и 3-й if в коде ниже), а также не допустить написание цифр, если в Label находится знак «бесконечность» (он появляется, например, при делении на ноль; 1-й if). Для решения данных проблем напишем метод CorrectNumber():
private void CorrectNumber() { //если есть знак «бесконечность» — не даёт писать цифры после него if (labelNumber.Text.IndexOf(«∞») != —1) labelNumber.Text = labelNumber.Text.Substring(0, labelNumber.Text.Length — 1); //ситуация: слева ноль, а после него НЕ запятая, тогда ноль можно удалить if (labelNumber.Text[0] == ‘0’ && (labelNumber.Text.IndexOf(«,») != 1)) labelNumber.Text = labelNumber.Text.Remove(0, 1); //аналогично предыдущему, только для отрицательного числа if (labelNumber.Text[0] == ‘-‘) if (labelNumber.Text[1] == ‘0’ && (labelNumber.Text.IndexOf(«,») != 2)) labelNumber.Text = labelNumber.Text.Remove(1, 1); } |
Еще необходим метод, проверяющий: не нажата ли какая-нибудь кнопка калькулятора из математических операций, требующих два операнда. Данная проверка необходима для недопущения нажатия других мат. операций, если какая-либо уже нажата. Код метода CanPress():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
private bool CanPress() { if (!buttonMult.Enabled) return false; if (!buttonDiv.Enabled) return false; if (!buttonPlus.Enabled) return false; if (!buttonMinus.Enabled) return false; if (!buttonSqrtX.Enabled) return false; if (!buttonDegreeY.Enabled) return false; return true; } |
Также, напишем вспомогательный метод FreeButtons(), который снимает нажатия со всех кнопок математических операций калькулятора, требующих два операнда (умножение, деление, сложение, вычитание, вычисление корня произвольной степени и возведение числа в произвольную степень):
private void FreeButtons() { buttonMult.Enabled = true; buttonDiv.Enabled = true; buttonPlus.Enabled = true; buttonMinus.Enabled = true; buttonSqrtX.Enabled = true; buttonDegreeY.Enabled = true; } |
Со вспомогательными методами закончили. Идем дальше.
В конструкторе Form1 создадим экземпляр класса Calc, а также в «дисплей» калькулятора занесем значение ноль.
public Form1() { InitializeComponent(); C = new Calc(); labelNumber.Text = «0»; } |
Теперь перейдем к кодированию обработчиков нажатий кнопок калькулятора. Кнопка «Очистка» (CE):
private void buttonClear_Click(object sender, EventArgs e) { labelNumber.Text = «0»; C.Clear_A(); FreeButtons(); k = 0; } |
В «дисплей» записывается ноль, переменная a стирается, нажатия с кнопок математических операций снимаются и k обнуляется.
Кнопка изменения знака у числа «+/-«:
private void buttonChangeSign_Click(object sender, EventArgs e) { if (labelNumber.Text[0] == ‘-‘) labelNumber.Text = labelNumber.Text.Remove(0, 1); else labelNumber.Text = «-« + labelNumber.Text; } |
Кнопка «запятая» (будет добавлена, если ее еще нет и на «дисплее» нет знака бесконечность):
private void buttonPoint_Click(object sender, EventArgs e) { if ((labelNumber.Text.IndexOf(«,») == —1) && (labelNumber.Text.IndexOf(«∞») == -1)) labelNumber.Text += «,»; } |
Теперь цифровые клавиши 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
private void button0_Click(object sender, EventArgs e) { labelNumber.Text += «0»; CorrectNumber(); } private void button1_Click(object sender, EventArgs e) { labelNumber.Text += «1»; CorrectNumber(); } private void button2_Click(object sender, EventArgs e) { labelNumber.Text += «2»; CorrectNumber(); } private void button3_Click(object sender, EventArgs e) { labelNumber.Text += «3»; CorrectNumber(); } private void button4_Click(object sender, EventArgs e) { labelNumber.Text += «4»; CorrectNumber(); } private void button5_Click(object sender, EventArgs e) { labelNumber.Text += «5»; CorrectNumber(); } private void button6_Click(object sender, EventArgs e) { labelNumber.Text += «6»; CorrectNumber(); } private void button7_Click(object sender, EventArgs e) { labelNumber.Text += «7»; CorrectNumber(); } private void button8_Click(object sender, EventArgs e) { labelNumber.Text += «8»; CorrectNumber(); } private void button9_Click(object sender, EventArgs e) { labelNumber.Text += «9»; CorrectNumber(); } |
Кнопка «Равно»:
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 |
private void buttonCalc_Click(object sender, EventArgs e) { if (!buttonMult.Enabled) labelNumber.Text = C.Multiplication(Convert.ToDouble(labelNumber.Text)).ToString(); if (!buttonDiv.Enabled) labelNumber.Text = C.Division(Convert.ToDouble(labelNumber.Text)).ToString(); if (!buttonPlus.Enabled) labelNumber.Text = C.Sum(Convert.ToDouble(labelNumber.Text)).ToString(); if (!buttonMinus.Enabled) labelNumber.Text = C.Subtraction(Convert.ToDouble(labelNumber.Text)).ToString(); if (!buttonSqrtX.Enabled) labelNumber.Text = C.SqrtX(Convert.ToDouble(labelNumber.Text)).ToString(); if (!buttonDegreeY.Enabled) labelNumber.Text = C.DegreeY(Convert.ToDouble(labelNumber.Text)).ToString(); C.Clear_A(); FreeButtons(); k = 0; } |
В зависимости от того, какая из кнопок математических операций нажата, будет вызван соответствующий метод из класса Calc, вычисляющий данную функцию, и результат выведется в Label.
Далее кнопки математических операций. Сработают только в том случае, если другие в данный момент не нажаты (CanPress() вернет true):
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
//кнопка Умножение private void buttonMult_Click(object sender, EventArgs e) { if(CanPress()) { C.Put_A(Convert.ToDouble(labelNumber.Text)); buttonMult.Enabled = false; labelNumber.Text = «0»; } } //кнопка Деление private void buttonDiv_Click(object sender, EventArgs e) { if (CanPress()) { C.Put_A(Convert.ToDouble(labelNumber.Text)); buttonDiv.Enabled = false; labelNumber.Text = «0»; } } //кнопка Сложение private void buttonPlus_Click(object sender, EventArgs e) { if (CanPress()) { C.Put_A(Convert.ToDouble(labelNumber.Text)); buttonPlus.Enabled = false; labelNumber.Text = «0»; } } //кнопка Вычитание private void buttonMinus_Click(object sender, EventArgs e) { if (CanPress()) { C.Put_A(Convert.ToDouble(labelNumber.Text)); buttonMinus.Enabled = false; labelNumber.Text = «0»; } } //кнопка Корень произвольной степени private void buttonSqrtX_Click(object sender, EventArgs e) { if (CanPress()) { C.Put_A(Convert.ToDouble(labelNumber.Text)); buttonSqrtX.Enabled = false; labelNumber.Text = «0»; } } //кнопка Возведение в произвольную степень private void buttonDegreeY_Click(object sender, EventArgs e) { if (CanPress()) { C.Put_A(Convert.ToDouble(labelNumber.Text)); buttonDegreeY.Enabled = false; labelNumber.Text = «0»; } } //кнопка Корень квадратный private void buttonSqrt_Click(object sender, EventArgs e) { if (CanPress()) { C.Put_A(Convert.ToDouble(labelNumber.Text)); labelNumber.Text = C.Sqrt().ToString(); C.Clear_A(); FreeButtons(); } } //кнопка Квадрат числа private void buttonSquare_Click(object sender, EventArgs e) { if (CanPress()) { C.Put_A(Convert.ToDouble(labelNumber.Text)); labelNumber.Text = C.Square().ToString(); C.Clear_A(); FreeButtons(); } } //кнопка Факториал private void buttonFactorial_Click(object sender, EventArgs e) { if (CanPress()) { if ((Convert.ToDouble(labelNumber.Text) == (int)(Convert.ToDouble(labelNumber.Text))) && ((Convert.ToDouble(labelNumber.Text) >= 0.0))) { C.Put_A(Convert.ToDouble(labelNumber.Text)); labelNumber.Text = C.Factorial().ToString(); C.Clear_A(); FreeButtons(); } else MessageBox.Show(«Число должно быть >= 0 и целым!»); } } |
Калькулятор вычислит факториал только в том случае, если текущее введенное число целое и больше, либо равно нулю.
Кнопки математических операций над регистром памяти (MRC):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//кнопка М+ private void buttonMPlus_Click(object sender, EventArgs e) { C.M_Sum(Convert.ToDouble(labelNumber.Text)); } //кнопка М- private void buttonMMinus_Click(object sender, EventArgs e) { C.M_Subtraction(Convert.ToDouble(labelNumber.Text)); } //кнопка М* private void buttonMMult_Click(object sender, EventArgs e) { C.M_Multiplication(Convert.ToDouble(labelNumber.Text)); } //кнопка М/ private void buttonMDiv_Click(object sender, EventArgs e) { C.M_Division(Convert.ToDouble(labelNumber.Text)); } |
И последняя кнопка — «MRC». Она показывает содержимое регистра памяти на экран, а при повторном нажатии стирает его:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
private void buttonMRC_Click(object sender, EventArgs e) { if (CanPress()) { k++; if (k == 1) labelNumber.Text = C.MemoryShow().ToString(); if (k == 2) { C.Memory_Clear(); labelNumber.Text = «0»; k = 0; } } } |
На этом написание калькулятора на C# закончено. Спасибо за прочтение статьи! Если есть какие-либо вопросы, задавайте их в комментариях. Исходник программы доступен по ссылке ниже.
Скачать исходник
Ссылки по теме
Калькулятор Windows Forms на языке C#
Калькулятор процентных ставок на C#
Простенький калькулятор Windows Forms на C#
Обратная польская запись. Калькулятор на C#
Калькулятор C# в консоли
Привет!
Калькулятор у нас почему-то ассоциируется с чем-то, что должен написать каждый новичок. Возможно потому, что исторически компьютеры с той целью и создавались, чтобы считать. Но мы будем писать непростой калькулятор, не sympy конечно, но чтобы умел базовые алгебраические операции, типа дифференциирования, симплификации, а также фичи типа компиляции для ускорения вычислений.
Меньше воды! О чем статья?
Здесь будет поверхностно о построении выражения, парсинге из строки, подстановки переменной, аналитической производной, численным решении уравнения и определенного интеграла, рендеринг в формат LaTeX, комплексных числах, компиляцией функций, упрощении, раскрытии скобок, и бла бла бла. Вероятно, не в одной статье.
Для тех, кому нужно срочно что-нибудь склонировать, ссылка на репозиторий.
Берем оставшиеся с нового года печеньки, и погнали!
Для кого эта статья?
Я думаю, что статья может быть полезна новичку, но может быть те, кто чуть поопытнее, тоже найдут что-то интересное. Впрочем, я надеюсь написать статью так, чтобы ее можно было читать и не будучи C# программистом вообще.
Сборка выражения
Что такое «выражение»?
Когда я был маленький…
То я конечно хотел написать калькулятор. Что он должен уметь делать? Четыре основные операции, и в принципе куда еще больше. Так, моей задачей было посчитать значение строкового выражения, например «1 + (3 / 4 — (5 + 3 * 1))». Я взял мою любимую дельфи, и написал парсер, который сначала рекурсивно уходил в скобочки, а потом выражение в скобках заменял на значение, а скобки убирал. В принципе, вполне рабочий способ для меня в то время.
Конечно, это не строка. Довольно очевидно, что математическая формула — это либо дерево, либо стек, и здесь мы остановимся на первом. То есть каждая нода, каждый узел этого дерева, это какая-то операция, переменная, либо константа.
Операция — это либо функция, либо оператор, в принципе, примерно одно и то же. Ее дети — аргументы функции (оператора).
Иерархия классов в вашем коде
Разумеется, реализация может быть любой. Однако идея в том, что если ваше дерево состоит только из узлов и листьев, то они бывают разными. Поэтому я называю эти «штуки» — сущностями. Поэтому верхним классом у нас будет абстрактный класс Entity.
Абстрактный?
Как все знают из базового изучения языка, абстрактный класс хорош тем, что с одной стороны обобщает какие-то классы, с другой стороны позволяет разделить логику и поведение некоторых объектов. Объект абстрактного класса нельзя создать, а вот его наследника — можно.
А также будет четыре класса-наследника: NumberEntity, VariableEntity, OperatorEntity, FunctionEntity.
Как построить выражение?
Для начала мы будем строить выражение в коде, то есть
var x = new VariableEntity("x");
var expr = x * x + 3 * x + 12;
Если объявить пустой класс VariableEntity, то такой код выкинет вам ошибку, мол не знает как умножать и суммировать.
Переопределение операторов
Очень важная и полезная фича большинства языков, позволяя кастомизировать выполнение арифметических операций. Синтаксически реализуется по-разному в зависимости от языка. Например, реализация в C#
public static YourClass operator +(YourClass a, YourClass b) {
return new YourClass(a.ToString() + b.ToString());
}
Подробнее о переопределении операторов в C#
В репе реализовано тут.
(Не)явное приведение типов
В компилируемых языках типа C# такая штука обычно присутствует и позволяет без дополнительного вызова myvar.ToAnotherType() привести тип, если необходимо. Так, например, было бы удобно писать
NumberEntity myvar = 3;
Вместо привычного
NumberEntity myvar = new NumberEntity(3);
Подробнее о приведении типов в C#
В репе реализовано на этой строке.
Подвешивание
Класс Entity имеет поле Children — это просто список Entity, которые являются аргументами для данной сущности.
Мысли
Вообще-то детей могут иметь объекты лишь двух классов: OperatorEntity и FunctionEntity. То есть в принципе можно было бы создать какой-нибудь NodeEntity и у него унаследовать эти два класса, и создать LeafEntity и от него унаследовать VariableEntity и NumberEntity.
Когда у нас вызывается функция или оператор, нам стоит создать новую сущность, и в ее дети положить то, от чего вызывается функция или оператор. К примеру, сумма по идее должна выглядить примерно так:
public static Entity operator +(Entity a, Entity b){
var res = new OperatorEntity("+");
res.Children.Add(a);
res.Children.Add(b);
return res;
}
То есть теперь если у нас есть сущность x и сущность 3, то x+3 вернет сущность оператора суммы с двумя детьми: 3 и x. Так, мы можем строить деревья выражений.
Вызов функции более простой и не такой красивый, как с оператором:
public Entity Sin(Entity a)
{
var res = new FunctionEntity("sin");
res.Children.Add(a);
return res;
}
Подвешивание в репе реализовано тут.
Отлично, мы составили дерево выражений.
Подстановка переменной
Здесь все предельно просто. У нас есть Entity — мы проверяем является ли он сам переменной, если да, возвращаем значение, иначе — бежим по детям.
В этом огромном 48-строчном файле реализована столь сложная функция.
Вычисление значения
Собственно то, ради чего все это. Здесь мы по идее должны добавить в Entity какой-то такой метод
public Entity Eval()
{
if (IsLeaf)
{
return this;
}
else
return MathFunctions.InvokeEval(Name, Children);
}
Листик без изменений, а для всего остального у нас кастомное вычисление. Опять же, приведу лишь пример:
public static Entity Eval(List<Entity> args)
{
MathFunctions.AssertArgs(args.Count, 1);
var r = args[0].Eval();
if (r is NumberEntity)
return new NumberEntity(Number.Sin((r as NumberEntity).Value));
else
return r.Sin();
}
Если аргумент — число, то произведем численную функцию, иначе — вернем как было.
Number?
Это самая простая единица, число. Над ним можно проводить арифметические операции. По умолчанию оно комплексное. Также у него определены такие операции как Sin, Cos, и некоторые другие.
Если интересно, Number описан тут.
Производная
Численно производную посчитать может кто угодно, и такая функция пишется поистине в одну строку:
public double Derivative(Func<double, double> f, double x) => (f(x + 1.0e-5) - f(x)) * 1.0e+5;
Но разумеется нам хочется аналитическую производную. Так как у нас уже есть дерево выражений, мы можем рекурсивно заменить каждый узел в соответствии с правилом дифференциирования. Работать оно должно примерно так:
Вот, к примеру, как реализованна сумма в моем коде:
public static Entity Derive(List<Entity> args, VariableEntity variable) {
MathFunctions.AssertArgs(args.Count, 2);
var a = args[0];
var b = args[1];
return a.Derive(variable) + b.Derive(variable);
}
А вот произведение
public static Entity Derive(List<Entity> args, VariableEntity variable)
{
MathFunctions.AssertArgs(args.Count, 2);
var a = args[0];
var b = args[1];
return a.Derive(variable) * b + b.Derive(variable) * a;
}
А вот сам по себе обход:
public Entity Derive(VariableEntity x)
{
if (IsLeaf)
{
if (this is VariableEntity && this.Name == x.Name)
return new NumberEntity(1);
else
return new NumberEntity(0);
}
else
return MathFunctions.InvokeDerive(Name, Children, x);
}
Это метод Entity. И как видим, что у листа всего два состояния — либо это переменная, по которой мы дифференциируем, тогда ее производная равна 1, либо это константа (число либо VariableEntity), тогда ее производная 0, либо узел, тогда идет отсылка по имени (InvokeDerive обращается к словарю функций, где и находится нужная (например сумма или синус)).
Заметьте, я здесь не оставляю что-то типа dy/dx и сразу говорю, что производная от переменной не по которой мы дифференциируем равна 0. А вот здесь сделано по-другому.
Все дифференциирование описано в одном файле, а больше и не надо.
Упрощение выражения. Паттерны
Упрощение выражения в общем случае в принципе нетривиально. Ну например, какое выражение проще: или
? Но мы придерживаемся каких-то представлений, и на основе них хотим сделать те правила, которые точно упрощают выражение.
Можно при каждом Eval писать, что если у нас сумма, а дети — произведения, то переберем четыре варианта, и если где-то что-то равно, вынесем множитель… Но так делать конечно же не хочется. Поэтому можно догадаться до системы правил и паттернов. Итак, что мы хотим? Примерно такой синтаксис:
{ any1 / (any2 / any3) -> any1 * any3 / any2 },
{ const1 * var1 + const2 * var1 -> (const1 + const2) * var1 },
{ any1 + any1 * any2 -> any1 * (Num(1) + any2) },
Вот пример дерева, в котором нашлось поддерево (обведено в зеленый), отвечающее паттерну any1 + const1 * any1 (найденное any1 обведено в оранжевый).
Как видим, иногда нам важно, что одна и та же сущность должна повторяться, например чтобы сократить выражение x + a * x нам необходимо, чтобы и там и там был x, ведь x + a * y уже не сокращается. Поэтому нам нужно сделать алгоритм, который не только проверяет, что дерево соответсвует паттерну, но и
- Проверять, что одинаковые паттерновые Entity соответствуют одинаковым Entity.
- Записывать, что чему соответствует, чтобы потом подставить.
Точка входа выглядит примерно так:
internal Dictionary<int, Entity> EqFits(Entity tree)
{
var res = new Dictionary<int, Entity>();
if (!tree.PatternMakeMatch(this, res))
return null;
else
return res;
}
А в tree.PaternMakeMatch мы рекурсивно наполняем словарь ключами и их значениями. Вот пример списка самих паттерных Entity:
static readonly Pattern any1 = new Pattern(100, PatType.COMMON);
static readonly Pattern any2 = new Pattern(101, PatType.COMMON);
static readonly Pattern const1 = new Pattern(200, PatType.NUMBER);
static readonly Pattern const2 = new Pattern(201, PatType.NUMBER);
static readonly Pattern func1 = new Pattern(400, PatType.FUNCTION);
Когда мы будем писать any1 * const1 — func1 и так далее, у каждой ноды будет номер — это и есть ключ. Иначе говоря, при заполнении словаря, ключами выступят как раз эти номера: 100, 101, 200, 201, 400… А при постройке дерева мы будем смотреть на значение, соответствующее ключу, и подставлять его.
Реализовано тут.
Упрощение. Сортировка дерева
В статье, к которой я уже обращался, автор решил сделать просто, и отсортировал практически по хешу дерева. Ему удалось сократить a и -a, b + c + b превратить 2b + c. Но мы, конечно, хотим и чтобы (x + y) + x * y — 3 * x сокращалось, и в целом более сложные штуки.
Паттерны не работают?
Вообще, то, что мы сделали до этого, паттерны — чудовищно замечательная штука. Она позволит вам сокращать и разность квадратов, и сумму квадрата синуса и косинуса, и другие сложные штуки. Но элементарную пальму, ((((x + 1) + 1) + 1) + 1), она не сократит, ведь здесь главное правило — коммутативность слагаемых. Поэтому первый шаг — вычленить «линейных детей».
«Линейные дети»
Собственно для каждой ноды суммы или разности (и, кстати, произведения/деления) мы хотим получить список слагаемых (множителей).
Это в принципе несложно. Пусть функция LinearChildren(Entity node) возвращает список, тогда мы смотрим на child in node.Children: если child — это не сумма, то result.Add(child), иначе — result.AddRange(LinearChildren(child)).
Не самым красивым образом реализовано тут.
Группировка детей
Итак, у нас есть список детей, но что дальше? Допустим, у нас есть sin(x) + x + y + sin(x) + 2 * x. Очевидно, что наш алгоритм получит пять слагаемых. Далее мы хотим сгруппировать по похожести, например, x похож на 2 * x больше, чем на sin(x).
Вот хорошая группировка:
Так как в ней паттерны дальше справятся с преобразованием 2*x + x в 3*x.
То есть мы сначала группируем по некоторому хешу, а затем делаем MultiHang — преобразование n-арного суммирования в бираное.
Хеш узла
С одной стороны, и
следует поместить в одну группу. С другой стороны, при наличии
помещать в одну группу с
бессмысленно.
Мысли
Если подумать, то . Хотя мне кажется, это практически не проще, и уж точно не нужно. Да и вообще, упрощение — вещь ни разу неочевидная, и уж это точно не первое, что стоит писать при написании «калькулятора».
Поэтому мы реализовываем многоуровневую сортировку. Сначала мы делаем вид, что — одно и то же. Посортировали, успокоились. Потом делаем вид, что
можно помещать только с другими
. И вот уже наши
и
наконец объединились. Реализовано достаточно просто:
internal string Hash(SortLevel level)
{
if (this is FunctionEntity)
return this.Name + "_" + string.Join("_", from child in Children select child.Hash(level));
else if (this is NumberEntity)
return level == SortLevel.HIGH_LEVEL ? "" : this.Name + " ";
else if (this is VariableEntity)
return "v_" + Name;
else
return (level == SortLevel.LOW_LEVEL ? this.Name + "_" : "") + string.Join("_", from child in Children where child.Hash(level) != "" select child.Hash(level));
}
Как видим, функция по-любому влияет на сортировку (разумеется, ведь с
вообще никак в общем случае не связана). Как и переменная,
с
ну никак не получится смешать. А вот константы и операторы учитываются не на всех уровнях. В таком порядке идет сам процесс упрощения
public Entity Simplify(int level)
{
// Сначала мы делаем самую простую симплификацю: вычисление значений там, где это возможно, умножение на ноль и т. д.
var stage1 = this.InnerSimplify();
Entity res = stage1;
for (int i = 0; i < level; i++)
{
// Этот блок ответственнен за сортировку. Сначала мы группируем что-то типа x и x+1 (переменные и функции), затем что-то типа x-1 и x+1 (переменные, функции и константы), затем что-то типа x+1 и x+1 (учитывается все).
switch (i)
{
case 0: res = res.Sort(SortLevel.HIGH_LEVEL); break;
case 2: res = res.Sort(SortLevel.MIDDLE_LEVEL); break;
case 4: res = res.Sort(SortLevel.LOW_LEVEL); break;
}
// Здесь мы заменяем паттерны.
res = TreeAnalyzer.Replace(Patterns.CommonRules, res).InnerSimplify();
}
return res;
}
Мысли
Самая ли это лучшая реализация? Пихните в личные сообщения, может быть будут идеи получше. Я долго думал, как сделать это макимально красиво, хотя по моему мнению, до «красиво» здесь далеко.
Дерево сортирую тут.
«Компиляция» функций
В кавычках — так как не в сам IL код, а лишь в очень быстрый набор инструкций. Но зато очень просто.
Проблема Substitute
Чтобы посчитать значение функции, нам достаточно вызвать подстановку переменной и eval, например
var x = MathS.Var("x");
var expr = x * x + 3;
var result = expr.Substitute(x, 5).Eval();
Но это работает медленно, около 1.5 микросекунды на синус.
Инструкции
Чтобы ускорить вычисление, мы делаем вычисление функции на стеке, а именно:
1) Придумываем класс FastExpression, у которого будет список инструкций
2) При компиляции инструкции складываются в стек в обратном порядке, то есть если есть функция x * x + sin(x) + 3, то инструкции будут примерно такими:
PUSHVAR 0 // Подстановка переменной номер 0 - x
CALL 6 // Вызов функции номер 6 - синуса
PUSHCONST 3
CALL 0 // Вызов функции номер 0 - суммы
PUSHVAR 0
PUSHVAR 0
CALL 2
CALL 0
Далее при вызове мы прогоняем эти инструкции и возвращаем Number.
Пример выполнения инструкции суммы:
internal static void Sumf(Stack<Number> stack)
{
Number n1 = stack.Pop();
Number n2 = stack.Pop();
stack.Push(n1 + n2);
}
Вызов синуса сократился с 1500нс до 60нс (системный Complex.Sin работает за 30нс).
В репе реализовано тут.
Фух, вроде бы пока все. Хотя рассказать еще есть о чем, но мне кажется объем для одной статьи достаточный. Интересно ли кому-нибудь продолжение? А именно: парсинг из строки, форматирование в латех, определенный интеграл, и прочие плюшки.
Ссылка на репозиторий со всем кодом, а также тестами и самплами.
Мысли
Вообще-то я продолжаю работать над этим проектом. Распространяется он под MIT (то есть делайте с ним что хотите), и он никогда не станет ни закрытым, ни коммерческим. Более того, если есть идеи для улучшения и вклада — pull-реквесты очень приветствуются.
Спасибо за внимание!
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Делать следующую часть?
Проголосовали 167 пользователей.
Воздержались 36 пользователей.
Improve Article
Save Article
Improve Article
Save Article
C# is an object-oriented, modern programming language that was created by Microsoft. It runs on the .NET Framework. C# is very close to C/C++ and Java programming languages. In this article, we will learn how to create a calculator in C#.
Basic Functions of Calculator:
- Addition of two numbers.
- Difference between two numbers.
- Product of two numbers.
- Division of two numbers.
Approach:
- Declare local variables num1 and num2 for two numeric values.
- Enter the choice.
- Takes two numbers, num1, and num2.
- do-while jump to an operator selected by the user.
- Display the operation result.
- Exit
Example:
C#
using
System;
using
System.Text;
using
System.Threading.Tasks;
namespace
calculator_c_sharp
{
class
Program
{
static
void
Main(
string
[] args)
{
string
value;
do
{
int
res;
Console.Write(
"Enter first number:"
);
int
num1 = Convert.ToInt32(Console.ReadLine());
Console.Write(
"Enter second number:"
);
int
num2 = Convert.ToInt32(Console.ReadLine());
Console.Write(
"Enter symbol(/,+,-,*):"
);
string
symbol = Console.ReadLine();
switch
(symbol)
{
case
"+"
:
res = num1 + num2;
Console.WriteLine(
"Addition:"
+ res);
break
;
case
"-"
:
res = num1 - num2;
Console.WriteLine(
"Subtraction:"
+ res);
break
;
case
"*"
:
res = num1 * num2;
Console.WriteLine(
"Multiplication:"
+ res);
break
;
case
"/"
:
res = num1 / num2;
Console.WriteLine(
"Division:"
+ res);
break
;
default
:
Console.WriteLine(
"Wrong input"
);
break
;
}
Console.ReadLine();
Console.Write(
"Do you want to continue(y/n):"
);
value = Console.ReadLine();
}
while
(value==
"y"
|| value==
"Y"
);
}
}
}
Output:
Addition of two numbers:
Subtraction of two numbers:
Multiplication of two numbers:
Division of two numbers:
В данном уроке мы разработаем с вами калькулятор на C#. Программа будет написана согласно принципам объектно-ориентированного программирования (ООП): будет определен интерфейс для методов, реализующих функционал клавиш математических операций калькулятора, от интерфейса выполним наследование и напишем соответствующий класс и методы. Калькулятор, помимо базовых операций сложения, вычитания, умножения и деления, будет предоставлять возможность выполнять операции: извлечения квадратного корня, извлечения корня произвольной степени, возведения в квадрат, возведения в произвольную степень, вычисления факториала, а также работу с регистром памяти (MRC).
Создание интерфейса.
Создадим в Visual Studio проект на Visual C# Windows Forms. Добавим на форму элемент GroupBox, в который поместим Label. В свойстве Dock, элемента Label, необходимо указать Right, чтобы Label был привязан к правому краю. Связка данных элементов управления будет реализовывать дисплей калькулятора.
Калькулятор также содержит кнопки. Всего их 28 штук.
Программирование калькулятора на C#Реализация интерфейса класса.
Поскольку наш калькулятор будет написан в рамках парадигмы ООП (объектно-ориентированного программирования), то начнем кодирование с описания структуры интерфейса для класса, реализующего математические операции программы.
Добавим в проект класс InterfaceCalc.cs и определим в созданном файле интерфейс InterfaceCalc.
//интерфейс
public interface InterfaceCalc
{
//а — первый аргумент, b — второй
void Put_A(double a); //сохранить а
void Clear_A();
double Multiplication(double b);
double Division(double b);
double Sum(double b);
double Subtraction(double b); //вычитание
double SqrtX(double b);
double DegreeY(double b);
double Sqrt();
double Square();
double Factorial();
double MemoryShow(); //показать содержимое регистра памяти
void Memory_Clear(); //стереть содержимое регистра памяти
//* / + — к регистру памяти
void M_Multiplication(double b);
void M_Division(double b);
void M_Sum(double b);
void M_Subtraction(double b); //вычитание
}
Для выполнения математических операций понадобится два операнда: a и b (например, a + b). Операнд a придется хранить в памяти калькулятора, пока пользователь будет вводить второй аргумент операции. Для сохранения числа a объявим прототип метода void Put_A(double a), для очистки — void Clear_A(). Для умножения, деления, сложения и вычитания чисел a и b соответственно понадобятся методы: double Multiplication(double b), double Division(double b), double Sum(double b), double Subtraction(double b). Вычисление корня степени b из a: double SqrtX(double b). Возведение числа a в степень b: double DegreeY(double b). Вычисление квадратного корня: double Sqrt(). Возведение числа a в квадрат: double Square(). Вычисление факториала a!: double Factorial(). Теперь объявления методов для работы с регистром памяти (MRC) калькулятора. Показать содержимое памяти и очистить его: double MemoryShow(), void Memory_Clear(). M×, M÷, M+ и M- к регистру соответственно: void M_Multiplication(double b), void M_Division(double b), void M_Sum(double b), void M_Subtraction(double b).
Создание класса, реализующего интерфейс InterfaceCalc
Теперь добавим в калькулятор класс, который будет реализовывать написанный ранее интерфейс. Для этого в проекте создадим класс Calc : InterfaceCalc. Как вы видите, здесь используется наследование (оператор «двоеточие»). В данном классе напишем реализацию всех методов, требуемых спецификацией нашего интерфейса.
public class Calc : InterfaceCalc
{
private double a = 0;
private double memory = 0;
public void Put_A(double a)
{
this.a = a;
}
public void Clear_A()
{
a = 0;
}
public double Multiplication(double b)
{
return a * b;
}
public double Division(double b)
{
return a / b;
}
public double Sum(double b)
{
return a + b;
}
public double Subtraction(double b) //вычитание
{
return a — b;
}
public double SqrtX(double b)
{
return Math.Pow(a, 1 / b);
}
public double DegreeY(double b)
{
return Math.Pow(a, b);
}
public double Sqrt()
{
return Math.Sqrt(a);
}
public double Square()
{
return Math.Pow(a, 2.0);
}
public double Factorial()
{
double f = 1;
for (int i = 1; i <= a; i++)
f *= (double)i;
return f;
}
//показать содержимое регистра мамяти
public double MemoryShow()
{
return memory;
}
//стереть содержимое регистра мамяти
public void Memory_Clear()
{
memory = 0.0;
}
//* / + — к регистру памяти
public void M_Multiplication(double b)
{
memory *= b;
}
public void M_Division(double b)
{
memory /= b;
}
public void M_Sum(double b)
{
memory += b;
}
public void M_Subtraction(double b)
{
memory -= b;
}
}
Реализация работы калькулятора
Перейдем к написанию кода в классе Form1.
Объявим два поля:
CalcC;
int k;
Это классовая переменная C для класса Calc и целое число k. С помощью k будем считать количество нажатий кнопки MRC, чтобы при первом нажатии выводить значение регистра памяти на экран, а при последующем повторном — стирать значение регистра.
Далее. Нам понадобится ряд методов, которые будут обеспечивать правильное функционирование калькулятора.
Дисплеем в нашем калькуляторе является Label. Его содержимое — это строка текста. Нажатие на цифровые клавиши — это конкатенация текущего значения строки с символом клавиши. Необходимо исключить дублирование нулей в левой части строки (это делают 2-й и 3-й if в коде ниже), а также не допустить написание цифр, если в Label находится знак «бесконечность» (он появляется, например, при делении на ноль; 1-й if). Для решения данных проблем напишем метод CorrectNumber():
privatevoidCorrectNumber(){//если есть знак «бесконечность» — не даёт писать цифры после негоif(labelNumber.Text.IndexOf(«∞»)!=-1)labelNumber.Text=labelNumber.Text.Substring(0,labelNumber.Text.Length-1);//ситуация: слева ноль, а после него НЕ запятая, тогда ноль можно удалитьif(labelNumber.Text[0]==’0’&& (labelNumber.Text.IndexOf(«,») != 1)) labelNumber.Text = labelNumber.Text.Remove(0, 1);//аналогично предыдущему, только для отрицательного числаif(labelNumber.Text[0]==’-‘)if(labelNumber.Text[1]==’0’&& (labelNumber.Text.IndexOf(«,») != 2)) labelNumber.Text = labelNumber.Text.Remove(1, 1);}
Еще необходим метод, проверяющий: не нажата ли какая-нибудь кнопка калькулятора из математических операций, требующих два операнда. Данная проверка необходима для недопущения нажатия других мат. операций, если какая-либо уже нажата. Код метода CanPress():
privateboolCanPress(){if(!buttonMult.Enabled)returnfalse;if(!buttonDiv.Enabled)returnfalse;if(!buttonPlus.Enabled)returnfalse;if(!buttonMinus.Enabled)returnfalse;if(!buttonSqrtX.Enabled)returnfalse;if(!buttonDegreeY.Enabled)returnfalse;returntrue;}
Также, напишем вспомогательный метод FreeButtons(), который снимает нажатия со всех кнопок математических операций калькулятора, требующих два операнда (умножение, деление, сложение, вычитание, вычисление корня произвольной степени и возведение числа в произвольную степень):
privatevoidFreeButtons(){buttonMult.Enabled=true;buttonDiv.Enabled=true;buttonPlus.Enabled=true;buttonMinus.Enabled=true;buttonSqrtX.Enabled=true;buttonDegreeY.Enabled=true;}
Со вспомогательными методами закончили. Идем дальше.
В конструкторе Form1 создадим экземпляр класса Calc, а также в «дисплей» калькулятора занесем значение ноль.
publicForm1(){InitializeComponent();C=newCalc();labelNumber.Text=»0″;}
Теперь перейдем к кодированию обработчиков нажатий кнопок калькулятора. Кнопка «Очистка» (CE):
privatevoidbuttonClear_Click(objectsender,EventArgse){labelNumber.Text=»0″;C.Clear_A();FreeButtons();k=0;}
В «дисплей» записывается ноль, переменная a стирается, нажатия с кнопок математических операций снимаются и k обнуляется.
Кнопка изменения знака у числа «+/-«:
privatevoidbuttonChangeSign_Click(objectsender,EventArgse){if(labelNumber.Text[0]==’-‘)labelNumber.Text=labelNumber.Text.Remove(0,1);elselabelNumber.Text=»-«+labelNumber.Text;}
Кнопка «запятая» (будет добавлена, если ее еще нет и на «дисплее» нет знака бесконечность):
privatevoidbuttonPoint_Click(objectsender,EventArgse){if((labelNumber.Text.IndexOf(«,»)==-1)&& (labelNumber.Text.IndexOf(«∞») == -1)) labelNumber.Text += «,»;}
Теперь цифровые клавиши 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:
privatevoidbutton0_Click(objectsender,EventArgse){labelNumber.Text+=»0″;CorrectNumber();}privatevoidbutton1_Click(objectsender,EventArgse){labelNumber.Text+=»1″;CorrectNumber();}privatevoidbutton2_Click(objectsender,EventArgse){labelNumber.Text+=»2″;CorrectNumber();}privatevoidbutton3_Click(objectsender,EventArgse){labelNumber.Text+=»3″;CorrectNumber();}privatevoidbutton4_Click(objectsender,EventArgse){labelNumber.Text+=»4″;CorrectNumber();}privatevoidbutton5_Click(objectsender,EventArgse){labelNumber.Text+=»5″;CorrectNumber();}privatevoidbutton6_Click(objectsender,EventArgse){labelNumber.Text+=»6″;CorrectNumber();}privatevoidbutton7_Click(objectsender,EventArgse){labelNumber.Text+=»7″;CorrectNumber();}privatevoidbutton8_Click(objectsender,EventArgse){labelNumber.Text+=»8″;CorrectNumber();}privatevoidbutton9_Click(objectsender,EventArgse){labelNumber.Text+=»9″;CorrectNumber();}
Кнопка «Равно»:
private void buttonCalc_Click(object sender, EventArgs e)
{
if (!buttonMult.Enabled)
labelNumber.Text = C.Multiplication(Convert.ToDouble(labelNumber.Text)).ToString();
if (!buttonDiv.Enabled)
labelNumber.Text = C.Division(Convert.ToDouble(labelNumber.Text)).ToString();
if (!buttonPlus.Enabled)
labelNumber.Text = C.Sum(Convert.ToDouble(labelNumber.Text)).ToString();
if (!buttonMinus.Enabled)
labelNumber.Text = C.Subtraction(Convert.ToDouble(labelNumber.Text)).ToString();
if (!buttonSqrtX.Enabled)
labelNumber.Text = C.SqrtX(Convert.ToDouble(labelNumber.Text)).ToString();
if (!buttonDegreeY.Enabled)
labelNumber.Text = C.DegreeY(Convert.ToDouble(labelNumber.Text)).ToString();
C.Clear_A();
FreeButtons();
k = 0;
}
В зависимости от того, какая из кнопок математических операций нажата, будет вызван соответствующий метод из класса Calc, вычисляющий данную функцию, и результат выведется в Label.
Далее кнопки математических операций. Сработают только в том случае, если другие в данный момент не нажаты (CanPress() вернет true):
//кнопка УмножениеprivatevoidbuttonMult_Click(objectsender,EventArgse){if(CanPress()){C.Put_A(Convert.ToDouble(labelNumber.Text));buttonMult.Enabled=false;labelNumber.Text=»0″;}}//кнопка ДелениеprivatevoidbuttonDiv_Click(objectsender,EventArgse){if(CanPress()){C.Put_A(Convert.ToDouble(labelNumber.Text));buttonDiv.Enabled=false;labelNumber.Text=»0″;}}//кнопка СложениеprivatevoidbuttonPlus_Click(objectsender,EventArgse){if(CanPress()){C.Put_A(Convert.ToDouble(labelNumber.Text));buttonPlus.Enabled=false;labelNumber.Text=»0″;}}//кнопка ВычитаниеprivatevoidbuttonMinus_Click(objectsender,EventArgse){if(CanPress()){C.Put_A(Convert.ToDouble(labelNumber.Text));buttonMinus.Enabled=false;labelNumber.Text=»0″;}}//кнопка Корень произвольной степениprivatevoidbuttonSqrtX_Click(objectsender,EventArgse){if(CanPress()){C.Put_A(Convert.ToDouble(labelNumber.Text));buttonSqrtX.Enabled=false;labelNumber.Text=»0″;}}//кнопка Возведение в произвольную степеньprivatevoidbuttonDegreeY_Click(objectsender,EventArgse){if(CanPress()){C.Put_A(Convert.ToDouble(labelNumber.Text));buttonDegreeY.Enabled=false;labelNumber.Text=»0″;}}//кнопка Корень квадратныйprivatevoidbuttonSqrt_Click(objectsender,EventArgse){if(CanPress()){C.Put_A(Convert.ToDouble(labelNumber.Text));labelNumber.Text=C.Sqrt().ToString();C.Clear_A();FreeButtons();}}//кнопка Квадрат числаprivatevoidbuttonSquare_Click(objectsender,EventArgse){if(CanPress()){C.Put_A(Convert.ToDouble(labelNumber.Text));labelNumber.Text=C.Square().ToString();C.Clear_A();FreeButtons();}}//кнопка ФакториалprivatevoidbuttonFactorial_Click(objectsender,EventArgse){if(CanPress()){if((Convert.ToDouble(labelNumber.Text)==(int)(Convert.ToDouble(labelNumber.Text)))&& ((Convert.ToDouble(labelNumber.Text) >= 0.0))) { C.Put_A(Convert.ToDouble(labelNumber.Text));labelNumber.Text=C.Factorial().ToString();C.Clear_A();FreeButtons();}elseMessageBox.Show(«Число должно быть >= 0 и целым!»);}}
Калькулятор вычислит факториал только в том случае, если текущее введенное число целое и больше, либо равно нулю.
Кнопки математических операций над регистром памяти (MRC):
//кнопка М+privatevoidbuttonMPlus_Click(objectsender,EventArgse){C.M_Sum(Convert.ToDouble(labelNumber.Text));}//кнопка М-privatevoidbuttonMMinus_Click(objectsender,EventArgse){C.M_Subtraction(Convert.ToDouble(labelNumber.Text));}//кнопка М*privatevoidbuttonMMult_Click(objectsender,EventArgse){C.M_Multiplication(Convert.ToDouble(labelNumber.Text));}//кнопка М/privatevoidbuttonMDiv_Click(objectsender,EventArgse){C.M_Division(Convert.ToDouble(labelNumber.Text));}
И последняя кнопка — «MRC». Она показывает содержимое регистра памяти на экран, а при повторном нажатии стирает его:
privatevoidbuttonMRC_Click(objectsender,EventArgse){if(CanPress()){k++;if(k==1)labelNumber.Text=C.MemoryShow().ToString();if(k==2){C.Memory_Clear();labelNumber.Text=»0″;k=0;}}}
На этом написание калькулятора на C# закончено. Спасибо за прочтение статьи!
В сегодняшней статье мы создадим простой калькулятор способный выполнять сложение, вычитание, умножение, а также деление. И все это мы реализуем на языке C# и шаблонах Windows Forms. Так что, давайте приступим.
Шаг 1
Откройте Visual Studio или Visual C# Express Edition и создайте новый проект.
Установите его тип в приложение Windows Forms и задайте имя в приложения Калькулятор. Нажмите кнопку ОК.
Должно получиться следующее:
Шаг 2
Измените свойство текста формы на Калькулятор, потому что мы не хотим, чтобы наше приложение имело заголовок Form1 при запуске.
Для чего заходим во вкладку свойтва.
Шаг 3
Далее во вкладке Вид находим панель элементов или ToolBox.
Поместите элемент управления TextBox на форму и измените его размер. Выполните некоторые изменения в свойствах, как показано на рисунке.
Шаг 4
Теперь мы начинаем работать с дисплеем, обычно при запуске калькулятора на нем должно отображаться число 0. В нашем случае это не так. Поэтому мы изменяем свойство Text и записываем в нем 0. Убедитесь, что вы не добавляете никаких пробелов до или после 0.
Второе, что следует отметить, это то, что число выровнено слева, в то время как калькуляторы выравнивают число справа. Найдите свойство TextAlign и измените его на Right.
Вот как будет выглядеть окно.
Шаг 5
В окне панели инструментов перетащите кнопку на форму. Измените его свойство Name на n1. Это поможет нам определить, какой номер был нажат. Измените свойство backcolor для кнопки. После изменения цвета мы изменим текст, отображаемый кнопкой, поэтому измените свойство text на 1. Перейдите к свойству шрифта и установите для него значение шрифта Сourier new, а размер, например, 16.
Теперь мы можем повторить ту же операцию со всеми остальными девятью кнопками, или мы можем просто скопировать эту кнопку и быстро получить тот же результат. Просто удерживайте клавишу ctrl и перетащите элемент управления.
Измените названия кнопок (или вы можете оставить их как есть и пропустить эту часть). Имена по-прежнему будут n2, n3, n4, n5, n6, n7, n8, n9 и n0.Окно должно выглядеть так.
Шаг 6
Дважды нажмите кнопку n1, чтобы перейти к ее обработчику события. Тогда n1_Click — это название этой процедуры. Он сообщает вам, что он выполняется, когда пользователь нажимает кнопку с именем n1. Запишите код, чтобы он был таким:
private void n1_Click(object sender, EventArgs e)
{
if (textBox1.Text == "0" && textBox1.Text != null)
{
textBox1.Text = "1";
}
else
{
textBox1.Text = textBox1.Text + "1";
}
}
Вы должны повторить код для кнопок n2, n3, n4, n5, n6, n7, n8, n9 и n0, чтобы добавить числа 2,3,4,5,6,7,8,9,0 соответственно.
Теперь запустите приложение и нажмите несколько кнопок:
Таким образом, мы создали простой шаблон калькулятора с C# и Windows Forms, в который осталось добавить арифметическую логику.
-
Создано 25.11.2021 10:26:55
-
Михаил Русаков
Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!
Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.
Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления
Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.
Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):
CALCULATOR IN WINDOWS FORM
Microsoft.net framework or Mono Framework provides a free and open source graphical (GUI) class library called as Windows Form or WinForms.
These WinForms provide a platform to App developers to write rich client applications for laptop, desktops, and tablet PCs etc.
These WinForms are written in C# programming
Language and an attractive feature included in it is the drag and drop facilities that can be applied on the native Windows Controls like button , textbox, checkbox, listview etc
These controls included in uncountable number of applications. One of the application that we will study about is Calculator made in Windows Forms.
So let us quickly get started to make our own basic calculator in WinForms.
STEP 1: Open Visual Studio or the IDE which you are comfortable with and create a new project . Select the Windows Form Application that comes under .net framework.
Click on create and then name the project as per your choice, for eg. “Calculator_1” and then press OK.
A main form on your workspace will appear as this:
STEP 2: Right click on the form and select “properties” from the dialog box and then change the text property to “calculator” for your convenience.
Also, you can modify the size of the form by adjusting its property attributes.
STEP 3: Open toolbox by pressing Ctrl + Alt + X or from the view in Menu Bar if toolbox is not there on the workspace already.
- Once you come across the toolbox , select “textbox“ control and drop it onto the form. Resize it according to your need from the properties or you can adjust it directly by the arrows that appear on the textbox boundary once you click on the textbox.
- This textbox will display the values selected by the user and the result generated by the calculator.
- Change the alignment of the text in the textbox to right in the properties. You can also set the default value in calculator textbox to 0.
STEP 4: A basic calculator will contain numerical digits from 0 to 9, + , — , * , /, . , = . Also ON ,OFF buttons for turning the calculator on and off and vice versa. Two additional buttons namely “clear” and “<=” (backspace) are added if any value is to be removed from the textbox .
Therefore, we will be adding 20 buttons in the form. Please note that we will add the ON and OFF button in such a way that one button overlaps the other. This is to make any one button out of these two to be visible when the calculator is on or off. i. e , the ON button will be hidden when we turn it on and the OFF button will be shown and vice versa.
STEP 5: Select “ button control” from the toolbox and resize it. Copy and paste this button on the form for all the other buttons on the calculator to maintain symmetry in the calculator.
Adjust the “=” button size as according to the picture here.
STEP 6: Name these buttons according to the function they are performing . for eg. For numerical values , name them as “ b0 to b9 ” for 0 to 9. For + , — ,* ,/ ,= ,. , change name to add , sub , mul , div, result, point respectively.
Also change their text according to the values that are to be displayed on the buttons in the calculator.
STEP 7: Once it is done , you will have to specify the action that has to be performed by any button when it is pressed. Double click on the button you will come across the coding section of that button. You have to specify the functionality of each button one by one in the code section.
The coding should look like this:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Calculator_1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
float num, ans;
int count;
public void disable()
{
textBox1.Enabled = false;
on.Show();
off.Hide();
b0.Enabled = false;
b1.Enabled = false;
b2.Enabled = false;
b3.Enabled = false;
b4.Enabled = false;
b5.Enabled = false;
b6.Enabled = false;
b7.Enabled = false;
b8.Enabled = false;
b9.Enabled = false;
add.Enabled = false;
sub.Enabled = false;
mul.Enabled = false;
div.Enabled = false;
point.Enabled = false;
backspace.Enabled = false;
clear.Enabled = false;
result.Enabled = false;
}
public void enable()
{
textBox1.Enabled = true;
on.Hide();
off.Show();
b0.Enabled = true;
b1.Enabled = true;
b2.Enabled = true;
b3.Enabled = true;
b4.Enabled = true;
b5.Enabled = true;
b6.Enabled = true;
b7.Enabled = true;
b8.Enabled = true;
b9.Enabled = true;
add.Enabled = true;
clear.Enabled = true;
backspace.Enabled = true;
result.Enabled = true;
div.Enabled = true;
sub.Enabled = true;
mul.Enabled = true;
point.Enabled = true;
}
private void b1_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 1;
}
private void b2_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 2;
}
private void b3_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 3;
}
private void b4_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 4;
}
private void b5_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 5;
}
private void b6_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 6;
}
private void b7_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 7;
}
private void b8_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 8;
}
private void b9_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 9;
}
private void b0_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + 0;
}
private void div_Click(object sender, EventArgs e)
{
num = float.Parse(textBox1.Text);
textBox1.Clear();
textBox1.Focus();
count = 4;
label1.Text = num.ToString() + "/";
}
private void result_Click(object sender, EventArgs e)
{
compute();
label1.Text = "";
}
private void add_Click(object sender, EventArgs e)
{
num = float.Parse(textBox1.Text);
textBox1.Clear();
textBox1.Focus();
count = 1;
label1.Text = num.ToString() + "+";
}
private void sub_Click(object sender, EventArgs e)
{
num = float.Parse(textBox1.Text);
textBox1.Clear();
textBox1.Focus();
count = 2;
label1.Text = num.ToString() + "-";
}
private void mul_Click(object sender, EventArgs e)
{
num = float.Parse(textBox1.Text);
textBox1.Clear();
textBox1.Focus();
count = 3;
label1.Text = num.ToString() + "*";
}
private void point_Click(object sender, EventArgs e)
{
textBox1.Text = textBox1.Text + ".";
}
private void result_Click_1(object sender, EventArgs e)
{
compute();
label1.Text = "";
}
private void on_Click(object sender, EventArgs e)
{
enable();
}
private void off_Click(object sender, EventArgs e)
{
disable();
}
private void clear_Click(object sender, EventArgs e)
{
textBox1.Text = "";
}
private void backspace_Click(object sender, EventArgs e)
{
int length = textBox1.TextLength - 1;
string text = textBox1.Text;
textBox1.Clear();
for(int i=0; i< length; i++ )
{
textBox1.Text = textBox1.Text + text[i];
}
}
public void compute()
{
switch (count)
{
case 1:
ans = num + float.Parse(textBox1.Text);
textBox1.Text = ans.ToString();
break;
case 2:
ans = num - float.Parse(textBox1.Text);
textBox1.Text = ans.ToString();
break;
case 3:
ans = num * float.Parse(textBox1.Text);
textBox1.Text = ans.ToString();
break;
case 4:
ans = num / float.Parse(textBox1.Text);
textBox1.Text = ans.ToString();
break;
default:
break;
}
}
}
}
The calculator will appear somewhat like this: