これは古い質問ですが、多くのanwserはパフォーマンスがよくない、または大きな数字に対してオーバーフローします。D. Nesterovの答えが最良だと思います。堅牢で、シンプルで、高速です。2セントを足したいだけです。小数で遊んだり、ソースコードをチェックアウトしたりしました。public Decimal (int lo, int mid, int hi, bool isNegative, byte scale)
コンストラクタのドキュメントから。
10進数の2進数表現は、1ビットの符号、96ビットの整数、および整数を除算し、その小数部分を指定するために使用されるスケーリング係数で構成されます。スケーリング係数は、暗黙的に10を0から28の範囲の指数に累乗したものです。
これを知って、私の最初のアプローチはdecimal
、切り捨てたい小数に対応するスケールを持つ別のものを作成し、それを切り捨てて、最終的に希望のスケールで小数を作成することでした。
private const int ScaleMask = 0x00FF0000;
public static Decimal Truncate(decimal target, byte decimalPlaces)
{
var bits = Decimal.GetBits(target);
var scale = (byte)((bits[3] & (ScaleMask)) >> 16);
if (scale <= decimalPlaces)
return target;
var temporalDecimal = new Decimal(bits[0], bits[1], bits[2], target < 0, (byte)(scale - decimalPlaces));
temporalDecimal = Math.Truncate(temporalDecimal);
bits = Decimal.GetBits(temporalDecimal);
return new Decimal(bits[0], bits[1], bits[2], target < 0, decimalPlaces);
}
この方法はD. Nesterovの方法より速くなく、より複雑なので、もう少し遊んでみました。私の推測では、助剤を作成しdecimal
てビットを2回取得する必要があるため、処理が遅くなると考えられます。2回目の試行では、Decimal.GetBits(Decimal d)メソッドによって返されたコンポーネントを自分で操作しました。アイデアは、コンポーネントを必要な数の10倍に分割し、スケールを縮小することです。このコードは、(大きく)Decimal.InternalRoundFromZero(ref Decimal d、int decimalCount)メソッドに基づいています。
private const Int32 MaxInt32Scale = 9;
private const int ScaleMask = 0x00FF0000;
private const int SignMask = unchecked((int)0x80000000);
// Fast access for 10^n where n is 0-9
private static UInt32[] Powers10 = new UInt32[] {
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000
};
public static Decimal Truncate(decimal target, byte decimalPlaces)
{
var bits = Decimal.GetBits(target);
int lo = bits[0];
int mid = bits[1];
int hi = bits[2];
int flags = bits[3];
var scale = (byte)((flags & (ScaleMask)) >> 16);
int scaleDifference = scale - decimalPlaces;
if (scaleDifference <= 0)
return target;
// Divide the value by 10^scaleDifference
UInt32 lastDivisor;
do
{
Int32 diffChunk = (scaleDifference > MaxInt32Scale) ? MaxInt32Scale : scaleDifference;
lastDivisor = Powers10[diffChunk];
InternalDivRemUInt32(ref lo, ref mid, ref hi, lastDivisor);
scaleDifference -= diffChunk;
} while (scaleDifference > 0);
return new Decimal(lo, mid, hi, (flags & SignMask)!=0, decimalPlaces);
}
private static UInt32 InternalDivRemUInt32(ref int lo, ref int mid, ref int hi, UInt32 divisor)
{
UInt32 remainder = 0;
UInt64 n;
if (hi != 0)
{
n = ((UInt32)hi);
hi = (Int32)((UInt32)(n / divisor));
remainder = (UInt32)(n % divisor);
}
if (mid != 0 || remainder != 0)
{
n = ((UInt64)remainder << 32) | (UInt32)mid;
mid = (Int32)((UInt32)(n / divisor));
remainder = (UInt32)(n % divisor);
}
if (lo != 0 || remainder != 0)
{
n = ((UInt64)remainder << 32) | (UInt32)lo;
lo = (Int32)((UInt32)(n / divisor));
remainder = (UInt32)(n % divisor);
}
return remainder;
}
私は厳密なパフォーマンステストを実行していませんが、MacOS Sierra 10.12.6、3,06 GHzのIntel Core i3プロセッサで.NetCore 2.1をターゲットにしている場合、この方法はD. Nesterovの方法よりもはるかに高速であるように見えます(それ以降、数値は示しません) 、私が述べたように、私のテストは厳密ではありません)。パフォーマンスの向上がコードの複雑さの増加に見合うかどうかを評価するのは、これを実装する人次第です。