Округлить двойное до x значащих цифр

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

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

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

Вы хотите, чтобы x было цифр после начальных нулей десятичных знаков. Например, если вы хотите сохранить 2 цифры для следующего числа, 0,00030908 это 0,00031 или вы хотите 0,00030? или что-то другое?

lakshmanaraj 17.12.2008 14:58

Я не понимаю, что вы здесь имеете в виду. В вашем примере вы пытаетесь округлить до двух знаков после запятой? Или оставьте только одну цифру? Если второе, то оно должно быть 0,09, округляя в большую сторону 6 ...

The Archetypal Paul 17.12.2008 14:59

Или вы ищете N * 10 ^ X, где N имеет указанное количество цифр?

The Archetypal Paul 17.12.2008 14:59

Пожалуйста, дайте нам еще несколько примеров исходных чисел и того, что вы хотите видеть на выходе

The Archetypal Paul 17.12.2008 15:04

Округление до значащих цифр - это не то же самое, что округление до десятичных знаков. 0,3762 до 2 знаков после запятой составляет 0,38, где для 2 значащих цифр / цифр это 0,37 0,0037 до 2 знаков после запятой будет правильно 0,00, но для 2 значащих цифр это 0,0037, потому что 0 не имеют значения

Rocco 17.12.2008 17:35

Я не согласен. Округление до значащих цифр не означает, что вы должны автоматически усекать вместо округления. Например, см. en.wikipedia.org/wiki/Significant_figures. «... если округлить 0,039 до 1 значащей цифры, результат будет 0,04».

P Daddy 17.12.2008 23:22

Однако обратите внимание, что я предоставил оба ниже. 0,039.RoundToSignificantDigits (1) вернет 0,04, а 0,039.TruncateToSignificantDigits (1) вернет 0,03.

P Daddy 17.12.2008 23:27

Это правильно, я ошибался, думая, что они усечены.

Rocco 18.12.2008 13:12

Аналогичный вопрос: stackoverflow.com/questions/304011/… Вы можете просто добавить round () в соответствующее место.

strager 19.12.2008 07:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
73
9
64 795
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

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

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

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

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

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

Это возвращает 2340,0004 - по крайней мере, с некоторыми локализациями.

Gustav 27.07.2015 19:41

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

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

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

Это для демонстрации, я вообще не считал Decimal, если честно. Как мне преобразовать в строку с соответствующим количеством значащих цифр, как вы говорите? Мне не удалось найти пример в спецификации метода Double.ToString ().

Rocco 17.12.2008 16:40

@Rocco: Я знаю, что опоздал на 4 года, но я только что наткнулся на ваш вопрос. Я думаю, вам следует использовать Double.ToString ("Gn"). См. Мой ответ от 6 ноября 2012 г .:-)

farfareast 07.11.2012 03:07
Ответ принят как подходящий

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

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

Math.round (...) не принимает два параметра

user63904 28.11.2009 15:54

@leftbrainlogic: Да, это действительно так: msdn.microsoft.com/en-us/library/75ks3aby.aspx

P Daddy 01.12.2009 21:03

Ни один из этих методов не будет работать с отрицательными числами, поскольку Math.Log10 вернет Double.NaN, если d <0.

Fraser 26.02.2012 12:13

@Fraser: Хороший улов. Я оставлю читателю в качестве упражнения добавить Math.Abs.

P Daddy 26.02.2012 21:21

@PDaddy хммм, вам нужно будет проверить, если d == 0, так как это тоже приведет к Double.NaN - оба метода требуют пары защитных предложений, таких как: if (d == 0) {return 0; } если (d <0) {d = Math.Abs ​​(d); } - иначе вы получите деление на 0 в обоих.

Fraser 28.02.2012 17:44

@Fraser: Ну вот и упражнение для читателя. Кстати, Эрик заметил (stackoverflow.com/a/1925170/36388) недостаток отрицательных чисел более двух лет назад (хотя и не нулевой). Может быть, мне действительно стоит исправить этот код, чтобы люди перестали мне называть его.

P Daddy 29.02.2012 04:40

@PDaddy Да, пожалуйста, исправьте. Я бы добавил +1, если бы это было исправлено. Я полагаю, что многие люди ошибочно считают ответы с большим количеством голосов копируемыми и вставляемыми.

Evgeniy Berezovsky 27.04.2013 01:59

Хорошо, отредактировано для обработки чисел ≤ 0. Обратите внимание, однако, что остается одно несоответствие крошечный. Если вы вызываете функцию Round... со значением для digits, выходящим за пределы диапазона [0, 15], Math.Round выдаст исключение аргумента. Если d равен нулю, то Math.Round не вызывается, поэтому нет никаких исключений. На самом деле незначительно. Обратите внимание, что это не относится к функции Truncate....

P Daddy 03.05.2013 16:23

@PDaddy, ваше решение работает как шарм, но в некоторых случаях оно дает результат с 01 в конце 119.212024802412, 121.198454653809. Подскажите, пожалуйста, почему 01 идет последним вместо 00

Akshay 07.03.2014 17:37

@aarn: Я не уверен, что числа, которые вы указали, должны быть входами или выходами, и если они являются входами, вы не указали ввод "цифр", поэтому я не могу сказать со 100% уверенностью, что вы испытываете, но вполне вероятно, что вы видите артефакты преобразования между представлениями с плавающей запятой base-10 и base-2. См. Такие вопросы, как stackoverflow.com/questions/1089018 и stackoverflow.com/questions/18389455.

P Daddy 08.03.2014 00:57

@PDaddy, извините за недостаточную информацию, цифры являются входными, а цифры - 15

Akshay 08.03.2014 10:55

@aarn: Когда я использую ваше первое число (119.212 ...), вывод будет таким же, как и ввод. Когда я использую ваше второе число (121,198 ...), результат на 1,4E-14 выше. Эта проблема заключается в том, что при масштабировании числа мантисса изменяется (поскольку показатель степени равен основанию 2), и некоторая точность теряется с конца. Возможно, масштабирование в другом направлении устранило бы эту потерю точности. Но имейте в виду, что ни ваш ввод, ни ваш результат в точности не равны 121.198454653809. Для получения дополнительной информации см. Другие вопросы, на которые я ранее давал ссылку, или, возможно, откройте новый вопрос.

P Daddy 10.03.2014 04:49

приведенный выше код, дающий значение 5.8999999999999995E-12 для 5.9E-12 в .Net Core 3.0

Ramakrishna Reddy 22.04.2020 10:52

@RamakrishnaReddy: См. Такие вопросы, как stackoverflow.com/questions/1089018 и stackoverflow.com/questions/18389455.

P Daddy 22.04.2020 19:33
Console.WriteLine("{0:G17}", RoundToSignificantDigits(5.015 * 100, 15)) дает мне 501.49999999999994, что неверно. Правильный ответ должен быть 501,5.
Amr Ali 17.09.2020 17:49

@AmrAli: Это потому, что 5.015 не может быть точно представлен в представлении с плавающей запятой IEEE 754. Попробуйте 5.015.ToString("G17"), и вы получите 5.0149999999999997. Это число, которое вы умножаете на 100, а затем округляете до 15 цифр. Пожалуйста, просмотрите ссылки в моем предыдущем комментарии от 22 апреля, чтобы узнать больше.

P Daddy 17.09.2020 19:18

Пусть вводится 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

}

Я уже несколько месяцев использую функцию 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));
}

По какой-то причине этот код не может точно преобразовать 50,846113537656557 в 6 сигфигов, есть идеи?

Andrew Hancox 14.01.2010 17:17

Ошибка с (0,073699979, 7) возвращает 0.073699979999999998

LearningJrDev 10.12.2016 01:45

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

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

Это дает вам только количество значащих цифр справа от десятичной точки.

Robert Harvey 23.07.2014 02:06

Я обнаружил две ошибки в методах Папы Пи и Эрика. Это решает, например, ошибку точности, которую представил Эндрю Хэнкокс в этом вопросе и ответе. Также была проблема с круглыми направлениями. 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);
    }
  }
}

Ошибка для RoundToSignificantDigits (.00000000000000000846113537656557, 6), потому что Math.Round не позволяет второму параметру превышать 15.

Oliver Bock 14.12.2012 06:48

Я бы сказал, что 1050, округленное до двух значащих цифр, равно 1000. Округление до четного - очень распространенный метод округления.

Derrick Moeller 15.12.2017 17:14

Вот что я сделал на C++

/*
    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;
}

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

Вопрос помечен как C#

Sнаđошƒаӽ 02.02.2017 09:32

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

Вот пример использования, если вам нужны 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

Хотя это и близко, это не всегда возвращает сигфиги ... например, G4 удалит нули из 1.000 -> 1. Кроме того, он навязывает научную нотацию по своему усмотрению, нравится вам это или нет.

u8it 29.07.2016 23:58

Вероятно, следует согласиться с вами в отказе от значащих нулей в 1.0001. Что касается второго утверждения - использование научной нотации решается на основании того факта, что нотация будет занимать меньше места при печати (это старое правило FORTRAN для формата G). В каком-то смысле это предсказуемо, но если кто-то вообще предпочитает научный формат - им это неприятно.

farfareast 17.08.2016 01:06

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

Как упоминает Джон Скит: лучше справиться с этим в текстовой области. Как правило: для целей отображения не пытайтесь округлять / изменять значения с плавающей запятой, это никогда не работает на 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;
        }
    }
}

Я согласен с духом Оценка Джона:

Awful as it sounds, converting to a number of significant digits as a string by converting the number to a "full" string and then finding the first significant digit (and then taking appropriate rounding action after that) may well be the best way to go.

Мне нужно было округление значащих цифр для вычислительных целей приблизительный и не критичный к производительности, и круговой обход формата-синтаксического анализа через формат "G" достаточно хорош:

public static double RoundToSignificantDigits(this double value, int numberOfSignificantDigits)
{
    return double.Parse(value.ToString("G" + numberOfSignificantDigits));
}

для меня это работает довольно хорошо, а также справедливо для отрицательных чисел:

public static double RoundToSignificantDigits(double number, int digits)
{
    int sign = Math.Sign(number);

    if (sign < 0)
        number *= -1;

    if (number == 0)
        return 0;

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

Как указывает @Oliver Bock, Math.Round () для удвоений ошибочен (см. Примечания для вызывающих в его документация). Более поздний шаг умножения округленного числа на его десятичную экспоненту приведет к дополнительным ошибкам с плавающей запятой в конечных цифрах. Как правило, любое умножение или деление на степень десяти дает неточный результат, поскольку числа с плавающей запятой обычно представлены в двоичном, а не в десятичном виде.

Использование следующей функции позволит избежать ошибок с плавающей запятой в конечных цифрах:

static double RoundToSignificantDigits(double d, int digits)
{
    if (d == 0.0 || Double.IsNaN(d) || Double.IsInfinity(d))
    {
        return d;
    }
    // Compute shift of the decimal point.
    int shift = digits - 1 - (int)Math.Floor(Math.Log10(Math.Abs(d)));

    // Return if rounding to the same or higher precision.
    int decimalPlaces = 0;
    for (long pow = 1; Math.Floor(d * pow) != (d * pow); pow *= 10) decimalPlaces++;
    if (shift >= decimalPlaces)
        return d;

    // Round to sf-1 fractional digits of normalized mantissa x.dddd
    double scale = Math.Pow(10, Math.Abs(shift));
    return shift > 0 ?
           Math.Round(d * scale, MidpointRounding.AwayFromZero) / scale :
           Math.Round(d / scale, MidpointRounding.AwayFromZero) * scale;
}

Однако, если вы хотите использовать десятичную дробь, то Math.Round () надежен, как и умножение и деление на 10:

static double RoundToSignificantDigits(double d, int digits)
{
    if (d == 0.0 || Double.IsNaN(d) || Double.IsInfinity(d))
    {
        return d;
    }
    decimal scale = (decimal)Math.Pow(10, Math.Floor(Math.Log10(Math.Abs(d))) + 1);
    return (double)(scale * Math.Round((decimal)d / scale, digits, MidpointRounding.AwayFromZero));
}

Console.WriteLine("{0:G17}", RoundToSignificantDigits(5.015 * 100, 15)); // 501.5

Мое решение может быть полезным в некоторых случаях, я использую его для отображения цен на криптовалюту, которые сильно различаются по величине - оно всегда дает мне указанное количество значащих цифр, но в отличие от ToString ("G [количество цифр]") оно не показывает небольшие значения в научной нотации (не знаю, как избежать этого с помощью ToString (), если есть, дайте мне знать!)

    const int MIN_SIG_FIGS = 6; //will be one more for < 0
    int numZeros = (int)Math.Floor(Math.Log10(Math.Abs(price))); //get number of zeros before first digit, will be negative for price > 0
    int decPlaces = numZeros < MIN_SIG_FIGS
                  ? MIN_SIG_FIGS - numZeros < 0 
                        ? 0 
                        : MIN_SIG_FIGS - numZeros 
                  : 0; //dec. places: set to MIN_SIG_FIGS + number of zeros, unless numZeros greater than sig figs then no decimal places
    return price.ToString($"F{decPlaces}");

Другие вопросы по теме