FC2ブログ
ヽ|∵|ゝ(Fantom) の 開発blog? ホーム »C#ライブラリ
このページの記事一覧

【Unity】【C#】配列・リストのシャッフル  


 簡易音楽プレイヤーのデモを作っていて気づいたんだけど、C# にはシャッフル(Shuffle())の標準メソッドは無いのね。Java には Collections.shuffle() のメソッドがあるので、つい探してしまった…。あと要素のスワップ(Swap())も Java には標準であるんだよね(Collections.swap())。ちなみに私はプラグイン開発してるときは Java と C# を同時に書いているので、たまに混同する(笑)(実際に AndroidStudio とVisualSutdio の両方を開いている)。まぁ簡単に作れるものだけど、毎回書くのも面倒なのでとりあえずメモ的に残して置こうと思った。



(※) Unity 5.6.3p1 - 2018.1.5f1 / Windows10(x64) で確認



■配列のシャッフル

●配列のシャッフル(UnityEngine.Random / 拡張メソッド版)
using UnityEngine;
using Random = UnityEngine.Random;

public static class Extensions //拡張メソッド用 static クラス(名前は任意)
{
//配列の要素をシャッフルする (Fisher-Yates shuffle)
public static void Shuffle<T>(this T[] arr)
{
for (int i = arr.Length - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1); //[0]~[i]
T tmp = arr[i]; //swap
arr[i] = arr[j];
arr[j] = tmp;
}
}
}


//メインでは・・・
using System.Linq;

int[] arr = { 0, 1, 2, 3, 4, 5 };
for (int i = 0; i < 5; i++)
{
arr.Shuffle();
Debug.Log(arr.Select(e => e.ToString()).Aggregate((s, a) => s + ", " + a));
}

4, 3, 1, 5, 2, 0
5, 4, 3, 2, 0, 1
0, 3, 5, 1, 2, 4
1, 5, 0, 2, 4, 3
0, 1, 2, 4, 3, 5

 使っているアルゴリズムは有名な「フィッシャー–イェーツのシャッフル」(英:Fisher–Yates shuffle)である。詳細は Wikipedia を見て欲しいが、簡単に言えばインデクス(i)を後ろから1つずつ前方へ移動しながら、0~i の範囲(終点は[1](と[0]))でランダムに要素をスワップするアルゴリズムである。オーダーは O(n) なので巨大な配列で毎フレーム更新とかではなければ(笑)、それほど負荷は無いだろう。音楽プレイヤーではどの様に使ってるかというと、曲順を1度シャッフルして、その順番に再生し、最後まで行ったらまたシャッフル…を繰り返しているだけである。なので、ほとんど負荷は無いと言って良い。そういう使い方の場合、何の問題も無いと考えて良いだろう(Unityの様な毎フレーム更新のアプリケーションはなるべくオーダーの小さいアルゴリズム[O(1)とかO(logn)とか]を採用する方が良い)。

 またついでなので要素のスワップも定義して置くと、以下のように簡潔に書ける。

●配列のシャッフル+要素のスワップ(UnityEngine.Random / 拡張メソッド版)
using UnityEngine;
using Random = UnityEngine.Random;

public static class Extensions //拡張メソッド用 static クラス(名前は任意)
{
//配列の要素をシャッフルする (Fisher-Yates shuffle)
public static void Shuffle<T>(this T[] arr)
{
for (int i = arr.Length - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1); //[0]~[i]
arr.Swap(i, j);
}
}

//要素のスワップ
public static void Swap<T>(this T[] arr, int i, int j)
{
T tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}

//メインは同じ(※略)

 なお、ここでは拡張メソッドを用いているが、通常のメソッドで書くと以下のようになる。

●配列のシャッフル(UnityEngine.Random / 通常メソッド版)
・・・(略)・・・
public class Extensions //※通常メソッドの場合、static でなくても可
{
//配列の要素をシャッフルする (Fisher-Yates shuffle)
public static void Shuffle<T>(T[] arr)
{
for (int i = arr.Length - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1); //[0]~[i]
Swap(arr, i, j);
}
}

//要素のスワップ
public static void Swap<T>(T[] arr, int i, int j)
{
T tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}

//メインでは・・・
・・・(略)・・・
Extensions.Shuffle(arr); //※クラス名は任意の定義による
・・・(略)・・・

拡張メソッドは便利だが、増えすぎるとかえって邪魔になることもあるので、汎用性の高いものだけにすると良いだろう。

 あと、ここでは Unity用にランダム関数は UnityEngine.Random を使っているが、C#標準の System.Random を使いたい場合、以下のように書くこともできる。

●配列のシャッフル+要素のスワップ(System.Random / 拡張メソッド)
using UnityEngine;
using Random = System.Random; //ここでエイリアスにしておくと表記が楽

public static class Extensions //拡張メソッド用 static クラス(名前は任意)
{
static Random rand = new Random(); //※毎回 new するならいっそ static で利用する

//配列の要素をシャッフルする (Fisher-Yates shuffle)
public static void Shuffle<T>(this T[] arr)
{
for (int i = arr.Length - 1; i > 0; i--)
{
int j = rand.Next(i + 1); //[0]~[i]
arr.Swap(i, j);
}
}

//スワップは拡張メソッドと同じ(※略)
}

//メインは同じ(※略)

 System.Random の場合は毎回 new しなくてはならないので、いっそ static で使い回しした方が良いだろう。もちろん「Shuffle<T>(this T[] arr, Random rand)」のように引数にランダムジェネレータを与えるようにしても良いと思う(他の言語を含め、標準関数にはそういうものが多い)。

 が、まぁしかし、Unity を使っている分には「UnityEngine.Random」の方が簡単だし、円状とか球状とか便利なので迷わず使っても良いだろう。また数が少ない場合、UnityEngine.Random の方が良い感じに乱数が出るという話もある。こういった実験は私もよくやるのだが、情報を公開してくれていると非常に有り難い(笑)。なので私も複数の書き方やアルゴリズムを試したりしてるわけだ(といってもアルゴリズム系の記事は Java の方が多いが[※思いつきでやるのでたまたま])。以下に参考資料も載せておこう。

(参考)Unityの乱数が本当にランダムかどうか見てみた



■リストのシャッフル

●リストのシャッフル(UnityEngine.Random / 拡張メソッド版)
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public static class Extensions //拡張メソッド用 static クラス(名前は任意)
{
//リストの要素をシャッフルする (Fisher-Yates shuffle)
public static void Shuffle<T>(this List<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1); //[0]~[i]
T tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
}
}


//メインでは・・・
using System.Linq;

List<int> list = new List<int>() { 0, 1, 2, 3, 4, 5 };
for (int i = 0; i < 5; i++)
{
list.Shuffle();
Debug.Log(list.Select(e => e.ToString()).Aggregate((s, a) => s + ", " + a));
}

4, 3, 1, 5, 2, 0
5, 4, 3, 2, 0, 1
0, 3, 5, 1, 2, 4
1, 5, 0, 2, 4, 3
0, 1, 2, 4, 3, 5

 やっていることは配列のシャッフルと変わらないので、説明はいらないだろう。一瞬、IEnumerable で書くことも頭をよぎったが、そもそもシャッフルの対象となるのは静的配列かリストが最も多い。特に拡張メソッドで書くと入力候補もどんどん増加してしまうので、用途が限定されるならこれで十分と思った。まぁ、必要なら好きに改造してくれたまい(笑) [※たぶんコードが煩雑になる]。

 リストのスワップ定義も配列と同じようにやってみると、随分簡潔になる。

●リストのシャッフル+要素のスワップ(UnityEngine.Random / 拡張メソッド版)
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public static class Extensions //拡張メソッド用 static クラス(名前は任意)
{
//リストの要素をシャッフルする (Fisher-Yates shuffle)
public static void Shuffle<T>(this List<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1); //[0]~[i]
list.Swap(i, j);
}
}

//要素のスワップ
public static void Swap<T>(this List<T> list, int i, int j)
{
T tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
}

//メインは同じ(※略)

 もちろん、配列のときと同じように通常のメソッドで書くには以下のようになる。

●リストのシャッフル(UnityEngine.Random / 通常メソッド版)
・・・(略)・・・
public class Extensions
{
//リストの要素をシャッフルする (Fisher-Yates shuffle)
public static void Shuffle<T>(List<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1); //[0]~[i]
Swap(list, i, j);
}
}

//要素のスワップ
public static void Swap<T>(List<T> list, int i, int j)
{
T tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
}

//メインでは・・・
・・・(略)・・・
Extensions.Shuffle(list); //※クラス名は任意の定義による
・・・(略)・・・

 ついでに C#標準の System.Random を使う場合、配列のときと同じように以下のように書ける。

●配列のシャッフル+要素のスワップ(System.Random / 拡張メソッド)
using UnityEngine;
using Random = System.Random; //ここでエイリアスにしておくと表記が楽

public static class Extensions //拡張メソッド用 static クラス(名前は任意)
{
static Random rand = new Random(); //※毎回 new するならいっそ static で利用する

//リストの要素をシャッフルする (Fisher-Yates shuffle)
public static void Shuffle<T>(this List<T> list)
{
for (int i = arr.Length - 1; i > 0; i--)
{
int j = rand.Next(i + 1); //[0]~[i]
arr.Swap(i, j);
}
}

//スワップは拡張メソッドと同じ(※略)
}

//メインは同じ(※略)

 これも配列のときと同じように「Shuffle<T>(this List<T> list, Random rand)」のように引数にランダムジェネレータを与えるようにしても良いと思う。だがまぁ、「UnityEngine.Random」の方を使った方が楽だろう。


 ゲームの場合、アイテムドロップに直接ランダム関数を用いることも多いと思うが、アイテムのインデクスの確率テーブルなどを作ってシャッフルすれば、全体としては確実に分布が行き渡るので、そういう使い方も良いかもね(例えばテーブルを {0,0,0,1,1,2} としてシャッフル→順番に取り出せば、確実に 3:2:1 の分布でアイテムが出る=直接ランダムの場合、回数が多くなるほど確率は正しくなるが、少ない回数では偏ることもあるため)。

 音楽プレイヤーのシャッフルなんかも直接ランダムの場合、曲数少ないと「A-B-A-A-B-C」(=3曲を6回ランダム再生)のように偏ることもあるが、シャッフルで {A,C,B}+{C,A,B} のようにすれば(=3曲を2回シャッフルで順に再生)偏りは減る(末尾と先頭が連続することはあるが、全体的にはバラバラ順に聞こえる)。プラグイン(ver.1.16以降)の簡易音楽プレイヤーでもそのまま実装しているので、興味があったらダウンロードしてビルドして試してみるのも良いだろう(※好きに改造して貰っても構わない)。


※他、様々な機能を使ったデモが同梱。Unity×Androidアプリ開発に!

※サンプルで使っているループ曲を含んだライブラリも配信中。全31曲!




(関連記事)
【Unity】AssetStore版 FantomPlugin のセットアップ
【C#】連想配列(Dictionary)を値 or キーでソート
【C#】2次元配列(ジャグ配列・多次元配列)のソート
【C#】多次元配列とジャグ配列(2次元配列)のサイズ(長さ)、相互変換など
【Java】配列要素の反転(reverse)


このコンテンツの一部には『ユニティちゃんライセンス』で許諾されたアセットが使用されています。
Portions of this work are licensed under Unity-Chan License


スポンサーサイト

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  C#ライブラリ  アルゴリズム 
tb: 0   cm: --

【C#】 LowerBound, UpperBound (二分探索)  


 私は時折、頭の体操代わりにプロコン問題を解いたりしているのだが、使う言語はその時々で変えてるので、関数によっては無いと不便に思うものがある。LowerBound, UpperBound はその代表例かな。ものによっては BinarySearch でも同じ結果を得られるものもあるが、やはりあった方が良いので改めて書いておくことにした。内容的には JavaRuby 版と同じ。使い所を覚えれば、簡潔かつ高速に処理できるので、二分探索系のアルゴリズムは知っておいても損はないだろう。




■C# 版 LowerBound, UpperBound

using System;
using System.Collections.Generic;

/// <summary>
/// 指定した値以上の先頭のインデクスを返す
/// </summary>
/// <typeparam name="T">比較する値の型</typeparam>
/// <param name="arr">対象の配列(※ソート済みであること)</param>
/// <param name="start">開始インデクス [inclusive]</param>
/// <param name="end">終了インデクス [exclusive]</param>
/// <param name="value">検索する値</param>
/// <param name="comparer">比較関数(インターフェイス)</param>
/// <returns>指定した値以上の先頭のインデクス</returns>
public static int LowerBound<T>(T[] arr, int start, int end, T value, IComparer<T> comparer)
{
int low = start;
int high = end;
int mid;
while (low < high)
{
mid = ((high - low) >> 1) + low;
if (comparer.Compare(arr[mid], value) < 0)
low = mid + 1;
else
high = mid;
}
return low;
}

//引数省略のオーバーロード
public static int LowerBound<T>(T[] arr, T value) where T : IComparable
{
return LowerBound(arr, 0, arr.Length, value, Comparer<T>.Default);
}

/// <summary>
/// 指定した値より大きい先頭のインデクスを返す
/// </summary>
/// <typeparam name="T">比較する値の型</typeparam>
/// <param name="arr">対象の配列(※ソート済みであること)</param>
/// <param name="start">開始インデクス [inclusive]</param>
/// <param name="end">終了インデクス [exclusive]</param>
/// <param name="value">検索する値</param>
/// <param name="comparer">比較関数(インターフェイス)</param>
/// <returns>指定した値より大きい先頭のインデクス</returns>
public static int UpperBound<T>(T[] arr, int start, int end, T value, IComparer<T> comparer)
{
int low = start;
int high = end;
int mid;
while (low < high)
{
mid = ((high - low) >> 1) + low;
if (comparer.Compare(arr[mid], value) <= 0)
low = mid + 1;
else
high = mid;
}
return low;
}

//引数省略のオーバーロード
public static int UpperBound<T>(T[] arr, T value)
{
return UpperBound(arr, 0, arr.Length, value, Comparer<T>.Default);
}

 ついでに引数省略のオーバーロードも書いてあるが、実際にはもう何パターンかあった方が便利かも知れない(全要素+IComparer指定 / 範囲指定+IComparer省略など)。もし予め検索の範囲が絞りこめるなら、更に高速に処理できるので範囲指定はオススメだ。プロコン問題やリアルタイム処理が必要な高負荷アプリケーションなどでは積極的に使っていこう。

 ちなみに返ってくるインデクスは LowerBound は「指定した値以上の先頭のインデクス」で、UpperBound は「指定した値より大きい先頭のインデクス」になる。BinarySearch の場合は、値が見つかった場合はそのインデクスが返り、見つからないとき負の値となり、ビット反転することで「指定した値より大きい先頭のインデクス」が返る(つまり毎回正負を調べる必要がある)。完全一致の有無を調べるには BinarySearch の方が便利だが、一番近い値のインデクス見つけるなら LowerBound や UpperBound の方が便利だ。使い分けると良いだろう。

 以降は簡単な使用例なので、既に知っているなら読み飛ばしても良いだろう。



■いくつかの値の範囲で何番目の要素かを求める

 例えば以下のような表があり、「ある風速に対して風力を求める」問題があったとする。実際にこの問題は AtCoder の練習問題の一部を抜粋したものだが、色々な人の解答例を見ていると(AtCoderyukicoder などは他の人の解答を見れるので勉強になる)、逐次検索または if 文で頭から値を比較しているものが圧倒的に多い。しかしこの表のように単調増加なものなら、二分検索が使えると考えて良い。この場合は UpperBound を使うと簡単だ。

風力風速
風力00.0m⁄s 以上 0.2m⁄s 以下
風力10.3m⁄s 以上 1.5m⁄s 以下
風力21.6m⁄s 以上 3.3m⁄s 以下
風力33.4m⁄s 以上 5.4m⁄s 以下
風力45.5m⁄s 以上 7.9m⁄s 以下
風力58.0m⁄s 以上 10.7m⁄s 以下
風力610.8m⁄s 以上 13.8m⁄s 以下
風力713.9m⁄s 以上 17.1m⁄s 以下
風力817.2m⁄s 以上 20.7m⁄s 以下
風力920.8m⁄s 以上 24.4m⁄s 以下
風力1024.5m⁄s 以上 28.4m⁄s 以下
風力1128.5m⁄s 以上 32.6m⁄s 以下
風力1232.7m⁄s 以上

●UpperBound を使っての解答例
//※・・・関数などは略・・・

//表の各要素の開始の風速を配列にしたもの
double[] WindPower = {
0.0, 0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8, 24.5, 28.5, 32.7
};

double ms = 15.3; //※与えられた風速とする(m/s)
int wp = UpperBound(WindPower, ms) - 1;
Console.WriteLine("風速" + wp);

風速7

 実はこの問題の場合は BinarySearch でも事足りる。しかし BinarySearch の場合、値が見つかった時と見つからなかったときで処理を分岐する必要があり、冗長となることも多い。

●Array.BinarySearchを使っての解答例
//※・・・関数などは略・・・

//表の各要素の開始の風速を配列にしたもの
double[] WindPower = {
0.0, 0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.8, 24.5, 28.5, 32.7
};

double ms = 15.3; //※与えられた風速とする(m/s)
int wp = Array.BinarySearch(WindPower, ms);
Console.WriteLine("風速" + (wp < 0 ? ~wp - 1 : wp)); //見つからなかったときビット反転(~)するとUpperBoundと同じになる

風速7

 実際にはこの問題くらいの要素数なら逐次検索でも構わないのだが、要素数が何千とあったり、60fpsで動作するゲームなどでフレーム毎で使用する必要などあった場合、検索回数を大幅に減らせるので効果はてきめんとなる。



■検索する値の個数を求める

 次によくあるパターンだが、ある数が連続して並んでいるとき、その範囲や個数を調べる方法などもある。例えば以下のような表があったとき、「2~4 の値を持つインデクスの範囲とその要素数を求めたい」としよう。一番初めに思い浮かぶアルゴリズムは先頭から検索していき、2 が出てきた所で開始インデクスを記録、4 より上の値が出てきた所で終了インデクスを記録するといった感じだろうか。開始と終了の差を取ればその個数もわかる。しかし、ここでは LowerBound, UpperBound を使ってみよう。

Index[0][1][2][3][4][5][6][7][8][9][10][11][12]
Value0011122344455

//※・・・関数などは略・・・

int[] arr = {0, 0, 1, 1, 1, 2, 2, 3, 4, 4, 4, 5, 5}; //表
int low = 2; //検索する最小値
int high = 4; //検索する最大値
int from = LowerBound(arr, low); //検索する最小値で最初のインデクス
int to = UpperBound(arr, high); //検索する最大値を超えた最初のインデクス
Console.WriteLine("from = " + from + ", to = " + to + ", count = " + (to - from));

from = 5, to = 11, count = 6

 この例も要素数が少ない場合は逐次検索でもそれほど大差ないが、何千・何万という要素数でなら、圧倒的な速さとなる。



 私の見たところ、実際の開発現場でのコードでも逐次検索は多用されている感じがあるので、データが単調増加ないし単調減少なら二分探索を取り入れると良いと思う。確実に高速化できるし、もしデータを順に並べて良いものなら、予めリアルタイムにソート済みデータを作っていくアルゴリズムを組んでおけば、何かと便利なことが多い(Ruby 版だが「ソート済み配列を作る」方法もある[※C# なら List にすると良い])。

 この辺りは慣れだと思うが、常に検索回数を減らせるような工夫を忘れないようにすれば、paizaCodeIQ などの上級者問題(A, S ランク)も解けるようになる(タイムアウトしなくなる)。挑戦してみると良いだろう。


(関連記事)
【Ruby】二分探索, lower_bound, upper_bound
【Java】lower_bound(), upper_bound() 関数を作る
【Java】二分探索を汎用的に使えるようにする


■参考になる書籍


category: C#

thread: プログラミング

janre: コンピュータ

tag: C#ライブラリ  配列操作  二分探索 
tb: 0   cm: --

【C#】倍数での Floor, Ceil, Round(一定間隔での切り捨て、切り上げ、四捨五入) [double 版]  



 通常の Floor, Ceil, Round は小数の位を切り捨てたり切り上げたりして1ごとの整数にするものだが、5ごとの値とか10の倍数(下1桁を揃える)にしたいと思うことも少なくない。

 ゲームなどで一定間隔の座標に揃えたり、値の下n桁を0したいときなど応用範囲は広いので、毎回書くくらいなら簡単なライブラリとして作っておこう。





■nの倍数で切り捨てる(nおきの数に切り捨てる)

●double 版 MultipleFloor()
using System;

/// <summary>
/// より小さい倍数を求める(倍数で切り捨てられるような値)
///(例)倍数 = 10 のとき、12 → 10, 17 → 10
/// </summary>
/// <param name="value">入力値</param>
/// <param name="multiple">倍数</param>
/// <returns>倍数で切り捨てた値</returns>
public static double MultipleFloor(double value, double multiple)
{
return Math.Floor(value / multiple) * multiple;
}


//メインでは…
Console.WriteLine("5 -> " + MultipleFloor(5, 10)); //0
Console.WriteLine("12 -> " + MultipleFloor(12, 10)); //10
Console.WriteLine("27 -> " + MultipleFloor(27, 10)); //20

(例)n を10おきに切り捨てる(下1桁を0にする)
 5, 12, 27 → 0, 10, 20

(例)n を5おきに切り捨てる
 5, 12, 27 → 5, 10, 25




■nの倍数で切り上げる(nおきの数に繰り上げる)

●double 版 MultipleCeil()
using System;

/// <summary>
/// より大きい倍数を求める(倍数で繰り上がるような値)
///(例)倍数 = 10 のとき、12 → 20, 17 → 20
/// </summary>
/// <param name="value">入力値</param>
/// <param name="multiple">倍数</param>
/// <returns>倍数で切り上げた値</returns>
public static double MultipleCeil(double value, double multiple)
{
return Math.Ceiling(value / multiple) * multiple;
}


//メインでは…
Console.WriteLine("5 -> " + MultipleCeil(5, 10)); //10
Console.WriteLine("12 -> " + MultipleCeil(12, 10)); //20
Console.WriteLine("27 -> " + MultipleCeil(27, 10)); //30

(例)n を10おきに切り上げる(倍数で繰り上げる)
 5, 12, 27 → 10, 20, 30

(例)n を5おきに切り上げる
 5, 12, 27 → 5, 15, 30




■nの倍数で四捨五入のような値を求める(nおきの数の中間の値で切り捨て・切り上げをする)

●double 版 MultipleRound()
using System;

/// <summary>
/// 倍数での四捨五入のような値を求める(nおきの数の中間の値で切り捨て・切り上げをする)
///(例)倍数 = 10 のとき、12 → 10, 17 → 20
/// </summary>
/// <param name="value">入力値</param>
/// <param name="multiple">倍数</param>
/// <returns>倍数の中間の値で、切り捨て・切り上げした値
public static double MultipleRound(double value, double multiple)
{
return MultipleFloor(value + multiple * 0.5, multiple); //四捨五入的
//return Math.Round(value / multiple) * multiple; //五捨六入的(正の数のとき)
}


//メインでは…
Console.WriteLine("5 -> " + MultipleRound(5, 10)); //10
Console.WriteLine("12 -> " + MultipleRound(12, 10)); //10
Console.WriteLine("27 -> " + MultipleRound(27, 10)); //30

(例)n を10おきに切り捨て・切り上げる(10の中間の5を閾値として切り捨て・切り上げをする:4→0, 5→10)
 5, 12, 27 → 10, 10, 30

(例)n を5おきに切り捨て・切り上げる(5の中間の2.5を閾値として切り捨て・切り上げをする:2.4→0, 2.5→5)
 5, 12, 27 → 5, 10, 25

 1つだけ注意点は MultipleRound() に関しては、関数内部で MultipleFloor() を使っている点だ。これは C# の Math.Round() の丸め方はどちらかと言うと五捨六入に近いので、他の言語に移植などするとき、そのままでは結果が異なってしまうため、Round(v) の代わりに Floor(v + 0.5) を使っている。この辺りは C# 限定で Math.Round の仕様に則った値にしたいなら、コメントアウトしてある方を使っても良いだろう。他の言語との Round の値の違いは以下のページの一覧やフォーラムなどを参照して欲しい。

Java, C#, PHP, Ruby, Python, JavaScript での Math.round(四捨五入・五捨六入)比較
Math.Roundって五捨六入?


(関連記事)
【Unity】倍数での Floor, Ceil, Round(一定間隔での切り捨て、切り上げ、四捨五入) [float 版]
 Java, C#, PHP, Ruby, Python, JavaScript での Math.round(四捨五入・五捨六入)比較
【Java】Math.floor(), ceil(), round() 動作互換アルゴリズムを試す


category: C#

thread: プログラミング

janre: コンピュータ

tag: C#ライブラリ 
tb: 0   cm: --


プロフィール

Social

検索フォーム

全記事一覧

カテゴリ

ユーザータグ

最新記事

リンク

PR

▲ Pagetop