[C#] 제곱, 제곱근, 역수 수식 표시 & 계산기 정밀도 높이기(부동 소수점 계산 오류)
2024. 1. 11. 16:15ㆍ[C#]/[C# 윈폼] 혼자해보는 계산기 만들기
1. 역수, 제곱, 제곱근에 대한 수식 표시 기능을 만들었다.
1) 역수 처리
// 역수 처리
private void OneOverXBtn_Click(object sender, EventArgs e)
{
double num = double.Parse(NumScreen.Text);
double inverse = 1.0 / num;
NumScreen.Text = inverse.ToString("#,##0.#########");
if (!isNewNum)
{
string lastExpression = expressionScreen.Text.Substring(expressionScreen.Text.LastIndexOf(' ') + 1);
if (lastExpression.Contains("1/("))
{
expressionScreen.Text = expressionScreen.Text.Substring(0, expressionScreen.Text.LastIndexOf("1/("));
}
else
{
string currentExpression = expressionScreen.Text;
expressionScreen.Text = currentExpression + " ";
}
}
else
{
expressionScreen.Text = "";
}
expressionScreen.Text += "1/(" + NumScreen.Text + ")";
isNewNum = true;
Opt = Operators.None;
}
2) 제곱 처리
//제곱 처리
private void SqrBtn_Click(object sender, EventArgs e)
{
double num = double.Parse(NumScreen.Text);
double result = num * num;
NumScreen.Text = result.ToString("#,##0.#########");
Result = result;
if (!isNewNum)
{
string lastExpression = expressionScreen.Text.Substring(expressionScreen.Text.LastIndexOf(' ') + 1);
if (lastExpression.Contains("²"))
{
expressionScreen.Text = expressionScreen.Text.Substring(0, expressionScreen.Text.LastIndexOf("²"));
}
else
{
string currentExpression = expressionScreen.Text;
expressionScreen.Text = currentExpression + " ";
}
}
else
{
expressionScreen.Text = "";
}
expressionScreen.Text += num + "²";
isNewNum = true;
Opt = Operators.None; // 사칙 연산 초기화
}
3) 제곱근 처리
//제곱근 처리
private void rootBtn_Click(object sender, EventArgs e)
{
double num = double.Parse(NumScreen.Text);
double result = Math.Sqrt(num); // 제곱근 계산
NumScreen.Text = result.ToString("#,##0.#########");
Result = result;
if (!isNewNum)
{
string lastExpression = expressionScreen.Text.Substring(expressionScreen.Text.LastIndexOf(' ') + 1);
if (lastExpression.Contains("²√"))
{
expressionScreen.Text = expressionScreen.Text.Substring(0, expressionScreen.Text.LastIndexOf("²√"));
}
else
{
string currentExpression = expressionScreen.Text;
expressionScreen.Text = currentExpression + " ";
}
}
else
{
expressionScreen.Text = "";
}
expressionScreen.Text += "²√" + num;
isNewNum = true;
Opt = Operators.None; // 사칙 연산 초기화
}
- 이렇게 만들고 보니 계산법과 수식만 다르고 같은 로직으로 돌아간다는 게 보였다.
- 그래서 하나의 함수로 만들어줬다.
변경 후
// 역수 처리
private void OneOverXBtn_Click(object sender, EventArgs e)
{
SetExpressionText("1/(");
}
//제곱 처리
private void SqrBtn_Click(object sender, EventArgs e)
{
SetExpressionText("²");
}
//제곱근 처리
private void rootBtn_Click(object sender, EventArgs e)
{
SetExpressionText("²√");
}
SetExpressionText 함수
private void SetExpressionText(string sign)
{
double num = double.Parse(NumScreen.Text);
double result = 0.0;
// 수식 추가 및 초기화 로직
if (!isNewNum)
{
string lastExpression = expressionScreen.Text.Substring(expressionScreen.Text.LastIndexOf(' ') + 1);
if (lastExpression.Contains(sign))
{
expressionScreen.Text = expressionScreen.Text.Substring(0, expressionScreen.Text.LastIndexOf(sign));
}
else
{
expressionScreen.Text += " ";
}
}
else
{
expressionScreen.Text = "";
}
// 연산 로직
switch (sign)
{
case "²":
result = num * num;
expressionScreen.Text = $"{num}²";
break;
case "²√":
result = Math.Sqrt(num);
expressionScreen.Text = $"²√{num}";
break;
case "1/(":
result = 1.0 / num;
expressionScreen.Text = $"1/({num})";
break;
}
NumScreen.Text = result.ToString("#,##0.#########");
isNewNum = true;
Opt = Operators.None;
}
- sign값을 받아와 각 sign 별 처리를 해줬다.
- 이렇게 해주니 한눈에 보기도 편하고 나중에 유지보수도 쉬워질 것 같았다.
2. 계산 정밀도 높히기(feat. 부동 소수점)
12의 제곱근을 계산한 뒤 다시 제곱을 하면 11.999999999라는 숫자가 나오는 것을 볼 수 있었다.
전부 실패하고 100% 정확도는 아니지만 거의 근사한 방법으로 시도했다.
- 소수점에 9 갯수와 0 갯수를 Count해서 올림/반올림
시도해 본 방법
Heron's method (x)
Newton's method (x)
소수점 반올림 (x)
소수점에 9 갯수와 0 갯수를 Count해서 올림/반올림 (채택)
가장 정확한 계산이 나올 때 까지 알고리즘을 계속해서 변경해줬다.
- 우선 9자리까지 표현해주던 수를 소수점 12자리로 변경했다.
- 처음에는 소수점 아래 "9"를 7개 이상 포함하면 올림 처리, "0"을 7개 이상 포함하면 내림 처리를 해줬다.
- 하지만 12.8과 같은 소수점이 있는 수를 입력 후 제곱, 제곱근을 계산하면 반올림 처리가 되서 제곱근을 하다가 제곱을 하면 13이 나오는 오류가 발생했다..
private double RoundCustom(double value, int decimals)
{
string stringValue = value.ToString($"F{decimals}");
int indexOfDecimal = stringValue.IndexOf('.');
if (indexOfDecimal != -1)
{
string decimalPart = stringValue.Substring(indexOfDecimal + 1);
// 내림 처리
if (decimalPart.Count(c => c == '0') >= 7)
{
return Math.Floor(value);
}
// 올림 처리
if (decimalPart.Count(c => c == '9') >= 7)
{
return Math.Ceiling(value);
}
}
return Math.Round(value, decimals);
}
그래서 어떻게 하면 더 정확도를 높힐까 생각했다.
- 연속되는 0000가 있으면 그 위치까지 내림 처리를 해준다.
- 연속되는 9999가 있으면 그 위에 소수점까지 올림 처리를 해준다.
- 소수점 아래 숫자가 "9"를 7개 이상 포함하면 정수 올림 처리를 해준다.
- 소수점 아래 숫자가 "0"을 7개 이상 포함하면 내림 처리를 해준다.
private double RoundCustom(double value, int decimals)
{
string stringValue = value.ToString($"F{decimals}");
int indexOfDecimal = stringValue.IndexOf('.');
if (indexOfDecimal != -1)
{
string decimalPart = stringValue.Substring(indexOfDecimal + 1);
// 내림 처리
if (decimalPart.Contains("0000"))
{
return Math.Floor(value * Math.Pow(10, decimals - 4)) / Math.Pow(10, decimals - 4);
}
// 올림 처리
if (decimalPart.Contains("9999"))
{
return Math.Ceiling(value * Math.Pow(10, decimals - 4)) / Math.Pow(10, decimals - 4);
}
}
// 정수에 대한 올림 처리
if (value >= 0 && value % 1 == 0 && value.ToString().Length >= 7)
{
return Math.Ceiling(value);
}
// 정수에 대한 내림 처리
if (value < 0 && value % 1 == 0 && value.ToString().Length >= 8)
{
return Math.Floor(value);
}
return Math.Round(value, decimals);
}
이게 지금까지는 정확도가 꽤 맞는 편이라고 생각한다.
물론 100% 정확도는 아니라서 오류가 있긴 하지만 어지간하면 근사치에 가장 가까운 값이 나오도록 했다.
부동 소수점이 계산에서 꽤나 까다로운 에러를 발생시킨다는 것을 깨달았다.
나중에 큰 프로젝트를 참여하게 된다면 해당 공부를 훨씬 정밀하게 해야겠다고 느꼈다.
반응형
'[C#] > [C# 윈폼] 혼자해보는 계산기 만들기' 카테고리의 다른 글
[C#] 계산기 전체 코드 (1) | 2024.01.11 |
---|---|
[C#] 소수점 처리 에러 (1) | 2024.01.10 |
[C#] 자릿수 표시 & 수식 표시 기능 추가 (0) | 2024.01.09 |
[C#] KeyEvent (0) | 2024.01.08 |
[C#] 윈도우폼 계산기 만들기(~ing) (1) | 2024.01.05 |