假设我们有一个System.Decimal数字.
为了说明,我们来看一个ToString()表示如下:
d.ToString() = "123.4500"
Run Code Online (Sandbox Code Playgroud)
关于这个十进制,可以说以下内容.对于我们这里的目的,比例定义为小数点右边的位数.有效比例相似但忽略了小数部分中出现的任何尾随零.(换句话说,这些参数定义为SQL小数加上一些额外的参数,以说明小数部分中尾随零的System.Decimal概念.)
给定一个任意的System.Decimal,如何有效地计算所有这四个参数而不转换为String并检查String?该解决方案可能需要Decimal.GetBits.
更多例子:
Examples Precision Scale EffectivePrecision EffectiveScale
0 1 (?) 0 1 (?) 0
0.0 2 (?) 1 1 (?) 0
12.45 4 2 4 2
12.4500 6 4 4 2
770 3 0 3 0
Run Code Online (Sandbox Code Playgroud)
(?)或者将这些精度解释为零也没关系.
Jon*_*eet 27
是的,你需要使用Decimal.GetBits.不幸的是,你必须使用96位整数,并且.NET中没有简单的整数类型可以处理96位.另一方面,你有可能Decimal自己使用......
这里有一些代码与您的示例生成相同的数字.希望你觉得它有用 :)
using System;
public class Test
{
static public void Main(string[] x)
{
ShowInfo(123.4500m);
ShowInfo(0m);
ShowInfo(0.0m);
ShowInfo(12.45m);
ShowInfo(12.4500m);
ShowInfo(770m);
}
static void ShowInfo(decimal dec)
{
// We want the integer parts as uint
// C# doesn't permit int[] to uint[] conversion,
// but .NET does. This is somewhat evil...
uint[] bits = (uint[])(object)decimal.GetBits(dec);
decimal mantissa =
(bits[2] * 4294967296m * 4294967296m) +
(bits[1] * 4294967296m) +
bits[0];
uint scale = (bits[3] >> 16) & 31;
// Precision: number of times we can divide
// by 10 before we get to 0
uint precision = 0;
if (dec != 0m)
{
for (decimal tmp = mantissa; tmp >= 1; tmp /= 10)
{
precision++;
}
}
else
{
// Handle zero differently. It's odd.
precision = scale + 1;
}
uint trailingZeros = 0;
for (decimal tmp = mantissa;
tmp % 10m == 0 && trailingZeros < scale;
tmp /= 10)
{
trailingZeros++;
}
Console.WriteLine("Example: {0}", dec);
Console.WriteLine("Precision: {0}", precision);
Console.WriteLine("Scale: {0}", scale);
Console.WriteLine("EffectivePrecision: {0}",
precision - trailingZeros);
Console.WriteLine("EffectiveScale: {0}", scale - trailingZeros);
Console.WriteLine();
}
}
Run Code Online (Sandbox Code Playgroud)
小智 24
当我需要在将十进制值写入数据库之前验证精度和比例时,我遇到了这篇文章.我实际上想出了一种不同的方法来实现这一点,使用System.Data.SqlTypes.SqlDecimal,结果比这里讨论的其他两种方法更快.
static DecimalInfo SQLInfo(decimal dec)
{
System.Data.SqlTypes.SqlDecimal x;
x = new System.Data.SqlTypes.SqlDecimal(dec);
return new DecimalInfo((int)x.Precision, (int)x.Scale, (int)0);
}
Run Code Online (Sandbox Code Playgroud)
Jas*_*aty 11
使用ToString比Jon Skeet的解决方案快约10倍.虽然速度相当快,但这里的挑战(如果有任何参与者!)是要击败ToString的性能.
我从以下测试程序得到的性能结果是:ShowInfo 239 ms FastInfo 25 ms
using System;
using System.Diagnostics;
using System.Globalization;
public class Test
{
static public void Main(string[] x)
{
Stopwatch sw1 = new Stopwatch();
Stopwatch sw2 = new Stopwatch();
sw1.Start();
for (int i = 0; i < 10000; i++)
{
ShowInfo(123.4500m);
ShowInfo(0m);
ShowInfo(0.0m);
ShowInfo(12.45m);
ShowInfo(12.4500m);
ShowInfo(770m);
}
sw1.Stop();
sw2.Start();
for (int i = 0; i < 10000; i++)
{
FastInfo(123.4500m);
FastInfo(0m);
FastInfo(0.0m);
FastInfo(12.45m);
FastInfo(12.4500m);
FastInfo(770m);
}
sw2.Stop();
Console.WriteLine(sw1.ElapsedMilliseconds);
Console.WriteLine(sw2.ElapsedMilliseconds);
Console.ReadLine();
}
// Be aware of how this method handles edge cases.
// A few are counterintuitive, like the 0.0 case.
// Also note that the goal is to report a precision
// and scale that can be used to store the number in
// an SQL DECIMAL type, so this does not correspond to
// how precision and scale are defined for scientific
// notation. The minimal precision SQL decimal can
// be calculated by subtracting TrailingZeros as follows:
// DECIMAL(Precision - TrailingZeros, Scale - TrailingZeros).
//
// dec Precision Scale TrailingZeros
// ------- --------- ----- -------------
// 0 1 0 0
// 0.0 2 1 1
// 0.1 1 1 0
// 0.01 2 2 0 [Diff result than ShowInfo]
// 0.010 3 3 1 [Diff result than ShowInfo]
// 12.45 4 2 0
// 12.4500 6 4 2
// 770 3 0 0
static DecimalInfo FastInfo(decimal dec)
{
string s = dec.ToString(CultureInfo.InvariantCulture);
int precision = 0;
int scale = 0;
int trailingZeros = 0;
bool inFraction = false;
bool nonZeroSeen = false;
foreach (char c in s)
{
if (inFraction)
{
if (c == '0')
trailingZeros++;
else
{
nonZeroSeen = true;
trailingZeros = 0;
}
precision++;
scale++;
}
else
{
if (c == '.')
{
inFraction = true;
}
else if (c != '-')
{
if (c != '0' || nonZeroSeen)
{
nonZeroSeen = true;
precision++;
}
}
}
}
// Handles cases where all digits are zeros.
if (!nonZeroSeen)
precision += 1;
return new DecimalInfo(precision, scale, trailingZeros);
}
struct DecimalInfo
{
public int Precision { get; private set; }
public int Scale { get; private set; }
public int TrailingZeros { get; private set; }
public DecimalInfo(int precision, int scale, int trailingZeros)
: this()
{
Precision = precision;
Scale = scale;
TrailingZeros = trailingZeros;
}
}
static DecimalInfo ShowInfo(decimal dec)
{
// We want the integer parts as uint
// C# doesn't permit int[] to uint[] conversion,
// but .NET does. This is somewhat evil...
uint[] bits = (uint[])(object)decimal.GetBits(dec);
decimal mantissa =
(bits[2] * 4294967296m * 4294967296m) +
(bits[1] * 4294967296m) +
bits[0];
uint scale = (bits[3] >> 16) & 31;
// Precision: number of times we can divide
// by 10 before we get to 0
uint precision = 0;
if (dec != 0m)
{
for (decimal tmp = mantissa; tmp >= 1; tmp /= 10)
{
precision++;
}
}
else
{
// Handle zero differently. It's odd.
precision = scale + 1;
}
uint trailingZeros = 0;
for (decimal tmp = mantissa;
tmp % 10m == 0 && trailingZeros < scale;
tmp /= 10)
{
trailingZeros++;
}
return new DecimalInfo((int)precision, (int)scale, (int)trailingZeros);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
23505 次 |
| 最近记录: |