Подтвердить что ты не робот

Круглая цифра в два раза

Если у меня есть двойной (234.004223) и т.д., я хотел бы округлить это до x значащих цифр в С#.

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

Например, 0.086 до одного десятичного знака становится 0,1, но я бы хотел, чтобы он оставался на уровне 0,08.

4b9b3361

Ответ 1

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

static double RoundToSignificantDigits(this double d, int digits){
    if(d == 0)
        return 0;

    double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
    return scale * Math.Round(d / scale, digits);
}

Если, как в вашем примере, вы действительно хотите усечь, то вы хотите:

static double TruncateToSignificantDigits(this double d, int digits){
    if(d == 0)
        return 0;

    double scale = Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1 - digits);
    return scale * Math.Truncate(d / scale);
}

Ответ 2

Я использую функцию pDaddy sigfig в течение нескольких месяцев и обнаружил ошибку. Вы не можете брать журнал отрицательного числа, поэтому, если d отрицательно, результат равен NaN.

Следующее исправляет ошибку:

public static double SetSigFigs(double d, int digits)
{   
    if(d == 0)
        return 0;

    decimal scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);

    return (double) (scale * Math.Round((decimal)d / scale, digits));
}

Ответ 3

Мне кажется, что вы не хотите округлять до десяти знаков после запятой - вы хотите округлить до x значащих цифр. Поэтому в вашем примере вы хотите округлить 0.086 до одной значащей цифры, а не десятичного разряда.

Теперь использование двойного и округления до нескольких значащих цифр проблематично для начала, из-за сохранения двоичных файлов. Например, вы можете округлить 0.12 до значения, близкого к 0.1, но 0.1 не является точно представимым как double. Вы действительно не должны использовать десятичную дробь? В качестве альтернативы, это действительно для показа? Если это для отображения, я подозреваю, что вы должны фактически преобразовать double непосредственно в строку с соответствующим количеством значимых цифр.

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

Ответ 4

Если это предназначено для отображения (как вы указываете в комментарии к ответу Jon Skeet), вы должны использовать Gn спецификатор формата. Где n - количество значащих цифр - именно то, что вам нужно.

Вот пример использования, если вы хотите, чтобы 3 значащих цифры (печатный вывод в комментарии каждой строки):

    Console.WriteLine(1.2345e-10.ToString("G3"));//1.23E-10
    Console.WriteLine(1.2345e-5.ToString("G3")); //1.23E-05
    Console.WriteLine(1.2345e-4.ToString("G3")); //0.000123
    Console.WriteLine(1.2345e-3.ToString("G3")); //0.00123
    Console.WriteLine(1.2345e-2.ToString("G3")); //0.0123
    Console.WriteLine(1.2345e-1.ToString("G3")); //0.123
    Console.WriteLine(1.2345e2.ToString("G3"));  //123
    Console.WriteLine(1.2345e3.ToString("G3"));  //1.23E+03
    Console.WriteLine(1.2345e4.ToString("G3"));  //1.23E+04
    Console.WriteLine(1.2345e5.ToString("G3"));  //1.23E+05
    Console.WriteLine(1.2345e10.ToString("G3")); //1.23E+10

Ответ 5

Я нашел две ошибки в методах P Daddy и Eric. Это решает, например, ошибку точности, представленную Эндрю Хэнкоксом в этом Q & A. Также была проблема с круглыми направлениями. 1050 с двумя значимыми цифрами не 1000,0, это 1100,0. Округление было зафиксировано с помощью MidpointRounding.AwayFromZero.

static void Main(string[] args) {
  double x = RoundToSignificantDigits(1050, 2); // Old = 1000.0, New = 1100.0
  double y = RoundToSignificantDigits(5084611353.0, 4); // Old = 5084999999.999999, New = 5085000000.0
  double z = RoundToSignificantDigits(50.846, 4); // Old = 50.849999999999994, New =  50.85
}

static double RoundToSignificantDigits(double d, int digits) {
  if (d == 0.0) {
    return 0.0;
  }
  else {
    double leftSideNumbers = Math.Floor(Math.Log10(Math.Abs(d))) + 1;
    double scale = Math.Pow(10, leftSideNumbers);
    double result = scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero);

    // Clean possible precision error.
    if ((int)leftSideNumbers >= digits) {
      return Math.Round(result, 0, MidpointRounding.AwayFromZero);
    }
    else {
      return Math.Round(result, digits - (int)leftSideNumbers, MidpointRounding.AwayFromZero);
    }
  }
}

Ответ 6

Как говорит Джон Скит: лучше справиться с этим в текстовой области. Как правило: для целей показайте, не пытайтесь округлить/изменить свои значения с плавающей запятой, он никогда не работает на 100%. Дисплей является второстепенной задачей, и вы должны обрабатывать любые специальные требования к форматированию, такие как работа со строками.

Мое решение ниже я реализовал несколько лет назад и зарекомендовал себя очень надежно. Он был тщательно протестирован и хорошо работает. Примерно в 5 раз больше времени выполнения, чем решение P Daddy/Eric.

Примеры ввода + вывода, приведенные ниже в коде.

using System;
using System.Text;

namespace KZ.SigDig
{
    public static class SignificantDigits
    {
        public static string DecimalSeparator;

        static SignificantDigits()
        {
            System.Globalization.CultureInfo ci = System.Threading.Thread.CurrentThread.CurrentCulture;
            DecimalSeparator = ci.NumberFormat.NumberDecimalSeparator;
        }

        /// <summary>
        /// Format a double to a given number of significant digits.
        /// </summary>
        /// <example>
        /// 0.086 -> "0.09" (digits = 1)
        /// 0.00030908 -> "0.00031" (digits = 2)
        /// 1239451.0 -> "1240000" (digits = 3)
        /// 5084611353.0 -> "5085000000" (digits = 4)
        /// 0.00000000000000000846113537656557 -> "0.00000000000000000846114" (digits = 6)
        /// 50.8437 -> "50.84" (digits = 4)
        /// 50.846 -> "50.85" (digits = 4)
        /// 990.0 -> "1000" (digits = 1)
        /// -5488.0 -> "-5000" (digits = 1)
        /// -990.0 -> "-1000" (digits = 1)
        /// 0.0000789 -> "0.000079" (digits = 2)
        /// </example>
        public static string Format(double number, int digits, bool showTrailingZeros = true, bool alwaysShowDecimalSeparator = false)
        {
            if (Double.IsNaN(number) ||
                Double.IsInfinity(number))
            {
                return number.ToString();
            }

            string sSign = "";
            string sBefore = "0"; // Before the decimal separator
            string sAfter = ""; // After the decimal separator

            if (number != 0d)
            {
                if (digits < 1)
                {
                    throw new ArgumentException("The digits parameter must be greater than zero.");
                }

                if (number < 0d)
                {
                    sSign = "-";
                    number = Math.Abs(number);
                }

                // Use scientific formatting as an intermediate step
                string sFormatString = "{0:" + new String('#', digits) + "E0}";
                string sScientific = String.Format(sFormatString, number);

                string sSignificand = sScientific.Substring(0, digits);
                int exponent = Int32.Parse(sScientific.Substring(digits + 1));
                // (the significand now already contains the requested number of digits with no decimal separator in it)

                StringBuilder sFractionalBreakup = new StringBuilder(sSignificand);

                if (!showTrailingZeros)
                {
                    while (sFractionalBreakup[sFractionalBreakup.Length - 1] == '0')
                    {
                        sFractionalBreakup.Length--;
                        exponent++;
                    }
                }

                // Place decimal separator (insert zeros if necessary)

                int separatorPosition = 0;

                if ((sFractionalBreakup.Length + exponent) < 1)
                {
                    sFractionalBreakup.Insert(0, "0", 1 - sFractionalBreakup.Length - exponent);
                    separatorPosition = 1;
                }
                else if (exponent > 0)
                {
                    sFractionalBreakup.Append('0', exponent);
                    separatorPosition = sFractionalBreakup.Length;
                }
                else
                {
                    separatorPosition = sFractionalBreakup.Length + exponent;
                }

                sBefore = sFractionalBreakup.ToString();

                if (separatorPosition < sBefore.Length)
                {
                    sAfter = sBefore.Substring(separatorPosition);
                    sBefore = sBefore.Remove(separatorPosition);
                }
            }

            string sReturnValue = sSign + sBefore;

            if (sAfter == "")
            {
                if (alwaysShowDecimalSeparator)
                {
                    sReturnValue += DecimalSeparator + "0";
                }
            }
            else
            {
                sReturnValue += DecimalSeparator + sAfter;
            }

            return sReturnValue;
        }
    }
}

Ответ 7

Ошибка Math.Round() в двойниках (см. примечания к вызывающим абонентам в документации). Более поздний шаг умножения округленного числа на его десятичный показатель приведет к дополнительным ошибкам с плавающей запятой в конечных цифрах. Использование другого Round(), поскольку @Rowanto не будет надежно помогать и страдает от других проблем. Однако, если вы готовы перейти через десятичный код, то Math.Round() является надежным, так как он умножается и делит на степени 10:

static ClassName()
{
    powersOf10 = new decimal[28 + 1 + 28];
    powersOf10[28] = 1;
    decimal pup = 1, pdown = 1;
    for (int i = 1; i < 29; i++) {
        pup *= 10;
        powersOf10[i + 28] = pup;
        pdown /= 10;
        powersOf10[28 - i] = pdown;
    }
}

/// <summary>Powers of 10 indexed by power+28.  These are all the powers
/// of 10 that can be represented using decimal.</summary>
static decimal[] powersOf10;

static double RoundToSignificantDigits(double v, int digits)
{
    if (v == 0.0 || Double.IsNaN(v) || Double.IsInfinity(v)) {
        return v;
    } else {
        int decimal_exponent = (int)Math.Floor(Math.Log10(Math.Abs(v))) + 1;
        if (decimal_exponent < -28 + digits || decimal_exponent > 28 - digits) {
            // Decimals won't help outside their range of representation.
            // Insert flawed Double solutions here if you like.
            return v;
        } else {
            decimal d = (decimal)v;
            decimal scale = powersOf10[decimal_exponent + 28];
            return (double)(scale * Math.Round(d / scale, digits, MidpointRounding.AwayFromZero));
        }
    }
}

Ответ 8

Этот вопрос похож на тот, который вы задаете:

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

Таким образом, вы можете сделать следующее:

double Input2 = 234.004223;
string Result2 = Math.Floor(Input2) + Convert.ToDouble(String.Format("{0:G1}", Input2 - Math.Floor(Input2))).ToString("R6");

Закруглена до 1 значащей цифры.

Ответ 9

Введите inputNumber, который необходимо преобразовать с помощью significantDigitsRequired после десятичной точки, затем significantDigitsResult является ответом на следующий псевдокод.

integerPortion = Math.truncate(**inputNumber**)

decimalPortion = myNumber-IntegerPortion

if( decimalPortion <> 0 )
{

 significantDigitsStartFrom = Math.Ceil(-log10(decimalPortion))

 scaleRequiredForTruncation= Math.Pow(10,significantDigitsStartFrom-1+**significantDigitsRequired**)

**siginficantDigitsResult** = integerPortion + ( Math.Truncate (decimalPortion*scaleRequiredForTruncation))/scaleRequiredForTruncation

}
else
{

  **siginficantDigitsResult** = integerPortion

}

Ответ 10

Вот что я сделал в С++

/*
    I had this same problem I was writing a design sheet and
    the standard values were rounded. So not to give my
    values an advantage in a later comparison I need the
    number rounded, so I wrote this bit of code.

    It will round any double to a given number of significant
    figures. But I have a limited range written into the
    subroutine. This is to save time as my numbers were not
    very large or very small. But you can easily change that
    to the full double range, but it will take more time.

    Ross Mckinstray
    [email protected]
*/

#include <iostream>
#include <fstream>
#include <string>
#include <math.h>
#include <cmath>
#include <iomanip>

#using namespace std;

double round_off(double input, int places) {
    double roundA;
    double range = pow(10, 10); // This limits the range of the rounder to 10/10^10 - 10*10^10 if you want more change range;
    for (double j = 10/range; j< 10*range;) {
        if (input >= j && input < j*10){
            double figures = pow(10, places)/10;
            roundA = roundf(input/(j/figures))*(j/figures);
        }
        j = j*10;
    }
    cout << "\n in sub after loop";
    if (input <= 10/(10*10) && input >= 10*10) {
        roundA = input;
        cout << "\nDID NOT ROUND change range";
    }
    return roundA;
}

int main() {
    double number, sig_fig;

    do {
        cout << "\nEnter number ";
        cin >> number;
        cout << "\nEnter sig_fig ";
        cin >> sig_fig;
        double output = round_off(number, sig_fig);

        cout << setprecision(10);
        cout << "\n I= " << number;
        cout << "\n r= " <<output;
        cout << "\nEnter 0 as number to exit loop";
    }
    while (number != 0);

    return 0;
}

Надеюсь, я ничего не изменил, форматируя его.

Ответ 11

Я только что сделал:

int integer1 = Math.Round(double you want to round, 
    significant figures you want to round to)