截断两位小数而不四舍五入


107

可以说我有一个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));
}

这将返回3.46,但看起来有些肮脏:

void Main()
{
    Console.Write(Math.Round(3.46799999999 -.005 , 2));
}
c#  math  rounding 

Answers:


150
value = Math.Truncate(100 * value) / 100;

请注意,此类分数不能准确地以浮点表示。


12
使用十进制作为您的值,此答案将起作用。不可能总是以任何浮点表示形式工作。
德里斯

1
这使我想知道是否应该可以在浮点文字中指定舍入方向。嗯
Steve314 2010年

必须有某种方式告诉程序员与一个号码可以存储超过308位的假设计算是非常不合适的。Double只能存储15。在这里,溢出是一个很大的功能,溢出非常严重。
汉斯·帕森特

抱歉,我认为“值”是十进制。
nightcoder '16

54

在现实世界中使用完整功能截断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;
}

如果您需要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

然后像这样使用它:

decimal result = TruncateDecimal(0.275, 2);

要么

Dim result As Decimal = TruncateDecimal(0.275, 2)

1
这将在大量溢出。
nightcoder '16

1
要添加到夜间编码器中,在函数中使用Int32作为中介的事实将导致溢出。如果确实必须将其强制转换为Integer,则应使用Int64。问题是,为什么无论为什么Truncate都会返回Decimal积分,您还是要产生额外的开销。只需执行以下操作:十进制步长=(十进制)Math.Pow(10,precision); 返回Math.Truncate(step * value)/ step;
Sarel Esterhuizen

我将演员表放到整数。我将它们分隔开来,以提高可读性和功能的理解。
Corgalore

27

使用模运算符:

var fourPlaces = 0.5485M;
var twoPlaces = fourPlaces - (fourPlaces % 0.01M);

结果:0.54


1
我不理解(阅读:没有花时间去验证)所有其他花哨的解决方案,这正是我想要的。谢谢!
艾萨克·贝克

在.Net Fiddle clicky上运行此命令可产生0.5400 ...下面的D. Nesterov回答产生了预期的结果0.54
ttugates

您确实意识到@ttugates 0.54和0.5400是完全相同的值,对吗?除非/直到需要进行格式化以进行显示,否则跟随多少个零都无所谓-在这种情况下,如果格式化正确,结果将相同:$"{0.54m:C}"产生"$0.54"和是, $"{0.5400m:C}"产生"$0.54"
伦纳德·刘易斯

25

通用且快速的方法(无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;
}

4
我在其他答案中提到的所有测试中都进行了此测试,它运行完美。惊讶的是它没有更多的赞誉。值得注意的是,小数只能在0到28之间(对于大多数人来说可能不错)。
RichardOD

1
我同意那个。这是最好的答案。+1
布兰科·迪米特里耶维奇

1
很好的答案,这就是我所说的“跳出框框思考”
bruno.almeida

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;
    }

我知道这很旧,但我注意到了并对此提出了问题。这里的因子是一个int,因此,如果将其截断为大量小数位(例如25),将导致最终结果具有精度错误。我通过将因子类型更改为十进制来修复它。
TheKingDave 2013年

@TheKingDave:可能无关紧要,但是因为因子不能有小数,所以最好对它进行建模,对吧?
Ignacio Soler Garcia

@SoMoS对我来说Decimal效果更好,因为它为我提供了最高的f​​actor存储值。它仍然有一个限制,但是对于我的应用程序来说已经足够了。另一方面,长时间无法为我的应用程序存储足够大的数字。例如,如果您长时间执行Truncate(25),则将存在一些不准确性。
TheKingDave

已更新,允许根据@TheKingDave建议将其截断到更多地方,谢谢。
Tim Lloyd

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));
}

2
我不喜欢在其后缀“ Ex”。C#支持重载,您的Truncate方法将与.net本机方法组合在一起,从而为用户提供无缝体验。
Gqqnbig '16

1
您的算法导致一些不正确的结果。默认的MidpointRounding模式是Banker的舍入,将0.5舍入到最接近的偶数值。 Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));因此失败。如果您在Math.Round调用中指定“普通”舍入(AwayFromZero),则Assert.AreEqual(0m, 0m.TruncateEx(1));失败
Jon Senchyna

1
只有这样,该解决方案将工作,如果你使用MidpointRounding.AwayFromZero特别的代码来处理值0
乔恩Senchyna

1
乔恩是正确的:0m.TruncateEx(0)导致-1除非显式处理0。同样,除非在Math.Round中使用MidpointRounding.AwayFromZero,否则-11m.TruncateEx(0)的结果为-10。似乎可以很好地与这些修改一起使用。
何何何

1
即使更改AwayFromZero并显式处理0,-9999999999999999999999999999m.TruncateEx(0)也会导致-9999999999999999999999999998,因此在某些情况下它仍然会出错。
Ho Ho Ho

3

这是一个古老的问题,但是许多选民的表现并不出色,或者由于人数众多而溢出。我认为D. Nesterov的答案是最好的答案:健壮,简单且快速。我只想加两分钱。我玩弄小数,还检查了源代码。来自public Decimal (int lo, int mid, int hi, bool isNegative, byte scale) 构造函数文档

小数的二进制表示形式由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并两次检索这些位,这会使它变慢。第二次尝试时,我自己操作了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的方法(我不会给出数字,因为,正如我已经提到的,我的测试并不严格。实施此方法的人员取决于评估是否为增加的代码复杂性而获得了性能提升。


由于所有的想法和努力,我不得不投票。您将Nesterov's设定为基准,并且继续前进-脱帽而出。
AndrewBenjamin


2

((long)(3.4679 * 100)) / 100.0让你想要什么?


1

这是一个扩展方法:

public static decimal? TruncateDecimalPlaces(this decimal? value, int places)
    {
        if (value == null)
        {
            return null;
        }

        return Math.Floor((decimal)value * (decimal)Math.Pow(10, places)) / (decimal)Math.Pow(10, places);

    } // end

0

如果您不必太担心性能,并且最终结果可以是字符串,则以下方法将可以抵抗浮动精度问题:

string Truncate(double value, int precision)
{
    if (precision < 0)
    {
        throw new ArgumentOutOfRangeException("Precision cannot be less than zero");
    }

    string result = value.ToString();

    int dot = result.IndexOf('.');
    if (dot < 0)
    {
        return result;
    }

    int newLength = dot + precision + 1;

    if (newLength == dot + 1)
    {
        newLength--;
    }

    if (newLength > result.Length)
    {
        newLength = result.Length;
    }

    return result.Substring(0, newLength);
}

6
实际上,对“。”进行硬编码。这不是一个好主意,最好使用System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator [0]
David Airapetyan

0

这是我的TRUNC函数的实现

private static object Tranc(List<Expression.Expression> p)
{
    var target = (decimal)p[0].Evaluate();

    // check if formula contains only one argument
    var digits = p.Count > 1
        ? (decimal) p[1].Evaluate()
        : 0;

    return Math.Truncate((double)target * Math.Pow(10, (int)digits)) / Math.Pow(10, (int)digits);
}

0

那这个呢?

Function TruncateDecimal2(MyValue As Decimal) As Decimal
        Try
            Return Math.Truncate(100 * MyValue) / 100
        Catch ex As Exception
            Return Math.Round(MyValue, 2)
        End Try
End Function

0

除了上述解决方案之外,还有另一种方法可以实现。

    decimal val=23.5678m,finalValue;

    //take the decimal part    
     int decimalPos = val.ToString().IndexOf('.');
     string decimalPart = val.ToString().Substring(decimalPosition+1,val.ToString().Length);
    //will result.56
   string wholePart=val.ToString().Substring(0,decimalPos-1);
   //concantinate and parse for decimal.
  string truncatedValue=wholePart+decimalPart;//"23.56"
  bool isDecimal=Decimal.tryParse(truncatedValue,out finalValue);//finalValue=23.56

0

在某些情况下,这可能就足够了。

我有一个十进制值 SubCent = 0.0099999999999999999999999999M,该格式倾向于格式化为| SubCent:0.010000 |。通过string.Format("{0:N6}", SubCent );和许多其他格式选择。

我的要求不是四舍五入SubCent值,也不要记录每个数字。

以下满足我的要求:

string.Format("SubCent:{0}|", 
    SubCent.ToString("N10", CultureInfo.InvariantCulture).Substring(0, 9));

返回字符串:| SubCent:0.0099999 |

为了容纳具有整数部分的值,下面是一个开始。

tmpValFmt = 567890.0099999933999229999999M.ToString("0.0000000000000000000000000000");
decPt = tmpValFmt.LastIndexOf(".");
if (decPt < 0) decPt = 0;
valFmt4 = string.Format("{0}", tmpValFmt.Substring(0, decPt + 9));

返回字符串:

valFmt4 = "567890.00999999"

0

我正在使用此函数截断字符串变量中小数点后的值

public static string TruncateFunction(string value)
    {
        if (string.IsNullOrEmpty(value)) return "";
        else
        {
            string[] split = value.Split('.');
            if (split.Length > 0)
            {
                string predecimal = split[0];
                string postdecimal = split[1];
                postdecimal = postdecimal.Length > 6 ? postdecimal.Substring(0, 6) : postdecimal;
                return predecimal + "." + postdecimal;

            }
            else return value;
        }
    }

1
尽管此代码可以回答问题,但提供有关如何和/或为什么解决问题的其他上下文将提高​​答案的长期价值。
Nic3500 '18

0

这就是我所做的:

        c1 = a1 - b1;
        d1 = Math.Ceiling(c1 * 100) / 100;

减去两个输入数字而不舍入或舍去小数位。因为其他解决方案对我不起作用。不知道它是否对其他人有用,我只想分享这个:)希望它对那些正在寻找类似于我的问题的解决方案的人有用。谢谢

PS:我是一个初学者,请随时提出一些建议。:D如果您实际上是在与钱打交道,这是好事,对吗?它只有2位小数,四舍五入是一个没有。


0
        public static void ReminderDigints(decimal? number, out decimal? Value,  out decimal? Reminder)
        {
            Reminder = null;
            Value = null;
            if (number.HasValue)
            {
                Value = Math.Floor(number.Value);
                Reminder = (number - Math.Truncate(number.Value));
            }
        }



        decimal? number= 50.55m;             
        ReminderDigints(number, out decimal? Value, out decimal? Reminder);

0
public static decimal TruncateDecimalPlaces(this decimal value, int precision)
    {
        try
        {
            step = (decimal)Math.Pow(10, precision);
            decimal tmp = Math.Truncate(step * value);
            return tmp / step;
        }
        catch (OverflowException)
        {
            step = (decimal)Math.Pow(10, -1 * precision);
            return value - (value % step);
        }
    }

-2

实际上,您希望从3.4679获得3.46。这只是字符的表示,因此与数学函数无关。数学函数并非旨在完成此工作。只需使用以下代码。

Dim str1 As String
str1=""
str1 ="3.4679" 
  Dim substring As String = str1.Substring(0, 3)

    ' Write the results to the screen.
    Console.WriteLine("Substring: {0}", substring)

Or 
    Please use the following code.
Public function result(ByVal x1 As Double) As String 
  Dim i as  Int32
  i=0
  Dim y as String
  y = ""
  For Each ch as Char In x1.ToString
    If i>3 then
     Exit For
    Else
    y + y +ch
    End if
    i=i+1
  Next
  return y
End Function

上面的代码可以修改为任何数字将以下代码放入按钮单击事件中

Dim str As String 
str= result(3.4679)
 MsgBox("The number is " & str)

-2

关于什么

var i = Math.Truncate(number);var r = i + Math.Truncate((number - i) * 100) / 100;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.