Форматирование чисел со значимыми цифрами в C # - PullRequest
31 голосов
/ 01 октября 2008

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

Ответы [ 8 ]

22 голосов
/ 01 января 2010

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

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

Также смотрите эту другую ветку . Метод пиролистики хорош.

Мне кажется, что часть проблемы с конечными нулями больше похожа на строковую операцию, поэтому я включил метод расширения 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;
    }
}
5 голосов
/ 01 октября 2008

Это может помочь:


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 дает самый странный результат. Кажется, чтобы округлить значащие цифры?

1 голос
/ 15 декабря 2015

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

Некоторые утилиты

    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);
    }
1 голос
/ 26 января 2012

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

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

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

1 голос
/ 02 октября 2008

Я закончил тем, что получил код из http://ostermiller.org/utils/SignificantFigures.java.html. Это было в Java, поэтому я сделал быстрый поиск / замену и некоторое переформатирование для создания C #. Кажется, это хорошо работает для моих значительных потребностей фигуры. Кстати, я удалил его комментарии 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();
    }
}
0 голосов
/ 23 июля 2014

Следующий код не совсем соответствует спецификации, так как он не пытается округлить что-либо слева от десятичной точки. Но это проще, чем все остальное, представленное здесь (пока). Я был очень удивлен, что 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);
}
0 голосов
/ 01 октября 2008

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

0 голосов
/ 01 октября 2008

Насколько я помню, «значащие цифры» означают количество цифр после разделителя точек, поэтому 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);
}

С уважением Карстен

...