Если у меня есть double (234.004223) и т. д., Я хотел бы округлить это до x значащих цифр в C#.
Пока я могу найти способы округления только до x десятичных знаков, но это просто убирает точность, если в числе есть какие-либо нули.
Например, 0,086 с точностью до одного десятичного знака становится 0,1, но я бы хотел, чтобы оно оставалось на 0,08.
Я не понимаю, что вы здесь имеете в виду. В вашем примере вы пытаетесь округлить до двух знаков после запятой? Или оставьте только одну цифру? Если второе, то оно должно быть 0,09, округляя в большую сторону 6 ...
Или вы ищете N * 10 ^ X, где N имеет указанное количество цифр?
Пожалуйста, дайте нам еще несколько примеров исходных чисел и того, что вы хотите видеть на выходе
Округление до значащих цифр - это не то же самое, что округление до десятичных знаков. 0,3762 до 2 знаков после запятой составляет 0,38, где для 2 значащих цифр / цифр это 0,37 0,0037 до 2 знаков после запятой будет правильно 0,00, но для 2 значащих цифр это 0,0037, потому что 0 не имеют значения
Я не согласен. Округление до значащих цифр не означает, что вы должны автоматически усекать вместо округления. Например, см. en.wikipedia.org/wiki/Significant_figures. «... если округлить 0,039 до 1 значащей цифры, результат будет 0,04».
Однако обратите внимание, что я предоставил оба ниже. 0,039.RoundToSignificantDigits (1) вернет 0,04, а 0,039.TruncateToSignificantDigits (1) вернет 0,03.
Это правильно, я ошибался, думая, что они усечены.
Аналогичный вопрос: stackoverflow.com/questions/304011/… Вы можете просто добавить round () в соответствующее место.





Этот вопрос похож на тот, который вы задаете:
Форматирование чисел со значащими цифрами в 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 - по крайней мере, с некоторыми локализациями.
Мне кажется, что вы вообще не хотите округлять до x десятичных знаков - вы хотите округлить до x значащих цифр. Итак, в вашем примере вы хотите округлить 0,086 до одной значащей цифры, а не до одного десятичного знака.
Теперь использование двойного числа и округление до ряда значащих цифр проблематично для начала из-за способа хранения двойных чисел. Например, вы можете округлить 0,12 до Закрыть до 0,1, но 0,1 нельзя точно представить как двойное. Вы уверены, что вам не следует использовать десятичную дробь? В качестве альтернативы, действительно ли это для отображения? Если это для целей отображения, я подозреваю, что вам действительно нужно преобразовать double непосредственно в строку с соответствующим количеством значащих цифр.
Если вы можете ответить на эти вопросы, я могу попытаться придумать какой-нибудь подходящий код. Как бы ужасно это ни звучало, преобразование числа значащих цифр в строку путем преобразования числа в "полную" строку с последующим нахождением первой значащей цифры (и последующим выполнением соответствующих действий округления) вполне может быть лучшим выходом. .
Это для демонстрации, я вообще не считал Decimal, если честно. Как мне преобразовать в строку с соответствующим количеством значащих цифр, как вы говорите? Мне не удалось найти пример в спецификации метода Double.ToString ().
@Rocco: Я знаю, что опоздал на 4 года, но я только что наткнулся на ваш вопрос. Я думаю, вам следует использовать Double.ToString ("Gn"). См. Мой ответ от 6 ноября 2012 г .:-)
Фреймворк не имеет встроенной функции для округления (или усечения, как в вашем примере) до ряда значащих цифр. Однако один из способов сделать это - масштабировать число так, чтобы первая значащая цифра находилась сразу после десятичной точки, округлить (или усечь), а затем уменьшить масштаб. Следующий код должен помочь:
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 (...) не принимает два параметра
@leftbrainlogic: Да, это действительно так: msdn.microsoft.com/en-us/library/75ks3aby.aspx
Ни один из этих методов не будет работать с отрицательными числами, поскольку Math.Log10 вернет Double.NaN, если d <0.
@Fraser: Хороший улов. Я оставлю читателю в качестве упражнения добавить Math.Abs.
@PDaddy хммм, вам нужно будет проверить, если d == 0, так как это тоже приведет к Double.NaN - оба метода требуют пары защитных предложений, таких как: if (d == 0) {return 0; } если (d <0) {d = Math.Abs (d); } - иначе вы получите деление на 0 в обоих.
@Fraser: Ну вот и упражнение для читателя. Кстати, Эрик заметил (stackoverflow.com/a/1925170/36388) недостаток отрицательных чисел более двух лет назад (хотя и не нулевой). Может быть, мне действительно стоит исправить этот код, чтобы люди перестали мне называть его.
@PDaddy Да, пожалуйста, исправьте. Я бы добавил +1, если бы это было исправлено. Я полагаю, что многие люди ошибочно считают ответы с большим количеством голосов копируемыми и вставляемыми.
Хорошо, отредактировано для обработки чисел ≤ 0. Обратите внимание, однако, что остается одно несоответствие крошечный. Если вы вызываете функцию Round... со значением для digits, выходящим за пределы диапазона [0, 15], Math.Round выдаст исключение аргумента. Если d равен нулю, то Math.Round не вызывается, поэтому нет никаких исключений. На самом деле незначительно. Обратите внимание, что это не относится к функции Truncate....
@PDaddy, ваше решение работает как шарм, но в некоторых случаях оно дает результат с 01 в конце 119.212024802412, 121.198454653809. Подскажите, пожалуйста, почему 01 идет последним вместо 00
@aarn: Я не уверен, что числа, которые вы указали, должны быть входами или выходами, и если они являются входами, вы не указали ввод "цифр", поэтому я не могу сказать со 100% уверенностью, что вы испытываете, но вполне вероятно, что вы видите артефакты преобразования между представлениями с плавающей запятой base-10 и base-2. См. Такие вопросы, как stackoverflow.com/questions/1089018 и stackoverflow.com/questions/18389455.
@PDaddy, извините за недостаточную информацию, цифры являются входными, а цифры - 15
@aarn: Когда я использую ваше первое число (119.212 ...), вывод будет таким же, как и ввод. Когда я использую ваше второе число (121,198 ...), результат на 1,4E-14 выше. Эта проблема заключается в том, что при масштабировании числа мантисса изменяется (поскольку показатель степени равен основанию 2), и некоторая точность теряется с конца. Возможно, масштабирование в другом направлении устранило бы эту потерю точности. Но имейте в виду, что ни ваш ввод, ни ваш результат в точности не равны 121.198454653809. Для получения дополнительной информации см. Другие вопросы, на которые я ранее давал ссылку, или, возможно, откройте новый вопрос.
приведенный выше код, дающий значение 5.8999999999999995E-12 для 5.9E-12 в .Net Core 3.0
@RamakrishnaReddy: См. Такие вопросы, как stackoverflow.com/questions/1089018 и stackoverflow.com/questions/18389455.
Console.WriteLine("{0:G17}", RoundToSignificantDigits(5.015 * 100, 15)) дает мне 501.49999999999994, что неверно. Правильный ответ должен быть 501,5.
@AmrAli: Это потому, что 5.015 не может быть точно представлен в представлении с плавающей запятой IEEE 754. Попробуйте 5.015.ToString("G17"), и вы получите 5.0149999999999997. Это число, которое вы умножаете на 100, а затем округляете до 15 цифр. Пожалуйста, просмотрите ссылки в моем предыдущем комментарии от 22 апреля, чтобы узнать больше.
Пусть вводится 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 сигфигов, есть идеи?
Ошибка с (0,073699979, 7) возвращает 0.073699979999999998
Я только что сделал:
int integer1 = Math.Round(double you want to round,
significant figures you want to round to)
Это дает вам только количество значащих цифр справа от десятичной точки.
Я обнаружил две ошибки в методах Папы Пи и Эрика. Это решает, например, ошибку точности, которую представил Эндрю Хэнкокс в этом вопросе и ответе. Также была проблема с круглыми направлениями. 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.
Я бы сказал, что 1050, округленное до двух значащих цифр, равно 1000. Округление до четного - очень распространенный метод округления.
Вот что я сделал на 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#
Если это для целей отображения (как вы указываете в комментарии к ответу Джона Скита), вы должны использовать 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. Кроме того, он навязывает научную нотацию по своему усмотрению, нравится вам это или нет.
Вероятно, следует согласиться с вами в отказе от значащих нулей в 1.0001. Что касается второго утверждения - использование научной нотации решается на основании того факта, что нотация будет занимать меньше места при печати (это старое правило FORTRAN для формата G). В каком-то смысле это предсказуемо, но если кто-то вообще предпочитает научный формат - им это неприятно.
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}");
Вы хотите, чтобы x было цифр после начальных нулей десятичных знаков. Например, если вы хотите сохранить 2 цифры для следующего числа, 0,00030908 это 0,00031 или вы хотите 0,00030? или что-то другое?