演算子

【C#】初心者でもすぐできる四則演算プログラムの作成方法と割り算の注意点

結論として、C#で四則演算を扱うには+ - * / %を数値型に適用し、結果をConsoleに表示するMainメソッドを用意すれば十分です。

整数割り算は小数点以下を切り捨てるため正確な値が必要ならdoubleへキャストし、除数が0かどうかを事前に確認すると安全です。

目次から探す
  1. 四則演算子の概要
  2. データ型の選び方と数値精度
  3. 割り算を安全に扱うテクニック
  4. 計算結果の出力とフォーマット
  5. プログラム構造の改善ポイント
  6. 例外処理とロギング
  7. デバッグとテスト
  8. パフォーマンスと最適化
  9. 数学ライブラリの利用拡張
  10. まとめ

四則演算子の概要

C#で計算を行う際に使う基本的な演算子には、足し算、引き算、掛け算、割り算、そして剰余があります。

ここではそれぞれの演算子の特徴や注意点をわかりやすく解説いたします。

足し算 +

足し算は、数値を合計するための演算子です。

C#では+を使って表現します。

正数同士の加算

正の整数同士を足し算する場合は、単純に値を合計します。

例えば、10と3を足すと13になります。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 3;
        int sum = a + b; // 足し算
        Console.WriteLine($"足し算の結果: {sum}"); // 13と表示されます
    }
}
足し算の結果: 13

このように、+演算子は数値の合計を求めるのに使います。

注意:文字列連結との違い

+演算子は数値だけでなく、文字列の連結にも使えます。

例えば、文字列 "Hello""World"+ でつなげると "HelloWorld" になります。

using System;
class Program
{
    static void Main()
    {
        string greeting = "Hello";
        string target = "World";
        string message = greeting + " " + target; // 文字列連結
        Console.WriteLine(message); // "Hello World" と表示されます
    }
}
Hello World

数値と文字列を混ぜて使う場合は、数値が自動的に文字列に変換されて連結されるため、意図しない結果になることもあります。

例えば、

int x = 5;
string result = "値は" + x + 10;
Console.WriteLine(result);
値は510

この場合、x + 10 の計算はされず、"値は5""10" が連結されて "値は510" と表示されます。

計算したい場合は括弧で囲みましょう。

string result = "値は" + (x + 10);
Console.WriteLine(result); // "値は15"
値は15

引き算 –

引き算は、ある数値から別の数値を減じる演算子です。

-を使います。

符号付き計算

C#の整数型は符号付きであるため、負の数も扱えます。

例えば、10から3を引くと7ですが、3から10を引くと-7になります。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 3;
        int diff1 = a - b; // 7
        int diff2 = b - a; // -7
        Console.WriteLine($"10 - 3 = {diff1}");
        Console.WriteLine($"3 - 10 = {diff2}");
    }
}
10 - 3 = 7
3 - 10 = -7

このように、引き算は符号を考慮した計算が可能です。

掛け算 *

掛け算は、2つの数値をかけ合わせる演算子です。

*を使います。

オーバーフローの危険性

整数型の掛け算では、結果が型の最大値を超えるとオーバーフローが発生します。

例えば、int型の最大値は約21億ですが、これを超えると値が不正になります。

using System;
class Program
{
    static void Main()
    {
        int max = int.MaxValue;
        int result = max * 2; // オーバーフローが発生
        Console.WriteLine($"int.MaxValue * 2 = {result}");
    }
}
int.MaxValue * 2 = -2

このように、オーバーフローが起きると予期しない負の値になることがあります。

オーバーフローを検出したい場合は、checkedキーワードを使うと例外が発生します。

using System;
class Program
{
    static void Main()
    {
        int max = int.MaxValue;
        try
        {
            int result = checked(max * 2);
            Console.WriteLine(result);
        }
        catch (OverflowException)
        {
            Console.WriteLine("オーバーフローが発生しました");
        }
    }
}
オーバーフローが発生しました

割り算 /

割り算は、ある数値を別の数値で割る演算子です。

/を使います。

整数と浮動小数の結果差

整数型同士の割り算は、小数点以下が切り捨てられます。

例えば、10 / 33になります。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 3;
        int quotient = a / b; // 3
        Console.WriteLine($"整数の割り算 10 / 3 = {quotient}");
    }
}
整数の割り算 10 / 3 = 3

小数点以下も含めて計算したい場合は、double型を使うか、片方の値をdoubleにキャストします。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 3;
        double quotient1 = (double)a / b; // 3.3333...
        double quotient2 = a / (double)b; // 3.3333...
        Console.WriteLine($"浮動小数の割り算 (double)a / b = {quotient1}");
        Console.WriteLine($"浮動小数の割り算 a / (double)b = {quotient2}");
    }
}
浮動小数の割り算 (double)a / b = 3.3333333333333335
浮動小数の割り算 a / (double)b = 3.3333333333333335

割り算の除数が0の場合は例外が発生するため、必ず0でないことを確認してください。

剰余 %

剰余演算子は、割り算の余りを求めるために使います。

%を使います。

奇数判定の典型例

剰余演算は、数値が奇数か偶数かを判定するのに便利です。

例えば、2で割った余りが1なら奇数、0なら偶数です。

using System;
class Program
{
    static void Main()
    {
        int number = 7;
        if (number % 2 == 1)
        {
            Console.WriteLine($"{number} は奇数です");
        }
        else
        {
            Console.WriteLine($"{number} は偶数です");
        }
    }
}
7 は奇数です

このように、剰余演算は条件判定にもよく使われます。

演算子の優先順位と括弧の活用

四則演算子には優先順位があり、掛け算や割り算は足し算や引き算よりも先に計算されます。

例えば、

int result = 2 + 3 * 4; // 2 + (3 * 4) = 14

この場合、3 * 4が先に計算されてから2が足されます。

計算の順序を変えたい場合は、括弧を使います。

int result = (2 + 3) * 4; // (2 + 3) * 4 = 20

括弧を使うことで、計算の意図を明確にし、誤解を防げます。

複雑な式を書くときは、必ず括弧で優先順位を明示しましょう。

データ型の選び方と数値精度

C#で四則演算を行う際、使うデータ型によって計算結果の精度や扱える値の範囲が変わります。

ここでは代表的な数値型の特徴や注意点を詳しく説明します。

整数型 int long

整数型は小数点を含まない数値を扱います。

intlongがよく使われます。

範囲とパフォーマンス

  • intは32ビット符号付き整数で、範囲は-2,147,483,648から2,147,483,647までです。多くの用途で十分な範囲を持ち、計算も高速です
  • longは64ビット符号付き整数で、範囲は-9,223,372,036,854,775,808から9,223,372,036,854,775,807までです。非常に大きな整数を扱う場合に使いますが、intより計算速度はわずかに遅くなります
using System;
class Program
{
    static void Main()
    {
        int intValue = 1000000;
        long longValue = 1000000000000;
        Console.WriteLine($"intの値: {intValue}");
        Console.WriteLine($"longの値: {longValue}");
    }
}
intの値: 1000000
longの値: 1000000000000

パフォーマンス面では、intの方がCPUの処理に最適化されているため高速です。

特に大量の計算を行う場合は、必要に応じて型を選びましょう。

浮動小数型 float double

小数点を含む数値を扱う型です。

floatは単精度、doubleは倍精度の浮動小数点数です。

丸め誤差

浮動小数点型は内部で2進数の近似値として数値を表現するため、丸め誤差が発生します。

例えば、0.1を正確に表現できません。

using System;
class Program
{
    static void Main()
    {
        float f = 0.1f;
        double d = 0.1;
        Console.WriteLine($"floatの値: {f}");
        Console.WriteLine($"doubleの値: {d}");
        Console.WriteLine($"float + float: {f + f + f + f + f + f + f + f + f + f}"); // 0.99999994などになることも
    }
}
floatの値: 0.1
doubleの値: 0.1
float + float: 1

実際にはfloatの合計は1に非常に近いですが、厳密には誤差が含まれます。

doublefloatより精度が高いですが、完全な正確さは保証されません。

金融計算など誤差が許されない場合は注意が必要です。

高精度型 decimal

decimal型は128ビットの高精度浮動小数点数で、特に金融計算に適しています。

10進数を正確に表現できるため、丸め誤差が少ないです。

金額計算への適用

金額計算では、decimalを使うことで誤差を防げます。

using System;
class Program
{
    static void Main()
    {
        decimal price = 19.99m;
        decimal quantity = 3m;
        decimal total = price * quantity;
        Console.WriteLine($"合計金額: {total}"); // 59.97と正確に表示されます
    }
}
合計金額: 59.97

mサフィックスを付けることでdecimalリテラルを表現します。

floatdoubleより計算結果の誤差が少なく、金額や精密な小数計算におすすめです。

巨大整数 BigInteger

BigIntegerは任意の大きさの整数を扱える型で、System.Numerics名前空間にあります。

導入方法と制限

BigIntegerを使うには、プロジェクトにSystem.Numericsを参照し、using System.Numerics;を追加します。

サイズ制限がなく、メモリが許す限り大きな整数を扱えますが、計算速度はintlongより遅くなります。

using System;
using System.Numerics;
class Program
{
    static void Main()
    {
        BigInteger big1 = BigInteger.Pow(10, 50); // 10の50乗
        BigInteger big2 = BigInteger.Parse("123456789012345678901234567890");
        BigInteger sum = big1 + big2;
        Console.WriteLine($"BigIntegerの合計: {sum}");
    }
}
BigIntegerの合計: 100000000000000000000000000000123456789012345678901234567890

巨大な数値を扱う必要がある場合に便利ですが、通常の四則演算より計算コストが高い点に注意してください。

型変換とキャスト

異なる数値型間で計算する際は、型変換が必要になることがあります。

C#では暗黙変換と明示的キャストがあります。

暗黙変換

小さい型から大きい型への変換は自動で行われます。

例えば、intからlongfloatからdoubleなどです。

int i = 100;
long l = i; // 暗黙変換
double d = l; // 暗黙変換
Console.WriteLine($"int: {i}, long: {l}, double: {d}");
int: 100, long: 100, double: 100

明示的キャスト

大きい型から小さい型への変換は明示的にキャストしないとコンパイルエラーになります。

例えば、doubleからintへの変換はキャストが必要です。

double d = 9.99;
int i = (int)d; // 小数点以下は切り捨てられます
Console.WriteLine($"double: {d}, intにキャスト後: {i}");
double: 9.99, intにキャスト後: 9

キャスト時にデータが失われる可能性があるため、注意が必要です。

checked と unchecked キーワード

整数型の演算でオーバーフローが起きた場合、デフォルトでは例外は発生せず値が不正になります。

checkeduncheckedでオーバーフローの検出を制御できます。

オーバーフロー検出

checkedブロック内でオーバーフローが起きるとOverflowExceptionが発生します。

using System;
class Program
{
    static void Main()
    {
        int max = int.MaxValue;
        try
        {
            int result = checked(max + 1);
            Console.WriteLine(result);
        }
        catch (OverflowException)
        {
            Console.WriteLine("オーバーフローが検出されました");
        }
    }
}
オーバーフローが検出されました

一方、uncheckedではオーバーフローが無視され、値が循環します。

using System;
class Program
{
    static void Main()
    {
        int max = int.MaxValue;
        int result = unchecked(max + 1);
        Console.WriteLine(result); // -2147483648 と表示される
    }
}
-2147483648

通常はcheckedを使って安全に計算し、必要に応じてuncheckedでパフォーマンスを優先する使い分けが行われます。

割り算を安全に扱うテクニック

割り算は計算の基本ですが、注意しないとエラーや誤った結果を招くことがあります。

ここでは安全に割り算を行うためのポイントを具体的に説明します。

0除算の検出

割り算で最も注意すべきは、除数が0になることです。

0で割ると実行時に例外が発生するため、必ず除数が0でないかを確認しましょう。

条件分岐での回避

割り算を行う前に、if文などで除数が0かどうかをチェックします。

0の場合は計算をスキップしたり、エラーメッセージを表示したりします。

using System;
class Program
{
    static void Main()
    {
        int numerator = 10;
        int denominator = 0;
        if (denominator == 0)
        {
            Console.WriteLine("エラー: 0で割ることはできません");
        }
        else
        {
            int result = numerator / denominator;
            Console.WriteLine($"割り算の結果: {result}");
        }
    }
}
エラー: 0で割ることはできません

このように条件分岐で除数をチェックすることで、例外を未然に防げます。

小数点以下を保持する方法

整数同士の割り算は小数点以下が切り捨てられます。

小数点以下も含めて計算したい場合は、片方の値を浮動小数点型に変換します。

片方をdoubleで計算

例えば、int型の変数をdoubleにキャストして割り算を行うと、正確な小数点以下の値が得られます。

using System;
class Program
{
    static void Main()
    {
        int a = 10;
        int b = 3;
        double result = (double)a / b;
        Console.WriteLine($"小数点以下を含む割り算の結果: {result}");
    }
}
小数点以下を含む割り算の結果: 3.3333333333333335

この方法で、整数の割り算でも小数点以下の値を保持できます。

商と余りを同時に求める Math.DivRem

商と余りを同時に取得したい場合は、Math.DivRemメソッドが便利です。

これは割り算の商を返し、余りをoutパラメータで受け取れます。

using System;
class Program
{
    static void Main()
    {
        int dividend = 17;
        int divisor = 4;
        int remainder;
        int quotient = Math.DivRem(dividend, divisor, out remainder);
        Console.WriteLine($"商: {quotient}");
        Console.WriteLine($"余り: {remainder}");
    }
}
商: 4
余り: 1

Math.DivRemは一度の呼び出しで商と余りを効率よく取得できるため、計算の手間を減らせます。

切り上げ・切り捨て・四捨五入

割り算の結果を整数に変換する際、切り上げや切り捨て、四捨五入を使い分けることがあります。

C#のMathクラスに便利なメソッドが用意されています。

Math.Ceiling

Math.Ceilingは小数点以下を切り上げて、指定した数値以上の最小の整数を返します。

戻り値はdouble型です。

using System;
class Program
{
    static void Main()
    {
        double value = 3.1;
        double ceiling = Math.Ceiling(value);
        Console.WriteLine($"切り上げ: {ceiling}");
    }
}
切り上げ: 4

Math.Floor

Math.Floorは小数点以下を切り捨てて、指定した数値以下の最大の整数を返します。

using System;
class Program
{
    static void Main()
    {
        double value = 3.9;
        double floor = Math.Floor(value);
        Console.WriteLine($"切り捨て: {floor}");
    }
}
切り捨て: 3

Math.Round

Math.Roundは四捨五入を行います。

小数点以下の桁数を指定することも可能です。

using System;
class Program
{
    static void Main()
    {
        double value = 3.456;
        double rounded = Math.Round(value, 2);
        Console.WriteLine($"四捨五入(小数点以下2桁): {rounded}");
    }
}
四捨五入(小数点以下2桁): 3.46

これらのメソッドを使い分けることで、割り算の結果を用途に応じて適切に処理できます。

decimal を使った正確な割り算

decimal型は金融計算などで誤差を抑えたい場合に使います。

decimal同士の割り算は丸め誤差が少なく、正確な結果が得られます。

using System;
class Program
{
    static void Main()
    {
        decimal price = 100m;
        decimal quantity = 3m;
        decimal unitPrice = price / quantity;
        Console.WriteLine($"1個あたりの価格: {unitPrice}");
    }
}
1個あたりの価格: 33.3333333333333333333333333333

decimalは小数点以下28~29桁まで正確に扱えるため、金額計算などでの割り算に適しています。

負の数と剰余の符号

割り算や剰余演算で負の数を扱う場合、符号の扱いに注意が必要です。

C#の剰余演算子%は被除数(割られる数)の符号を結果に引き継ぎます。

using System;
class Program
{
    static void Main()
    {
        int a = -10;
        int b = 3;
        int remainder = a % b;
        Console.WriteLine($"-10 % 3 = {remainder}");
    }
}
-10 % 3 = -1

この例では、余りは-1となり、被除数の符号がそのまま残っています。

割り算の商は切り捨て方向に丸められます。

符号の扱いが異なる言語もあるため、負の数を使う場合は結果をよく確認してください。

必要に応じて符号を調整する処理を追加しましょう。

計算結果の出力とフォーマット

計算結果を見やすく表示するためには、適切なフォーマットを使うことが重要です。

C#では標準書式指定や文字列補間、カルチャ対応など多彩な方法が用意されています。

標準書式指定

数値を文字列に変換する際、書式指定子を使うことで表示形式を簡単に調整できます。

固定小数点 “F”

"F"は固定小数点形式で、小数点以下の桁数を指定できます。

例えば、小数点以下2桁で表示したい場合は"F2"を使います。

using System;
class Program
{
    static void Main()
    {
        double value = 123.456789;
        Console.WriteLine(value.ToString("F2")); // 小数点以下2桁で表示
        Console.WriteLine(value.ToString("F0")); // 小数点以下なしで表示
    }
}
123.46
123

四捨五入されて表示されるため、金額や計算結果の見た目を整えるのに便利です。

数字区切り “N”

"N"は数値に3桁ごとのカンマ区切りを付け、小数点以下の桁数も指定できます。

例えば"N0"は小数点以下なしでカンマ区切りを付けます。

using System;
class Program
{
    static void Main()
    {
        int largeNumber = 1234567890;
        Console.WriteLine(largeNumber.ToString("N0")); // カンマ区切り、小数点なし
        Console.WriteLine(largeNumber.ToString("N2")); // カンマ区切り、小数点以下2桁
    }
}
1,234,567,890
1,234,567,890.00

大きな数値を見やすく表示したいときに役立ちます。

文字列補間 $””

C#の文字列補間機能を使うと、変数や式を文字列の中に直接埋め込めます。

書式指定子も併用可能です。

using System;
class Program
{
    static void Main()
    {
        double price = 1234.5678;
        int quantity = 3;
        string message = $"価格: {price:F2}円, 数量: {quantity:N0}個";
        Console.WriteLine(message);
    }
}
価格: 1234.57円, 数量: 3個

{変数名:書式指定子}の形で簡単にフォーマットでき、コードが読みやすくなります。

カルチャを考慮したフォーマッタ

数値や日付の表示は文化圏によって異なります。

C#ではCultureInfoを使ってカルチャに応じたフォーマットが可能です。

CultureInfo

System.Globalization.CultureInfoを指定して、数値の区切り文字や小数点の表現を変えられます。

using System;
using System.Globalization;
class Program
{
    static void Main()
    {
        double value = 1234567.89;
        CultureInfo us = new CultureInfo("en-US");
        CultureInfo jp = new CultureInfo("ja-JP");
        CultureInfo fr = new CultureInfo("fr-FR");
        Console.WriteLine(value.ToString("N2", us)); // 1,234,567.89
        Console.WriteLine(value.ToString("N2", jp)); // 1,234,567.89
        Console.WriteLine(value.ToString("N2", fr)); // 1 234 567,89 (フランスは小数点がカンマ)
    }
}
1,234,567.89
1,234,567.89
1 234 567,89

カルチャを指定することで、ユーザーの地域に合わせた表示ができます。

コンソール以外の出力先

計算結果はコンソールだけでなく、ファイルやGUIアプリケーションの画面にも出力できます。

ファイルへ書き込み

System.IO名前空間のFile.WriteAllTextStreamWriterを使って、計算結果をテキストファイルに保存できます。

using System;
using System.IO;
class Program
{
    static void Main()
    {
        double result = 1234.5678;
        string formatted = result.ToString("F2");
        string path = "result.txt";
        File.WriteAllText(path, $"計算結果: {formatted}");
        Console.WriteLine($"結果をファイルに書き込みました: {path}");
    }
}
結果をファイルに書き込みました: result.txt

ファイルを開くと計算結果: 1234.57と保存されています。

GUIコンポーネントへの表示

WindowsフォームやWPFなどのGUIアプリケーションでは、ラベルやテキストボックスに計算結果を表示します。

// Windowsフォームの例(MainForm.csの一部)
private void ShowResult(double value)
{
    labelResult.Text = $"結果: {value:F2}";
}

GUIでは文字列補間や書式指定を使って見やすく表示し、ユーザーにわかりやすいインターフェースを提供します。

プログラム構造の改善ポイント

四則演算プログラムを作成するとき、コードの可読性や保守性を高めるためにプログラム構造を工夫することが重要です。

ここではメソッド化やクラス設計、インターフェースの活用、拡張メソッドの利用について具体的に説明します。

メソッド化で可読性向上

処理を小さな単位に分割してメソッド化すると、コードが整理されて読みやすくなります。

入力、計算、表示の処理をそれぞれ別のメソッドに分けるのが基本です。

入力処理メソッド

ユーザーからの入力を受け取り、適切な型に変換して返すメソッドを作ります。

入力の検証もここで行うとよいでしょう。

using System;
class Program
{
    static void Main()
    {
        int number1 = ReadInt("1つ目の整数を入力してください: ");
        int number2 = ReadInt("2つ目の整数を入力してください: ");
        Console.WriteLine($"入力された値: {number1}, {number2}");
    }
    static int ReadInt(string prompt)
    {
        int result;
        while (true)
        {
            Console.Write(prompt);
            string input = Console.ReadLine();
            if (int.TryParse(input, out result))
            {
                return result;
            }
            Console.WriteLine("整数を入力してください。");
        }
    }
}
1つ目の整数を入力してください: 10
2つ目の整数を入力してください: abc
整数を入力してください。
2つ目の整数を入力してください: 5
入力された値: 10, 5

このように入力処理をメソッド化すると、入力の検証ロジックが分かりやすくなり、再利用も簡単です。

計算メソッド

四則演算の計算処理をメソッドにまとめると、計算ロジックの変更や拡張が容易になります。

static int Add(int a, int b) => a + b;
static int Subtract(int a, int b) => a - b;
static int Multiply(int a, int b) => a * b;
static int Divide(int a, int b)
{
    if (b == 0) throw new DivideByZeroException("0で割ることはできません。");
    return a / b;
}

これらのメソッドを使うことで、計算部分が明確になり、エラー処理も集中管理できます。

表示メソッド

計算結果を表示する処理もメソッドに分けると、表示形式の変更が簡単です。

static void ShowResult(string operation, int result)
{
    Console.WriteLine($"{operation}の結果: {result}");
}

メイン処理では計算メソッドと表示メソッドを組み合わせて使います。

クラス設計と責務分割

複数の機能を持つプログラムでは、クラスを使って責務を分割すると管理しやすくなります。

四則演算専用のCalculatorクラスを作るのが典型例です。

Calculator クラス

using System;
class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Subtract(int a, int b) => a - b;
    public int Multiply(int a, int b) => a * b;
    public int Divide(int a, int b)
    {
        if (b == 0) throw new DivideByZeroException("0で割ることはできません。");
        return a / b;
    }
}

このクラスに計算ロジックを集約することで、他の部分と分離され、テストや拡張がしやすくなります。

class Program
{
    static void Main()
    {
        var calc = new Calculator();
        int a = 10;
        int b = 5;
        Console.WriteLine($"足し算: {calc.Add(a, b)}");
        Console.WriteLine($"引き算: {calc.Subtract(a, b)}");
        Console.WriteLine($"掛け算: {calc.Multiply(a, b)}");
        Console.WriteLine($"割り算: {calc.Divide(a, b)}");
    }
}
足し算: 15
引き算: 5
掛け算: 50
割り算: 2

インターフェースでテスト容易性を確保

インターフェースを導入すると、実装の差し替えやモック化が容易になり、単体テストがしやすくなります。

ICalculator の導入

public interface ICalculator
{
    int Add(int a, int b);
    int Subtract(int a, int b);
    int Multiply(int a, int b);
    int Divide(int a, int b);
}
class Calculator : ICalculator
{
    public int Add(int a, int b) => a + b;
    public int Subtract(int a, int b) => a - b;
    public int Multiply(int a, int b) => a * b;
    public int Divide(int a, int b)
    {
        if (b == 0) throw new DivideByZeroException();
        return a / b;
    }
}

テストコードではICalculatorをモック実装に差し替えられるため、計算ロジック以外の部分のテストが容易になります。

// テスト例(擬似コード)
class CalculatorTests
{
    void TestAdd()
    {
        ICalculator calc = new Calculator();
        Assert.Equal(5, calc.Add(2, 3));
    }
}

拡張メソッドで演算機能を追加

既存の型やクラスに新しいメソッドを追加したい場合、拡張メソッドを使うと便利です。

例えば、int型に四則演算の拡張メソッドを追加できます。

public static class IntExtensions
{
    public static int Add(this int a, int b) => a + b;
    public static int Subtract(this int a, int b) => a - b;
    public static int Multiply(this int a, int b) => a * b;
    public static int Divide(this int a, int b)
    {
        if (b == 0) throw new DivideByZeroException();
        return a / b;
    }
}

使い方は以下の通りです。

using System;
class Program
{
    static void Main()
    {
        int x = 10;
        int y = 2;
        Console.WriteLine(x.Add(y));       // 12
        Console.WriteLine(x.Subtract(y));  // 8
        Console.WriteLine(x.Multiply(y));  // 20
        Console.WriteLine(x.Divide(y));    // 5
    }
}
12
8
20
5

拡張メソッドは既存の型を変更せずに機能を追加できるため、柔軟な設計が可能です。

例外処理とロギング

プログラムで割り算などの計算を行う際、予期せぬエラーが発生することがあります。

例外処理を適切に行い、エラー情報をログに残すことで、安定した動作とトラブルシューティングが可能になります。

try-catch でエラーを捕捉

try-catch構文を使うと、例外が発生した際にプログラムの異常終了を防ぎ、エラー処理を行えます。

割り算で特に注意すべきはゼロ除算です。

特定の例外 DivideByZeroException

割り算の除数が0の場合、DivideByZeroExceptionが発生します。

これを捕捉して適切に処理しましょう。

using System;
class Program
{
    static void Main()
    {
        int numerator = 10;
        int denominator = 0;
        try
        {
            int result = numerator / denominator;
            Console.WriteLine($"結果: {result}");
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("エラー: 0で割ることはできません。");
            Console.WriteLine($"例外メッセージ: {ex.Message}");
        }
    }
}
エラー: 0で割ることはできません。
例外メッセージ: Attempted to divide by zero.

このように特定の例外を捕捉することで、ユーザーにわかりやすいメッセージを表示し、プログラムの異常終了を防げます。

finally ブロックの使いどころ

try-catchに加えてfinallyブロックを使うと、例外の有無にかかわらず必ず実行したい処理を記述できます。

例えば、リソースの解放や後片付けに使います。

using System;
class Program
{
    static void Main()
    {
        int numerator = 10;
        int denominator = 0;
        try
        {
            int result = numerator / denominator;
            Console.WriteLine($"結果: {result}");
        }
        catch (DivideByZeroException)
        {
            Console.WriteLine("0で割ることはできません。");
        }
        finally
        {
            Console.WriteLine("計算処理が終了しました。");
        }
    }
}
0で割ることはできません。
計算処理が終了しました。

finallyは例外が発生しても必ず実行されるため、ファイルやデータベース接続のクローズ処理などに適しています。

ログフレームワークの活用

エラーや重要な処理の記録は、トラブルシューティングや運用監視に欠かせません。

C#では様々なログフレームワークが利用可能です。

Serilog

Serilogは構成が簡単で柔軟なログライブラリです。

コンソールやファイル、クラウドサービスなど多彩な出力先に対応しています。

using System;
using Serilog;
class Program
{
    static void Main()
    {
        Log.Logger = new LoggerConfiguration()
            .WriteTo.Console()
            .WriteTo.File("log.txt")
            .CreateLogger();
        try
        {
            int a = 10;
            int b = 0;
            int result = a / b;
            Console.WriteLine(result);
        }
        catch (DivideByZeroException ex)
        {
            Log.Error(ex, "ゼロ除算が発生しました");
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }
}

このコードは例外発生時にコンソールとファイルにエラーログを出力します。

Serilogは構成が柔軟で、ログレベルや出力形式も細かく設定可能です。

標準TraceAPI

.NET標準のSystem.Diagnostics.Traceもログ出力に使えます。

簡単なログ記録やデバッグに適しています。

using System;
using System.Diagnostics;
class Program
{
    static void Main()
    {
        Trace.Listeners.Add(new TextWriterTraceListener("trace.log"));
        Trace.AutoFlush = true;
        try
        {
            int a = 10;
            int b = 0;
            int result = a / b;
            Console.WriteLine(result);
        }
        catch (DivideByZeroException ex)
        {
            Trace.TraceError($"ゼロ除算エラー: {ex.Message}");
        }
    }
}

Traceは軽量で設定も簡単ですが、Serilogのような高度な機能はありません。

用途に応じて使い分けるとよいでしょう。

デバッグとテスト

プログラムの品質を高めるためには、デバッグやテストを効果的に行うことが欠かせません。

ここではVisual Studioなどのデバッガの使い方や、単体テストの作成方法、自動化ビルドによる品質維持について説明します。

デバッガでステップ実行

デバッガを使うと、プログラムを一行ずつ実行しながら変数の値や処理の流れを確認できます。

これにより、バグの原因を特定しやすくなります。

Visual Studioでは、ブレークポイントを設定してプログラムの実行を一時停止し、ステップ実行(Step Into、Step Over、Step Out)で細かく動作を追えます。

ウォッチ式の活用

ウォッチ式は、特定の変数や式の値をリアルタイムで監視できる機能です。

デバッグ中に変数の変化を追跡したり、計算結果を確認したりするのに便利です。

例えば、計算中の変数resultの値をウォッチに追加すると、ステップ実行のたびに最新の値が表示されます。

Visual Studioのウォッチウィンドウにresulta + bなどの式を入力すると、現在の値が表示されます。

これにより、計算ロジックの動作確認が容易になります。

単体テストの作成

単体テストは、プログラムの各機能が正しく動作するかを自動で検証するテストです。

C#ではxUnitNUnitMSTestなどのフレームワークがよく使われます。

xUnit の基本

xUnitはシンプルで使いやすい単体テストフレームワークです。

テストメソッドには[Fact]属性を付けて定義します。

using Xunit;
public class CalculatorTests
{
    [Fact]
    public void Add_ReturnsCorrectSum()
    {
        var calc = new Calculator();
        int result = calc.Add(2, 3);
        Assert.Equal(5, result);
    }
}

この例では、Addメソッドが正しく2つの数値を足し合わせるかを検証しています。

テストが失敗すると、どの部分が問題かがわかりやすく表示されます。

境界値テスト

境界値テストは、入力の境界付近の値を重点的にテストする手法です。

例えば、割り算の除数が0や1、非常に大きな値の場合などをテストします。

[Fact]
public void Divide_ByZero_ThrowsException()
{
    var calc = new Calculator();
    Assert.Throws<DivideByZeroException>(() => calc.Divide(10, 0));
}
[Fact]
public void Divide_ByOne_ReturnsSameNumber()
{
    var calc = new Calculator();
    int result = calc.Divide(10, 1);
    Assert.Equal(10, result);
}

境界値テストを行うことで、エッジケースでの不具合を防げます。

自動化されたビルドで品質維持

継続的インテグレーション(CI)ツールを使うと、コードの変更があるたびに自動でビルドとテストが実行されます。

これにより、問題を早期に発見しやすくなり、品質を維持できます。

GitHub ActionsやAzure DevOps、Jenkinsなどが代表的なCIツールです。

例えばGitHub Actionsでは、リポジトリにワークフローファイルを追加して、プッシュ時にdotnet testコマンドを実行し、テスト結果を自動で確認できます。

name: .NET Core CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:

    - uses: actions/checkout@v2
    - name: Setup .NET

      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 6.0.x

    - name: Restore dependencies

      run: dotnet restore

    - name: Build

      run: dotnet build --no-restore

    - name: Test

      run: dotnet test --no-build --verbosity normal

このように自動化されたビルドとテストを導入することで、開発の効率化と品質向上が期待できます。

パフォーマンスと最適化

計算処理を高速化するためには、コードの書き方や利用する技術を工夫することが重要です。

特に大量のデータを扱う場合やリアルタイム処理では、パフォーマンスの最適化が求められます。

ループ内演算の効率化

ループ内での計算は繰り返し実行されるため、無駄な処理を減らすことがパフォーマンス向上につながります。

以下のポイントに注意しましょう。

  • 定数計算のループ外移動

ループ内で変わらない値の計算は、ループの外に出して一度だけ計算します。

int[] numbers = new int[1000];
int length = numbers.Length; // ループ外で取得
for (int i = 0; i < length; i++)
{
    numbers[i] = i * 2;
}
  • 変数のキャッシュ

プロパティやメソッド呼び出しの結果をループ内で何度も使う場合は、一時変数に格納しておくと効率的です。

  • 不要なメソッド呼び出しの削減

ループ内でのメソッド呼び出しはコストがかかるため、可能な限り減らします。

  • 配列やリストのアクセス最適化

インデックスアクセスは高速ですが、LINQや複雑なクエリはループ外でまとめて処理するほうが良い場合があります。

SIMD演算 Vector<T>

SIMD(Single Instruction Multiple Data)は、CPUのベクトル命令を利用して複数のデータを同時に処理する技術です。

C#ではSystem.Numerics.Vector<T>を使ってSIMD演算が可能です。

using System;
using System.Numerics;
class Program
{
    static void Main()
    {
        float[] data1 = new float[Vector<float>.Count];
        float[] data2 = new float[Vector<float>.Count];
        for (int i = 0; i < data1.Length; i++)
        {
            data1[i] = i;
            data2[i] = i * 2;
        }
        var vector1 = new Vector<float>(data1);
        var vector2 = new Vector<float>(data2);
        Vector<float> result = vector1 + vector2;
        for (int i = 0; i < result.Count; i++)
        {
            Console.WriteLine(result[i]);
        }
    }
}
0
3
6
9
...

この例では、複数の浮動小数点数を一度に加算しています。

SIMDを使うと、ループで一つずつ計算するよりも高速に処理できます。

ただし、データのサイズやCPUの対応状況によって効果は異なります。

非同期処理とCPUバインドの注意点

非同期処理はI/O待ちなどの待機時間を効率化するために有効ですが、CPUバインド(CPUリソースを多く使う計算処理)には注意が必要です。

  • CPUバインド処理は非同期化しても効果が薄い

計算処理自体はCPUを使い切るため、非同期にしても処理時間は変わりません。

  • Task.Runで別スレッドに処理を移す

重い計算をUIスレッドから切り離すためにTask.Runを使うことがありますが、スレッド切り替えのオーバーヘッドも考慮しましょう。

using System;
using System.Threading.Tasks;
class Program
{
    static async Task Main()
    {
        int result = await Task.Run(() => HeavyComputation(1000000));
        Console.WriteLine($"計算結果: {result}");
    }
    static int HeavyComputation(int n)
    {
        int sum = 0;
        for (int i = 0; i < n; i++)
        {
            sum += i;
        }
        return sum;
    }
}
  • 並列処理の活用

Parallel.ForPLINQを使うと、複数コアを活用してCPUバインド処理を高速化できます。

using System;
using System.Threading.Tasks;
class Program
{
    static void Main()
    {
        int[] data = new int[1000000];
        Parallel.For(0, data.Length, i =>
        {
            data[i] = i * 2;
        });
        Console.WriteLine("並列処理が完了しました。");
    }
}

非同期処理と並列処理は目的や処理内容に応じて使い分けることが重要です。

CPUバインドの計算は並列化で効率化し、I/O待ちなどは非同期で効率化しましょう。

数学ライブラリの利用拡張

C#では標準ライブラリに加え、数学的な計算を効率的に行うための専用ライブラリが用意されています。

特にSystem.Numerics名前空間は数値計算の拡張に便利で、行列やベクトル演算にも応用できます。

System.Numerics の活用

System.Numericsは大きな整数や複素数、ベクトル演算などをサポートするライブラリです。

代表的な型にはBigIntegerComplexVector<T>があります。

  • BigInteger

任意の大きさの整数を扱えます。

通常の整数型の範囲を超える計算に便利です。

using System;
using System.Numerics;
class Program
{
    static void Main()
    {
        BigInteger big1 = BigInteger.Pow(10, 30);
        BigInteger big2 = BigInteger.Parse("123456789012345678901234567890");
        BigInteger sum = big1 + big2;
        Console.WriteLine($"BigIntegerの合計: {sum}");
    }
}
BigIntegerの合計: 100000000000000000000000000000123456789012345678901234567890
  • Complex

複素数の計算を簡単に行えます。

実部と虚部を持ち、加減乗除や絶対値、偏角の計算が可能です。

using System;
using System.Numerics;
class Program
{
    static void Main()
    {
        Complex c1 = new Complex(2, 3);
        Complex c2 = new Complex(1, -4);
        Complex product = c1 * c2;
        Console.WriteLine($"複素数の積: {product}");
    }
}
複素数の積: 14, -5
  • Vector<T>

SIMD命令を利用した高速なベクトル演算が可能です。

floatdoubleなどの数値型で使えます。

行列・ベクトル演算への応用

数学や物理、グラフィックスの分野では行列やベクトルの演算が頻繁に使われます。

C#ではSystem.NumericsVector3Matrix4x4などの型を利用してこれらの計算を効率的に行えます。

  • Vector3

3次元ベクトルを表し、加算、減算、内積、外積などの演算が可能です。

using System;
using System.Numerics;
class Program
{
    static void Main()
    {
        Vector3 v1 = new Vector3(1, 2, 3);
        Vector3 v2 = new Vector3(4, 5, 6);
        Vector3 sum = v1 + v2;
        float dot = Vector3.Dot(v1, v2);
        Vector3 cross = Vector3.Cross(v1, v2);
        Console.WriteLine($"ベクトルの和: {sum}");
        Console.WriteLine($"内積: {dot}");
        Console.WriteLine($"外積: {cross}");
    }
}
ベクトルの和: <5, 7, 9>
内積: 32
外積: <-3, 6, -3>
  • Matrix4x4

4×4の行列を表し、変換や回転、拡大縮小などの操作に使います。

3Dグラフィックスの計算でよく利用されます。

using System;
using System.Numerics;
class Program
{
    static void Main()
    {
        Matrix4x4 translation = Matrix4x4.CreateTranslation(10, 0, 0);
        Matrix4x4 rotation = Matrix4x4.CreateRotationY((float)(Math.PI / 4));
        Matrix4x4 transform = translation * rotation;
        Console.WriteLine("変換行列:");
        Console.WriteLine(transform);
    }
}
変換行列:
<0.707107, 0, 0.707107, 0>
<0, 1, 0, 0>
<-0.707107, 0, 0.707107, 0>
<10, 0, 0, 1>

これらの型を活用することで、複雑な数学的計算を効率的に実装でき、パフォーマンスも向上します。

特にゲーム開発や科学技術計算で重宝されます。

四則演算プログラムを作成していると、計算結果に関する疑問やトラブルが発生することがあります。

ここでは特に多い問題とその対処法を解説します。

小数点以下が消える

整数型同士で割り算を行うと、小数点以下が切り捨てられてしまいます。

例えば、10 / 3の結果は3となり、3.3333...とはなりません。

int a = 10;
int b = 3;
int result = a / b; // 結果は3

対処法

小数点以下も含めて計算したい場合は、片方または両方の変数を浮動小数点型doublefloatにキャストします。

double result = (double)a / b; // 3.3333...

これにより、正確な割り算結果が得られます。

計算結果が桁あふれする

整数型の範囲を超える計算を行うと、オーバーフロー(桁あふれ)が発生し、予期しない値になります。

例えば、int.MaxValueに1を足すと負の値になります。

int max = int.MaxValue;
int overflow = max + 1; // オーバーフローして負の値になる

対処法

  • より大きな型longBigIntegerを使う
  • checkedキーワードを使ってオーバーフローを検出し、例外を発生させる
try
{
    int result = checked(max + 1);
}
catch (OverflowException)
{
    Console.WriteLine("オーバーフローが発生しました");
}

マイナスの余りが想定と違う

C#の剰余演算子%は、被除数(割られる数)の符号を結果に引き継ぎます。

例えば、-10 % 3の結果は-1になります。

int remainder = -10 % 3; // -1

一方で、数学的には余りは常に非負であることが多いため、期待と異なる場合があります。

対処法

余りを常に非負にしたい場合は、以下のように調整します。

int mod = ((a % b) + b) % b;
int a = -10;
int b = 3;
int mod = ((a % b) + b) % b; // 2

この方法で、余りを0以上の値に変換できます。

用途に応じて符号の扱いを明確にしましょう。

まとめ

この記事では、C#での四則演算プログラム作成に必要な基本演算子の使い方から、データ型の選び方や割り算の注意点、計算結果の表示方法まで幅広く解説しました。

特に割り算の0除算回避や小数点以下の保持、オーバーフロー対策などの安全な計算方法を詳しく紹介しています。

また、プログラム構造の改善や例外処理、デバッグ・テストのポイントも押さえ、パフォーマンス最適化や数学ライブラリの活用法も理解できます。

これらを踏まえれば、初心者でも正確で効率的な四則演算プログラムを作成できるようになります。

関連記事

Back to top button
目次へ