Ответ 1
value = Math.Truncate(100 * value) / 100;
Остерегайтесь того, что такие фракции не могут быть точно представлены в плавающей точке.
Допустим, что у меня есть значение 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));
}
value = Math.Truncate(100 * value) / 100;
Остерегайтесь того, что такие фракции не могут быть точно представлены в плавающей точке.
Было бы более полезно иметь полную функцию для реального использования обрезания десятичного числа в С#. Это можно было бы преобразовать в метод десятичного расширения довольно легко, если бы вы хотели:
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)
Одна проблема с другими примерами заключается в том, что они умножают входное значение перед его делением. Здесь есть краевой случай, что вы можете переполнять десятичное число, умножая сначала, край, но что-то, с чем я столкнулся. Безопаснее иметь дело с дробной частью отдельно следующим образом:
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;
}
Используйте оператор модуля:
var fourPlaces = 0.5485M;
var twoPlaces = fourPlaces - (fourPlaces % 0.01M);
результат: 0.54
Универсальный и быстрый метод (без 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;
}
Я оставлю решение для десятичных чисел.
Некоторые из решений для десятичных знаков здесь подвержены переполнению (если мы передаем очень большое десятичное число, и метод попытается его умножить).
Решение 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));
}
будет ли это работать для вас?
Console.Write(((int)(3.4679999999*100))/100.0);
Может ли ((long)(3.4679 * 100)) / 100.0
предоставить то, что вы хотите?
Вот метод расширения:
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
Это старый вопрос, но многие anwsers плохо работают или переполнены для больших чисел. Я думаю, что ответ д. Нестерова самый лучший: надежный, простой и быстрый. Я просто хочу добавить свои два цента.
Я поиграл с десятичными числами, а также проверил исходный код. Из 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);
}
Этот метод не быстрее, чем Д. Нестеров, и он более сложный, поэтому я немного поиграл. Я предполагаю, что необходимость создания вспомогательного 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, процессоре Intel Core i3 с тактовой частотой 3,06 ГГц и нацеленном на .NetCore 2.1 этот метод выглядит намного быстрее, чем Д. Нестеров (я не буду давать цифры, так как, как я уже говорил, мои тесты не являются строгими). Это зависит от того, кто реализует это, чтобы оценить, окупается ли прирост производительности за сложность добавленного кода.
Если вы не слишком беспокоитесь о производительности, и конечный результат может быть строкой, следующий подход будет устойчивым к проблемам с плавающей точностью:
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);
}
Вот моя реализация функции 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);
}
как насчет этого?
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
Помимо вышеупомянутых решений, мы можем достичь другого.
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
В некоторых условиях это может быть достаточно.
У меня было десятичное значение
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"
я использую эту функцию для усечения значения после десятичной в строковой переменной
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;
}
}
Вот что я сделал:
c1 = a1 - b1;
d1 = Math.Ceiling(c1 * 100) / 100;
вычитание двух введенных чисел без округления до десятичной дроби. потому что другие решения не работают для меня. не знаю, будет ли это работать для других, я просто хочу поделиться этим :) Надеюсь, что это работает для тех, кто находит решение проблемы, аналогичной моей. Благодаря
PS: я новичок, поэтому не стесняйтесь указывать на это.: D это хорошо, если вы на самом деле имеете дело с деньгами, ведь центы не так ли? у него только 2 знака после запятой и округление это нет нет.
На самом деле вы хотите 3.46 от 3.4679. Это только представление символов. Так что нет ничего общего с математической функцией. Функция Math не предназначена для выполнения этой работы. Просто используйте следующий код.
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)