Форматирование чисел со значимыми цифрами в C#

У меня есть некоторые десятичные данные, которые я помещаю в список SharePoint, где его нужно просмотреть. Я хотел бы ограничить количество значимых цифр, отображаемых в данных результата, на основе моих знаний конкретного расчета. Иногда это будет 3, поэтому 12345 станет 12300, а 0.012345 станет 0.0123. Иногда это будет 4 или 5. Есть ли удобный способ справиться с этим?

8 ответов


посмотреть: RoundToSignificantFigures по "P папа".
Я соединил его метод с другим, который мне понравился.

округление до значимых цифр намного проще в TSQL, где метод округления основан на позиции округления, а не на количестве десятичных знаков - что имеет место в .Net math.круглый. Вы можете округлить число в TSQL до отрицательных мест, которые будут округляться на целых числах, поэтому масштабирование не требуется.

Также см. Этот другой нить. Способ Pyrolistical это хорошо.

конечные нули часть проблемы кажется мне больше Строковой операцией, поэтому я включил метод расширения ToString (), который при необходимости будет заполнять нули.

using System;
using System.Globalization;

public static class Precision
{
    // 2^-24
    public const float FLOAT_EPSILON = 0.0000000596046448f;

    // 2^-53
    public const double DOUBLE_EPSILON = 0.00000000000000011102230246251565d;

    public static bool AlmostEquals(this double a, double b, double epsilon = DOUBLE_EPSILON)
    {
        // ReSharper disable CompareOfFloatsByEqualityOperator
        if (a == b)
        {
            return true;
        }
        // ReSharper restore CompareOfFloatsByEqualityOperator

        return (System.Math.Abs(a - b) < epsilon);
    }

    public static bool AlmostEquals(this float a, float b, float epsilon = FLOAT_EPSILON)
    {
        // ReSharper disable CompareOfFloatsByEqualityOperator
        if (a == b)
        {
            return true;
        }
        // ReSharper restore CompareOfFloatsByEqualityOperator

        return (System.Math.Abs(a - b) < epsilon);
    }
}

public static class SignificantDigits
{
    public static double Round(this double value, int significantDigits)
    {
        int unneededRoundingPosition;
        return RoundSignificantDigits(value, significantDigits, out unneededRoundingPosition);
    }

    public static string ToString(this double value, int significantDigits)
    {
        // this method will round and then append zeros if needed.
        // i.e. if you round .002 to two significant figures, the resulting number should be .0020.

        var currentInfo = CultureInfo.CurrentCulture.NumberFormat;

        if (double.IsNaN(value))
        {
            return currentInfo.NaNSymbol;
        }

        if (double.IsPositiveInfinity(value))
        {
            return currentInfo.PositiveInfinitySymbol;
        }

        if (double.IsNegativeInfinity(value))
        {
            return currentInfo.NegativeInfinitySymbol;
        }

        int roundingPosition;
        var roundedValue = RoundSignificantDigits(value, significantDigits, out roundingPosition);

        // when rounding causes a cascading round affecting digits of greater significance, 
        // need to re-round to get a correct rounding position afterwards
        // this fixes a bug where rounding 9.96 to 2 figures yeilds 10.0 instead of 10
        RoundSignificantDigits(roundedValue, significantDigits, out roundingPosition);

        if (Math.Abs(roundingPosition) > 9)
        {
            // use exponential notation format
            // ReSharper disable FormatStringProblem
            return string.Format(currentInfo, "{0:E" + (significantDigits - 1) + "}", roundedValue);
            // ReSharper restore FormatStringProblem
        }

        // string.format is only needed with decimal numbers (whole numbers won't need to be padded with zeros to the right.)
        // ReSharper disable FormatStringProblem
        return roundingPosition > 0 ? string.Format(currentInfo, "{0:F" + roundingPosition + "}", roundedValue) : roundedValue.ToString(currentInfo);
        // ReSharper restore FormatStringProblem
    }

    private static double RoundSignificantDigits(double value, int significantDigits, out int roundingPosition)
    {
        // this method will return a rounded double value at a number of signifigant figures.
        // the sigFigures parameter must be between 0 and 15, exclusive.

        roundingPosition = 0;

        if (value.AlmostEquals(0d))
        {
            roundingPosition = significantDigits - 1;
            return 0d;
        }

        if (double.IsNaN(value))
        {
            return double.NaN;
        }

        if (double.IsPositiveInfinity(value))
        {
            return double.PositiveInfinity;
        }

        if (double.IsNegativeInfinity(value))
        {
            return double.NegativeInfinity;
        }

        if (significantDigits < 1 || significantDigits > 15)
        {
            throw new ArgumentOutOfRangeException("significantDigits", value, "The significantDigits argument must be between 1 and 15.");
        }

        // The resulting rounding position will be negative for rounding at whole numbers, and positive for decimal places.
        roundingPosition = significantDigits - 1 - (int)(Math.Floor(Math.Log10(Math.Abs(value))));

        // try to use a rounding position directly, if no scale is needed.
        // this is because the scale mutliplication after the rounding can introduce error, although 
        // this only happens when you're dealing with really tiny numbers, i.e 9.9e-14.
        if (roundingPosition > 0 && roundingPosition < 16)
        {
            return Math.Round(value, roundingPosition, MidpointRounding.AwayFromZero);
        }

        // Shouldn't get here unless we need to scale it.
        // Set the scaling value, for rounding whole numbers or decimals past 15 places
        var scale = Math.Pow(10, Math.Ceiling(Math.Log10(Math.Abs(value))));

        return Math.Round(value / scale, significantDigits, MidpointRounding.AwayFromZero) * scale;
    }
}

Это может сделать трюк:


double Input1 = 1234567;
string Result1 = Convert.ToDouble(String.Format("{0:G3}",Input1)).ToString("R0");

double Input2 = 0.012345;
string Result2 = Convert.ToDouble(String.Format("{0:G3}", Input2)).ToString("R6");

изменение G3 на G4 дает самый странный результат. Кажется, это округляет значащие цифры?


Я в конечном итоге зацепил некоторый код из http://ostermiller.org/utils/SignificantFigures.java.html. Это было на java, поэтому я сделал быстрый поиск/замену и переформатирование resharper, чтобы сделать сборку C#. Кажется, это хорошо работает для моих значительных потребностей фигуры. FWIW, я удалил его комментарии javadoc, чтобы сделать его более кратким здесь, но исходный код документирован довольно хорошо.

/*
 * Copyright (C) 2002-2007 Stephen Ostermiller
 * http://ostermiller.org/contact.pl?regarding=Java+Utilities
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * See COPYING.TXT for details.
 */
public class SignificantFigures
{
    private String original;
    private StringBuilder _digits;
    private int mantissa = -1;
    private bool sign = true;
    private bool isZero = false;
    private bool useScientificNotation = true;

    public SignificantFigures(String number)
    {
        original = number;
        Parse(original);
    }


    public SignificantFigures(double number)
    {
        original = Convert.ToString(number);
        try
        {
            Parse(original);
        }
        catch (Exception nfe)
        {
            _digits = null;
        }
    }


    public bool UseScientificNotation
    {
        get { return useScientificNotation; }
        set { useScientificNotation = value; }
    }


    public int GetNumberSignificantFigures()
    {
        if (_digits == null) return 0;
        return _digits.Length;
    }


    public SignificantFigures SetLSD(int place)
    {
        SetLMSD(place, Int32.MinValue);
        return this;
    }

    public SignificantFigures SetLMSD(int leastPlace, int mostPlace)
    {
        if (_digits != null && leastPlace != Int32.MinValue)
        {
            int significantFigures = _digits.Length;
            int current = mantissa - significantFigures + 1;
            int newLength = significantFigures - leastPlace + current;
            if (newLength <= 0)
            {
                if (mostPlace == Int32.MinValue)
                {
                    original = "NaN";
                    _digits = null;
                }
                else
                {
                    newLength = mostPlace - leastPlace + 1;
                    _digits.Length = newLength;
                    mantissa = leastPlace;
                    for (int i = 0; i < newLength; i++)
                    {
                        _digits[i] = '0';
                    }
                    isZero = true;
                    sign = true;
                }
            }
            else
            {
                _digits.Length = newLength;
                for (int i = significantFigures; i < newLength; i++)
                {
                    _digits[i] = '0';
                }
            }
        }
        return this;
    }


    public int GetLSD()
    {
        if (_digits == null) return Int32.MinValue;
        return mantissa - _digits.Length + 1;
    }

    public int GetMSD()
    {
        if (_digits == null) return Int32.MinValue;
        return mantissa + 1;
    }

    public override String ToString()
    {
        if (_digits == null) return original;
        StringBuilder digits = new StringBuilder(this._digits.ToString());
        int length = digits.Length;
        if ((mantissa <= -4 || mantissa >= 7 ||
             (mantissa >= length &&
              digits[digits.Length - 1] == '0') ||
             (isZero && mantissa != 0)) && useScientificNotation)
        {
            // use scientific notation.
            if (length > 1)
            {
                digits.Insert(1, '.');
            }
            if (mantissa != 0)
            {
                digits.Append("E" + mantissa);
            }
        }
        else if (mantissa <= -1)
        {
            digits.Insert(0, "0.");
            for (int i = mantissa; i < -1; i++)
            {
                digits.Insert(2, '0');
            }
        }
        else if (mantissa + 1 == length)
        {
            if (length > 1 && digits[digits.Length - 1] == '0')
            {
                digits.Append('.');
            }
        }
        else if (mantissa < length)
        {
            digits.Insert(mantissa + 1, '.');
        }
        else
        {
            for (int i = length; i <= mantissa; i++)
            {
                digits.Append('0');
            }
        }
        if (!sign)
        {
            digits.Insert(0, '-');
        }
        return digits.ToString();
    }


    public String ToScientificNotation()
    {
        if (_digits == null) return original;
        StringBuilder digits = new StringBuilder(this._digits.ToString());
        int length = digits.Length;
        if (length > 1)
        {
            digits.Insert(1, '.');
        }
        if (mantissa != 0)
        {
            digits.Append("E" + mantissa);
        }
        if (!sign)
        {
            digits.Insert(0, '-');
        }
        return digits.ToString();
    }


    private const int INITIAL = 0;
    private const int LEADZEROS = 1;
    private const int MIDZEROS = 2;
    private const int DIGITS = 3;
    private const int LEADZEROSDOT = 4;
    private const int DIGITSDOT = 5;
    private const int MANTISSA = 6;
    private const int MANTISSADIGIT = 7;

    private void Parse(String number)
    {
        int length = number.Length;
        _digits = new StringBuilder(length);
        int state = INITIAL;
        int mantissaStart = -1;
        bool foundMantissaDigit = false;
        // sometimes we don't know if a zero will be
        // significant or not when it is encountered.
        // keep track of the number of them so that
        // the all can be made significant if we find
        // out that they are.
        int zeroCount = 0;
        int leadZeroCount = 0;

        for (int i = 0; i < length; i++)
        {
            char c = number[i];
            switch (c)
            {
                case '.':
                    {
                        switch (state)
                        {
                            case INITIAL:
                            case LEADZEROS:
                                {
                                    state = LEADZEROSDOT;
                                }
                                break;
                            case MIDZEROS:
                                {
                                    // we now know that these zeros
                                    // are more than just trailing place holders.
                                    for (int j = 0; j < zeroCount; j++)
                                    {
                                        _digits.Append('0');
                                    }
                                    zeroCount = 0;
                                    state = DIGITSDOT;
                                }
                                break;
                            case DIGITS:
                                {
                                    state = DIGITSDOT;
                                }
                                break;
                            default:
                                {
                                    throw new Exception(
                                        "Unexpected character '" + c + "' at position " + i
                                        );
                                }
                        }
                    }
                    break;
                case '+':
                    {
                        switch (state)
                        {
                            case INITIAL:
                                {
                                    sign = true;
                                    state = LEADZEROS;
                                }
                                break;
                            case MANTISSA:
                                {
                                    state = MANTISSADIGIT;
                                }
                                break;
                            default:
                                {
                                    throw new Exception(
                                        "Unexpected character '" + c + "' at position " + i
                                        );
                                }
                        }
                    }
                    break;
                case '-':
                    {
                        switch (state)
                        {
                            case INITIAL:
                                {
                                    sign = false;
                                    state = LEADZEROS;
                                }
                                break;
                            case MANTISSA:
                                {
                                    state = MANTISSADIGIT;
                                }
                                break;
                            default:
                                {
                                    throw new Exception(
                                        "Unexpected character '" + c + "' at position " + i
                                        );
                                }
                        }
                    }
                    break;
                case '0':
                    {
                        switch (state)
                        {
                            case INITIAL:
                            case LEADZEROS:
                                {
                                    // only significant if number
                                    // is all zeros.
                                    zeroCount++;
                                    leadZeroCount++;
                                    state = LEADZEROS;
                                }
                                break;
                            case MIDZEROS:
                            case DIGITS:
                                {
                                    // only significant if followed
                                    // by a decimal point or nonzero digit.
                                    mantissa++;
                                    zeroCount++;
                                    state = MIDZEROS;
                                }
                                break;
                            case LEADZEROSDOT:
                                {
                                    // only significant if number
                                    // is all zeros.
                                    mantissa--;
                                    zeroCount++;
                                    state = LEADZEROSDOT;
                                }
                                break;
                            case DIGITSDOT:
                                {
                                    // non-leading zeros after
                                    // a decimal point are always
                                    // significant.
                                    _digits.Append(c);
                                }
                                break;
                            case MANTISSA:
                            case MANTISSADIGIT:
                                {
                                    foundMantissaDigit = true;
                                    state = MANTISSADIGIT;
                                }
                                break;
                            default:
                                {
                                    throw new Exception(
                                        "Unexpected character '" + c + "' at position " + i
                                        );
                                }
                        }
                    }
                    break;
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    {
                        switch (state)
                        {
                            case INITIAL:
                            case LEADZEROS:
                            case DIGITS:
                                {
                                    zeroCount = 0;
                                    _digits.Append(c);
                                    mantissa++;
                                    state = DIGITS;
                                }
                                break;
                            case MIDZEROS:
                                {
                                    // we now know that these zeros
                                    // are more than just trailing place holders.
                                    for (int j = 0; j < zeroCount; j++)
                                    {
                                        _digits.Append('0');
                                    }
                                    zeroCount = 0;
                                    _digits.Append(c);
                                    mantissa++;
                                    state = DIGITS;
                                }
                                break;
                            case LEADZEROSDOT:
                            case DIGITSDOT:
                                {
                                    zeroCount = 0;
                                    _digits.Append(c);
                                    state = DIGITSDOT;
                                }
                                break;
                            case MANTISSA:
                            case MANTISSADIGIT:
                                {
                                    state = MANTISSADIGIT;
                                    foundMantissaDigit = true;
                                }
                                break;
                            default:
                                {
                                    throw new Exception(
                                        "Unexpected character '" + c + "' at position " + i
                                        );
                                }
                        }
                    }
                    break;
                case 'E':
                case 'e':
                    {
                        switch (state)
                        {
                            case INITIAL:
                            case LEADZEROS:
                            case DIGITS:
                            case LEADZEROSDOT:
                            case DIGITSDOT:
                                {
                                    // record the starting point of the mantissa
                                    // so we can do a substring to get it back later
                                    mantissaStart = i + 1;
                                    state = MANTISSA;
                                }
                                break;
                            default:
                                {
                                    throw new Exception(
                                        "Unexpected character '" + c + "' at position " + i
                                        );
                                }
                        }
                    }
                    break;
                default:
                    {
                        throw new Exception(
                            "Unexpected character '" + c + "' at position " + i
                            );
                    }
            }
        }
        if (mantissaStart != -1)
        {
            // if we had found an 'E'
            if (!foundMantissaDigit)
            {
                // we didn't actually find a mantissa to go with.
                throw new Exception(
                    "No digits in mantissa."
                    );
            }
            // parse the mantissa.
            mantissa += Convert.ToInt32(number.Substring(mantissaStart));
        }
        if (_digits.Length == 0)
        {
            if (zeroCount > 0)
            {
                // if nothing but zeros all zeros are significant.
                for (int j = 0; j < zeroCount; j++)
                {
                    _digits.Append('0');
                }
                mantissa += leadZeroCount;
                isZero = true;
                sign = true;
            }
            else
            {
                // a hack to catch some cases that we could catch
                // by adding a ton of extra states.  Things like:
                // "e2" "+e2" "+." "." "+" etc.
                throw new Exception(
                    "No digits in number."
                    );
            }
        }
    }

    public SignificantFigures SetNumberSignificantFigures(int significantFigures)
    {
        if (significantFigures <= 0)
            throw new ArgumentException("Desired number of significant figures must be positive.");
        if (_digits != null)
        {
            int length = _digits.Length;
            if (length < significantFigures)
            {
                // number is not long enough, pad it with zeros.
                for (int i = length; i < significantFigures; i++)
                {
                    _digits.Append('0');
                }
            }
            else if (length > significantFigures)
            {
                // number is too long chop some of it off with rounding.
                bool addOne; // we need to round up if true.
                char firstInSig = _digits[significantFigures];
                if (firstInSig < '5')
                {
                    // first non-significant digit less than five, round down.
                    addOne = false;
                }
                else if (firstInSig == '5')
                {
                    // first non-significant digit equal to five
                    addOne = false;
                    for (int i = significantFigures + 1; !addOne && i < length; i++)
                    {
                        // if its followed by any non-zero digits, round up.
                        if (_digits[i] != '0')
                        {
                            addOne = true;
                        }
                    }
                    if (!addOne)
                    {
                        // if it was not followed by non-zero digits
                        // if the last significant digit is odd round up
                        // if the last significant digit is even round down
                        addOne = (_digits[significantFigures - 1] & 1) == 1;
                    }
                }
                else
                {
                    // first non-significant digit greater than five, round up.
                    addOne = true;
                }
                // loop to add one (and carry a one if added to a nine)
                // to the last significant digit
                for (int i = significantFigures - 1; addOne && i >= 0; i--)
                {
                    char digit = _digits[i];
                    if (digit < '9')
                    {
                        _digits[i] = (char) (digit + 1);
                        addOne = false;
                    }
                    else
                    {
                        _digits[i] = '0';
                    }
                }
                if (addOne)
                {
                    // if the number was all nines
                    _digits.Insert(0, '1');
                    mantissa++;
                }
                // chop it to the correct number of figures.
                _digits.Length = significantFigures;
            }
        }
        return this;
    }

    public double ToDouble()
    {
        return Convert.ToDouble(original);
    }

    public static String Format(double number, int significantFigures)
    {
        SignificantFigures sf = new SignificantFigures(number);
        sf.SetNumberSignificantFigures(significantFigures);
        return sf.ToString();
    }
}

у меня есть короткий ответ на вычисление значащие цифры ряда. Вот код и результаты тестов...

using System;
using System.Collections.Generic;

namespace ConsoleApplicationRound
{
    class Program
    {
        static void Main(string[] args)
        {
            //char cDecimal = '.';    // for English cultures
            char cDecimal = ',';    // for German cultures
            List<double> l_dValue = new List<double>();
            ushort usSignificants = 5;

            l_dValue.Add(0);
            l_dValue.Add(0.000640589);
            l_dValue.Add(-0.000640589);
            l_dValue.Add(-123.405009);
            l_dValue.Add(123.405009);
            l_dValue.Add(-540);
            l_dValue.Add(540);
            l_dValue.Add(-540911);
            l_dValue.Add(540911);
            l_dValue.Add(-118.2);
            l_dValue.Add(118.2);
            l_dValue.Add(-118.18);
            l_dValue.Add(118.18);
            l_dValue.Add(-118.188);
            l_dValue.Add(118.188);

            foreach (double d in l_dValue)
            {
                Console.WriteLine("d = Maths.Round('" +
                    cDecimal + "', " + d + ", " + usSignificants +
                    ") = " + Maths.Round(
                    cDecimal, d, usSignificants));
            }

            Console.Read();
        }
    }
}

используемый класс математики выглядит следующим образом:

using System;
using System.Text;

namespace ConsoleApplicationRound
{
    class Maths
    {
        /// <summary>
        ///     The word "Window"
        /// </summary>
        private static String m_strZeros = "000000000000000000000000000000000";
        /// <summary>
        ///     The minus sign
        /// </summary>
        public const char m_cDASH = '-';

        /// <summary>
        ///     Determines the number of digits before the decimal point
        /// </summary>
        /// <param name="cDecimal">
        ///     Language-specific decimal separator
        /// </param>
        /// <param name="strValue">
        ///     Value to be scrutinised
        /// </param>
        /// <returns>
        ///     Nr. of digits before the decimal point
        /// </returns>
        private static ushort NrOfDigitsBeforeDecimal(char cDecimal, String strValue)
        {
            short sDecimalPosition = (short)strValue.IndexOf(cDecimal);
            ushort usSignificantDigits = 0;

            if (sDecimalPosition >= 0)
            {
                strValue = strValue.Substring(0, sDecimalPosition + 1);
            }

            for (ushort us = 0; us < strValue.Length; us++)
            {
                if (strValue[us] != m_cDASH) usSignificantDigits++;

                if (strValue[us] == cDecimal)
                {
                    usSignificantDigits--;
                    break;
                }
            }

            return usSignificantDigits;
        }

        /// <summary>
        ///     Rounds to a fixed number of significant digits
        /// </summary>
        /// <param name="d">
        ///     Number to be rounded
        /// </param>
        /// <param name="usSignificants">
        ///     Requested significant digits
        /// </param>
        /// <returns>
        ///     The rounded number
        /// </returns>
        public static String Round(char cDecimal,
            double d,
            ushort usSignificants)
        {
            StringBuilder value = new StringBuilder(Convert.ToString(d));

            short sDecimalPosition = (short)value.ToString().IndexOf(cDecimal);
            ushort usAfterDecimal = 0;
            ushort usDigitsBeforeDecimalPoint =
                NrOfDigitsBeforeDecimal(cDecimal, value.ToString());

            if (usDigitsBeforeDecimalPoint == 1)
            {
                usAfterDecimal = (d == 0)
                    ? usSignificants
                    : (ushort)(value.Length - sDecimalPosition - 2);
            }
            else
            {
                if (usSignificants >= usDigitsBeforeDecimalPoint)
                {
                    usAfterDecimal =
                        (ushort)(usSignificants - usDigitsBeforeDecimalPoint);
                }
                else
                {
                    double dPower = Math.Pow(10,
                        usDigitsBeforeDecimalPoint - usSignificants);

                    d = dPower*(long)(d/dPower);
                }
            }

            double dRounded = Math.Round(d, usAfterDecimal);
            StringBuilder result = new StringBuilder();

            result.Append(dRounded);
            ushort usDigits = (ushort)result.ToString().Replace(
                Convert.ToString(cDecimal), "").Replace(
                Convert.ToString(m_cDASH), "").Length;

            // Add lagging zeros, if necessary:
            if (usDigits < usSignificants)
            {
                if (usAfterDecimal != 0)
                {
                    if (result.ToString().IndexOf(cDecimal) == -1)
                    {
                        result.Append(cDecimal);
                    }

                    int i = (d == 0) ? 0 : Math.Min(0, usDigits - usSignificants);

                    result.Append(m_strZeros.Substring(0, usAfterDecimal + i));
                }
            }

            return result.ToString();
        }
    }
}

любой ответ с более коротким кодом?


вы можете получить элегантный бит идеального округления с помощью метода GetBits на Decimal и используя BigInteger для выполнения маскировки.

некоторые utils

    public static int CountDigits
        (BigInteger number) => ((int)BigInteger.Log10(number))+1;

    private static readonly BigInteger[] BigPowers10 
       = Enumerable.Range(0, 100)
                 .Select(v => BigInteger.Pow(10, v))
                 .ToArray();

основные функции

    public static decimal RoundToSignificantDigits
        (this decimal num,
         short n)
    {
        var bits = decimal.GetBits(num);
        var u0 = unchecked((uint)bits[0]);
        var u1 = unchecked((uint)bits[1]);
        var u2 = unchecked((uint)bits[2]);

        var i = new BigInteger(u0)
                + (new BigInteger(u1) << 32)
                + (new BigInteger(u2) << 64);

        var d = CountDigits(i);

        var delta = d - n;
        if (delta < 0)
            return num;

        var scale = BigPowers10[delta];
        var div = i/scale;
        var rem = i%scale;
        var up = rem > scale/2;
        if (up)
            div += 1;
        var shifted = div*scale;

        bits[0] =unchecked((int)(uint) (shifted & BigUnitMask));
        bits[1] =unchecked((int)(uint) (shifted>>32 & BigUnitMask));
        bits[2] =unchecked((int)(uint) (shifted>>64 & BigUnitMask));

        return new decimal(bits);
    }

тестовый случай 0

    public void RoundToSignificantDigits()
    {
        WMath.RoundToSignificantDigits(0.0012345m, 2).Should().Be(0.0012m);
        WMath.RoundToSignificantDigits(0.0012645m, 2).Should().Be(0.0013m);
        WMath.RoundToSignificantDigits(0.040000000000000008, 6).Should().Be(0.04);
        WMath.RoundToSignificantDigits(0.040000010000000008, 6).Should().Be(0.04);
        WMath.RoundToSignificantDigits(0.040000100000000008, 6).Should().Be(0.0400001);
        WMath.RoundToSignificantDigits(0.040000110000000008, 6).Should().Be(0.0400001);
        WMath.RoundToSignificantDigits(0.20000000000000004, 6).Should().Be(0.2);
        WMath.RoundToSignificantDigits(0.10000000000000002, 6).Should().Be(0.1);
        WMath.RoundToSignificantDigits(0.0, 6).Should().Be(0.0);

    }

тестовый пример 1

     public void RoundToSigFigShouldWork()
    {
        1.2m.RoundToSignificantDigits(1).Should().Be(1m);
        0.01235668m.RoundToSignificantDigits(3).Should().Be(0.0124m);
        0.01m.RoundToSignificantDigits(3).Should().Be(0.01m);

        1.23456789123456789123456789m.RoundToSignificantDigits(4)
                                     .Should().Be(1.235m);

        1.23456789123456789123456789m.RoundToSignificantDigits(16)
                                     .Should().Be(1.234567891234568m);

        1.23456789123456789123456789m.RoundToSignificantDigits(24)
                                     .Should().Be(1.23456789123456789123457m);

        1.23456789123456789123456789m.RoundToSignificantDigits(27)
                                     .Should().Be(1.23456789123456789123456789m);
    }

нашел в этой статье быстрый поиск по ней. В основном это преобразуется в строку и проходит по символам в этом массиве по одному, пока не достигнет максимума. важность. Это сработает?


следующий код не совсем соответствует спецификации, так как он не пытается округлить что-либо слева от десятичной точки. Но это проще, чем все остальное, представленное здесь (до сих пор). Я был очень удивлен, что у C# нет встроенного метода для обработки этого.

static public string SignificantDigits(double d, int digits=10)
{
    int magnitude = (d == 0.0) ? 0 : (int)Math.Floor(Math.Log10(Math.Abs(d))) + 1;
    digits -= magnitude;
    if (digits < 0)
        digits = 0;
    string fmt = "f" + digits.ToString();
    return d.ToString(fmt);
}

Как я помню, "значимые цифры" означает количество цифр после разделителя точек, поэтому 3 значимые цифры для 0.012345 будут 0.012, а не 0.0123, но это действительно не имеет значения для решения. Я также понимаю, что вы хотите "обнулить" последние цифры в определенной степени, если число > 1. Вы пишете, что 12345 станет 12300, но я не уверен, хотите ли вы, чтобы 123456 стал 1230000 или 123400 ? Мое решение делает последнее. Вместо расчета коэффициента конечно, можно сделать небольшой инициализированный массив, если у вас есть только несколько вариантов.

private static string FormatToSignificantFigures(decimal number, int amount)
{
    if (number > 1)
    {
        int factor = Factor(amount);
        return ((int)(number/factor)*factor).ToString();
    }

    NumberFormatInfo nfi = new CultureInfo("en-US", false).NumberFormat;
    nfi.NumberDecimalDigits = amount;

    return(number.ToString("F", nfi));
}

private static int Factor(int x)
{
    return DoCalcFactor(10, x-1);
}

private static int DoCalcFactor(int x, int y)
{
    if (y == 1) return x;
    return 10*DoCalcFactor(x, y - 1);
}

С уважением Карстен!--2-->