截断两个小数位而不进行舍入

94 c# math rounding

假设我的值为3.4679且想要3.46,如何在没有四舍五入的情况下截断到两位小数?

我试过以下但三个都给了我3.47:

void Main()
{
    Console.Write(Math.Round(3.4679, 2,MidpointRounding.ToEven));
    Console.Write(Math.Round(3.4679, 2,MidpointRounding.AwayFromZero));
    Console.Write(Math.Round(3.4679, 2));
}
Run Code Online (Sandbox Code Playgroud)

这返回3.46,但看起来很脏,如何:

void Main()
{
    Console.Write(Math.Round(3.46799999999 -.005 , 2));
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 134

value = Math.Truncate(100 * value) / 100;
Run Code Online (Sandbox Code Playgroud)

请注意,这些分数无法以浮点精确表示.

  • 对您的值使用十进制,这个答案将起作用.它不太可能总是在任何浮点表示中工作. (12认同)
  • 这让我想知道是否应该可以在浮点文字中指定舍入方向。嗯嗯。 (2认同)

Cor*_*ore 51

拥有一个完整函数来实现在C#中截断小数的实际用法会更有用.如果你想要,可以很容易地将它转换为Decimal扩展方法:

public decimal TruncateDecimal(decimal value, int precision)
{
    decimal step = (decimal)Math.Pow(10, precision);
    decimal tmp = Math.Truncate(step * value);
    return tmp / step;
}
Run Code Online (Sandbox Code Playgroud)

如果你需要VB.NET试试这个:

Function TruncateDecimal(value As Decimal, precision As Integer) As Decimal
    Dim stepper As Decimal = Math.Pow(10, precision)
    Dim tmp As Decimal = Math.Truncate(stepper * value)
    Return tmp / stepper
End Function
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

decimal result = TruncateDecimal(0.275, 2);
Run Code Online (Sandbox Code Playgroud)

要么

Dim result As Decimal = TruncateDecimal(0.275, 2)
Run Code Online (Sandbox Code Playgroud)

  • 这将溢出大量。 (2认同)

Tim*_*oyd 23

其他示例的一个问题是它们分割之前将输入值相乘.这里有一个边缘情况,你可以通过乘以第一个边缘情况来溢出十进制,但是我遇到了一些问题.分别处理小数部分更安全如下:

    public static decimal TruncateDecimal(this decimal value, int decimalPlaces)
    {
        decimal integralValue = Math.Truncate(value);

        decimal fraction = value - integralValue;

        decimal factor = (decimal)Math.Pow(10, decimalPlaces);

        decimal truncatedFraction = Math.Truncate(fraction * factor) / factor;

        decimal result = integralValue + truncatedFraction;

        return result;
    }
Run Code Online (Sandbox Code Playgroud)


小智 22

使用模数运算符:

var fourPlaces = 0.5485M;
var twoPlaces = fourPlaces - (fourPlaces % 0.01M);
Run Code Online (Sandbox Code Playgroud)

结果:0.54

  • 我不明白(阅读:没有花时间验证)所有这些其他奇特的解决方案,这正是我所寻找的。谢谢你! (2认同)

Pan*_*vos 21

在 .NET Core 3.0 及更高版本中Math.RoundDecimal.Round可以通过新的 MidpointRounding.ToZero截断数字。对于正数,MidpointRounding.ToNegativeInfinity具有相同的效果,而对于负数,则等效为MidpointRounding.ToPositiveInfinity

这些行:

Console.WriteLine(Math.Round(3.4679, 2,MidpointRounding.ToZero));
Console.WriteLine(Math.Round(3.9999, 2,MidpointRounding.ToZero));
Console.WriteLine(Math.Round(-3.4679, 2,MidpointRounding.ToZero));
Console.WriteLine(Math.Round(-3.9999, 2,MidpointRounding.ToZero));
Run Code Online (Sandbox Code Playgroud)

生产 :

3.46
3.99
-3.46
-3.99
Run Code Online (Sandbox Code Playgroud)


D. *_*rov 20

通用和快速方法(无Math.Pow()/乘法)System.Decimal:

decimal Truncate(decimal d, byte decimals)
{
    decimal r = Math.Round(d, decimals);

    if (d > 0 && r > d)
    {
        return r - new decimal(1, 0, 0, false, decimals);
    }
    else if (d < 0 && r < d)
    {
        return r + new decimal(1, 0, 0, false, decimals);
    }

    return r;
}
Run Code Online (Sandbox Code Playgroud)

  • 我通过其他答案中提到的所有测试来运行它,它完美地运行.惊讶它没有更多的赞成.值得注意的是,小数只能在0到28之间(大多数人可能都可以). (3认同)

nig*_*der 6

我将保留十进制数的解决方案.

这里的一些小数位解决方案容易溢出(如果我们传递一个非常大的十进制数,并且该方法将尝试将其相乘).

Tim Lloyd的解决方案可以防止溢出,但速度不会太快.

以下解决方案大约快2倍,并且没有溢出问题:

public static class DecimalExtensions
{
    public static decimal TruncateEx(this decimal value, int decimalPlaces)
    {
        if (decimalPlaces < 0)
            throw new ArgumentException("decimalPlaces must be greater than or equal to 0.");

        var modifier = Convert.ToDecimal(0.5 / Math.Pow(10, decimalPlaces));
        return Math.Round(value >= 0 ? value - modifier : value + modifier, decimalPlaces);
    }
}

[Test]
public void FastDecimalTruncateTest()
{
    Assert.AreEqual(-1.12m, -1.129m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.120m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.125m. TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.1255m.TruncateEx(2));
    Assert.AreEqual(-1.12m, -1.1254m.TruncateEx(2));
    Assert.AreEqual(0m,      0.0001m.TruncateEx(3));
    Assert.AreEqual(0m,     -0.0001m.TruncateEx(3));
    Assert.AreEqual(0m,     -0.0000m.TruncateEx(3));
    Assert.AreEqual(0m,      0.0000m.TruncateEx(3));
    Assert.AreEqual(1.1m,    1.12m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.15m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.19m.  TruncateEx(1));
    Assert.AreEqual(1.1m,    1.111m. TruncateEx(1));
    Assert.AreEqual(1.1m,    1.199m. TruncateEx(1));
    Assert.AreEqual(1.2m,    1.2m.   TruncateEx(1));
    Assert.AreEqual(0.1m,    0.14m.  TruncateEx(1));
    Assert.AreEqual(0,      -0.05m.  TruncateEx(1));
    Assert.AreEqual(0,      -0.049m. TruncateEx(1));
    Assert.AreEqual(0,      -0.051m. TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.14m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.15m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.16m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.19m.  TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.199m. TruncateEx(1));
    Assert.AreEqual(-0.1m,  -0.101m. TruncateEx(1));
    Assert.AreEqual(0m,     -0.099m. TruncateEx(1));
    Assert.AreEqual(0m,     -0.001m. TruncateEx(1));
    Assert.AreEqual(1m,      1.99m.  TruncateEx(0));
    Assert.AreEqual(1m,      1.01m.  TruncateEx(0));
    Assert.AreEqual(-1m,    -1.99m.  TruncateEx(0));
    Assert.AreEqual(-1m,    -1.01m.  TruncateEx(0));
}
Run Code Online (Sandbox Code Playgroud)

  • 我不喜欢给它添加"Ex"后缀.C#支持重载,你的`Truncate`方法将与.net本地方法一起分组,为用户提供无缝体验. (2认同)

Mus*_*ata 6

这是一个老问题,但许多答案表现不佳或在处理大数字时溢出。我认为 D. Nesterov 的答案是最好的:稳健、简单且快速。我只想补充我的两分钱。我玩弄了小数并检查了源代码。来自public Decimal (int lo, int mid, int hi, bool isNegative, byte scale) 构造函数文档

Decimal 数的二进制表示形式由 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);
    }
Run Code Online (Sandbox Code Playgroud)

这种方法并不比 D. Nesterov 的方法快,而且更复杂,所以我多尝试了一下。我的猜测是,必须创建一个辅助设备decimal并检索两次位会使其变慢。在第二次尝试中,我自己操作了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;
    }
Run Code Online (Sandbox Code Playgroud)

我还没有进行严格的性能测试,但在 MacOS Sierra 10.12.6、3,06 GHz Intel Core i3 处理器和目标 .NetCore 2.1 上,此方法似乎比 D. Nesterov 的方法快得多(我不会给出数字,因为,正如我所提到的,我的测试并不严格)。由实现此功能的人来评估性能提升是否会因为增加的代码复杂性而得到回报。