Обрізати два десяткових знаки без округлення


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 

Відповіді:


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

Слідкуйте за тим, щоб подібні дроби не могли бути точно представлені у плаваючій точці.


12
Використовуйте десятковий для своїх значень, і ця відповідь спрацює. Навряд чи завжди працюватиме в будь-якому поданні з плаваючою точкою.
driis

1
Це змушує мене замислитись, чи має бути можливість задати напрямок округлення в літералах з плаваючою комою. Гмммм.
Steve314

Має бути якийсь спосіб сказати програмісту, що обчислення з припущенням, що число може зберігати більше 308 цифр, є вкрай недоречним. Двомісний може зберігати лише 15. Переповнення тут дуже велика особливість, воно переповнене досить сильно.
Ганс Пасант

Вибачте, я подумав, що "значення" є десятковим.
нічний кодер

54

Було б корисніше мати повноцінну функцію для використання в реальному світі обрізання десяткової точки в C #. Це можна було б легко перетворити на десятковий метод розширення, якщо ви хочете:

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
Це переповниться у великій кількості.
нічний кодер

1
Щоб додати нічний кодер, той факт, що ви використовуєте Int32 як посередник у своїй функції, спричинить переповнення. Ви повинні використовувати Int64, якщо ви дійсно повинні передавати його цілому. Питання в тому, чому ви хочете все-таки понести цю додаткову витрату, оскільки Truncate все одно повертає інтеграли Decimal. Просто зробіть щось на кшталт: десятковий крок = (десятковий) Math.Pow (10, точність); повернути Math.Truncate (значення * значення *) / крок;
Сарел Естерхуайзен

Я кинув акторський склад на Integer. Я залишив їм окремі рядки для кращої читабельності та розуміння того, як функція працює.
Коргалоре

27

Використовуйте оператор модуля:

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

результат: 0,54


1
Я не розумію (читайте: не витратив час на перевірку) усіх цих інших вигадливих рішень, це саме те , що я шукав. Дякую!
Ісаак Бейкер

Запуск цього .Net Fiddle clickic виробляє 0.5400... Відповідь Д. Нестерова нижче дала очікуване 0.54.
підписувати

Ви розумієте, @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 (мабуть, нормально для більшості людей).
РічардОД

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

@TheKingDave: напевно, це не має значення, але як фактор не може мати десяткових знаків, слід краще моделювати його так довго, чи не так?
Ігнасіо Солер Гарсія

@SoMoS Для мене Decimal працював краще, оскільки він дав мені найвищі значення для зберігання фактору. Він все ще має обмеження, але він досить великий для мого застосування. Довго, з іншого боку, не вдалося зберегти достатньо велику кількість для моєї програми. Наприклад, якщо ви зробите усікання (25) з довгим, то буде деяка неточність.
TheKingDave

Оновлено, щоб дозволити усікання більшої кількості місць відповідно до пропозиції @TheKingDave, дякую.
Тім Ллойд

6

Я залишу рішення для десяткових чисел.

Деякі рішення десятків тут схильні до переповнення (якщо ми передамо дуже велике десяткове число і метод спробує його помножити).

Рішення Тіма Ллойда захищено від переливу, але це не дуже швидко.

Наступне рішення приблизно в 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
Мені не подобається суфікс "Ех" до нього. C # підтримує перевантаження, ваш Truncateметод буде згруповано разом з .net рідними, надаючи користувачеві безперебійний досвід.
Gqqnbig

1
Ваш алгоритм призводить до неправильних результатів. Режим MidpointRounding за замовчуванням - Округлення банку, яке округляє 0,5 до найближчого парного значення. Assert.AreEqual(1.1m, 1.12m.TruncateEx(1));не вдається через це. Якщо ви вкажете "нормальне" округлення (AwayFromZero) у виклику Math.Round, тоді Assert.AreEqual(0m, 0m.TruncateEx(1));не вдасться
Джон Сенчина

1
Єдиний спосіб роботи цього рішення полягає в тому, якщо ви використовуєте MidpointRounding.AwayFromZeroта спеціально код для обробки значення 0.
Джон Сенчина

1
Jon правильний: 0m.TruncateEx (0) призводить до -1, якщо 0 явно не обробляється. Так само -11m.TruncateEx (0) призводить до -10, якщо MidpointRounding.AwayFromZero не використовується в Math.Round. Схоже, добре працює з цими модифікаціями.
Ho Ho Ho

1
Навіть із змінами для AwayFromZero та чіткою обробкою 0, -9999999999999999999999999999m.TruncateEx (0) призводить до -999999999999999999999999999898, тому в деяких випадках це все-таки помиляється.
Ho Ho Ho

3

Це давнє запитання, але багато шанувальників не спрацьовують добре або переповнюються для великої кількості. Я думаю, що відповідь Д. Нестерова найкраща: надійний, простий і швидкий. Я просто хочу додати свої два центи. Я пограв із десятковими знаками, а також перевірив вихідний код . З 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 (Десяткове 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 ГГц та орієнтацію. як я вже згадував, мої тести не суворі). Той, хто реалізує це, залежить від того, чи окупність продуктивності окупиться за додаткову складність коду.


Мені довелося виголосити через всі думки та зусилля. Ви встановили Нестеров як орієнтир і продовжували рухатися - капелюхи.
AndrewBenjamin



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.0099999999999999999999999999MM, яке прагне форматувати | 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

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,46 від 3,4679. Це лише представлення символів. Тому математика не має нічого спільного. Функція математики не призначена для виконання цієї роботи. Просто використовуйте наступний код.

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.