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

Как можно округлить любое число (не только целые числа> 0) до N значащих цифр?

Например, если я хочу округлить до трех значащих цифр, я ищу формулу, которая могла бы принимать:

1,239,451 и доход 1,240,000

12.1257 и возврат 12.1

.0681 и возврат .0681

5 и возврат 5

Естественно, алгоритм не должен быть жестко запрограммирован так, чтобы обрабатывать только N из 3, хотя это было бы началом.


person DougN    schedule 14.10.2008    source источник
comment
Кажется, вопрос слишком общий. Для этого в разных языках программирования предусмотрены разные стандартные функции. На самом деле неуместно изобретать велосипед.   -  person Johnny Wong    schedule 11.11.2015


Ответы (16)


Вот тот же код на Java без ошибки 12.100000000000001, у других ответов есть

Я также удалил повторяющийся код, изменил power на целое число, чтобы предотвратить проблемы с плавающей точкой, когда n - d выполнен, и сделал длинное промежуточное число более понятным.

Ошибка была вызвана умножением большого числа на малое. Вместо этого я делю два числа одинакового размера.

РЕДАКТИРОВАТЬ
Исправлены другие ошибки. Добавлена ​​проверка на 0, так как это приведет к NaN. Функция действительно работает с отрицательными числами (исходный код не обрабатывает отрицательные числа, потому что лог отрицательного числа является комплексным числом)

public static double roundToSignificantFigures(double num, int n) {
    if(num == 0) {
        return 0;
    }

    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
    final int power = n - (int) d;

    final double magnitude = Math.pow(10, power);
    final long shifted = Math.round(num*magnitude);
    return shifted/magnitude;
}
person Pyrolistical    schedule 17.10.2009
comment
Спасибо, что приняли мой ответ. Я только что понял, что отвечу больше, чем через год после вопроса. Это одна из причин, почему stackoverflow такой классный. Вы можете найти полезную информацию! - person Pyrolistical; 22.05.2010
comment
Обратите внимание, что это может немного не работать для значений, близких к пределу округления. Например, округление 1,255 до 3 значащих цифр должно вернуть 1,26, но вернет 1,25. Это потому, что 1.255 * 100.0 - это 125.499999 ... Но этого следовало ожидать при работе с двойными - person cquezel; 22.11.2014
comment
Вау, я знаю, что это старый, но я пытаюсь его использовать. У меня есть число с плавающей запятой, которое я хочу отобразить до трех значащих цифр. Если значение с плавающей запятой равно 1.0, я вызываю ваш метод, но он по-прежнему возвращает значение 1.0, даже если я приведу значение с плавающей запятой как двойное. Я хочу вернуть его как 1. Есть идеи? - person Steve W; 20.01.2016
comment
Этот фрагмент java попадает в официальный пример Android android.googlesource.com/platform/development / + / fcf4286 / samples / - person Curious Sam; 18.02.2016
comment
Не идеально. Для num = -7999999.999999992 и n = 2 возвращается -7999999.999999999, но должно быть -8000000. - person Duncan Calvert; 24.03.2017
comment
Я тестировал +7999999.999999992, и он дает +8000000 нормально. Я думаю, что отрицательные числа следует рассматривать в особом случае, как и ноль, в целях безопасности. - person Eric Nicolas; 26.05.2017

Вот короткая и приятная реализация JavaScript:

function sigFigs(n, sig) {
    var mult = Math.pow(10, sig - Math.floor(Math.log(n) / Math.LN10) - 1);
    return Math.round(n * mult) / mult;
}

alert(sigFigs(1234567, 3)); // Gives 1230000
alert(sigFigs(0.06805, 3)); // Gives 0.0681
alert(sigFigs(5, 3)); // Gives 5
person Ates Goral    schedule 14.10.2008
comment
Хороший ответ, Атес. Возможно, добавим триггер для возврата 0, если n==0 :) - person sscirrus; 22.10.2011
comment
есть ли причина делать Math.log(n) / Math.LN10, а не Math.log10(n)? - person Lee; 23.10.2015
comment
@Lee developer.mozilla.org/en- США / docs / Web / JavaScript / Reference / Это новая технология, часть стандарта ECMAScript 2015 (ES6). Итак, в основном, проблемы с совместимостью. - person Ates Goral; 23.10.2015
comment
Я что-то упустил, или в этом ответе предполагается, что Math.floor(x) == Math.ceil(x) - 1? Потому что это не так, когда x является целым числом. Я думаю, что второй аргумент функции pow должен быть sig - Math.ceil(Math.log(n) / Math.LN10) (или просто использовать Math.log10) - person Paul; 28.02.2018

РЕЗЮМЕ:

double roundit(double num, double N)
{
    double d = log10(num);
    double power;
    if (num > 0)
    {
        d = ceil(d);
        power = -(d-N);
    }
    else
    {
        d = floor(d); 
        power = -(d-N);
    }

    return (int)(num * pow(10.0, power) + 0.5) * pow(10.0, -power);
}

Итак, вам нужно найти десятичный разряд первой ненулевой цифры, затем сохранить следующие цифры N-1, а затем округлить N-ю цифру на основе остальных.

Мы можем использовать журнал, чтобы сделать первое.

log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681  = -1.16

Итак, для чисел> 0 возьмите верхнюю часть журнала. Для чисел ‹0 возьмите пол бревна.

Теперь у нас есть цифра d: 7 в первом случае, 2 во втором, -2 в третьем.

Нам нужно округлить (d-N)-ю цифру. Что-то типа:

double roundedrest = num * pow(10, -(d-N));

pow(1239451, -4) = 123.9451
pow(12.1257, 1)  = 121.257
pow(0.0681, 4)   = 681

Затем выполните стандартное округление:

roundedrest = (int)(roundedrest + 0.5);

И отменить паузу.

roundednum = pow(roundedrest, -(power))

Где мощность - это мощность, рассчитанная выше.


По поводу точности: ответ пиролитика действительно ближе к реальному результату. Но учтите, что вы не можете точно представить 12.1. Если вы распечатаете ответы следующим образом:

System.out.println(new BigDecimal(n));

Ответы таковы:

Pyro's: 12.0999999999999996447286321199499070644378662109375
Mine: 12.10000000000000142108547152020037174224853515625
Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375

Итак, используйте ответ Пиро!

person Claudiu    schedule 14.10.2008
comment
Этот алгоритм кажется подверженным ошибкам с плавающей запятой. При реализации с помощью JavaScript я получаю: 0,06805 - ›0,06810000000000001 и 12,1 -› 12,100000000000001. - person Ates Goral; 14.10.2008
comment
12.1 сам по себе не может быть точно представлен с использованием чисел с плавающей запятой - это не результат этого алгоритма. - person Claudiu; 15.10.2008
comment
Этот код на Java производит 12.100000000000001, и он использует 64-битные числа с двойной точностью, которые могут точно представить 12.1. - person Pyrolistical; 17.10.2009
comment
Неважно, 64 или 128 бит. Вы не можете представить дробь 1/10, используя конечную сумму степеней двойки, и именно так представлены числа с плавающей запятой. - person Claudiu; 17.10.2009
comment
для тех, кто вмешивается, в основном ответ Pyrolistical более точен, чем мой, поэтому алгоритм печати чисел с плавающей запятой печатает «12 .1» вместо «12 .100000000000001». его ответ лучше, даже несмотря на то, что я был технически прав, что вы не можете точно представить «12,1». - person Claudiu; 06.03.2011

Разве не "короткая и приятная" реализация JavaScript

Number(n).toPrecision(sig)

e.g.

alert(Number(12345).toPrecision(3)

?

Извините, я не шучу здесь, просто использование функции roundit от Claudiu и .toPrecision в JavaScript дает разные результаты, но только в округлении последней цифры.

JavaScript:

Number(8.14301).toPrecision(4) == 8.143

.СЕТЬ

roundit(8.14301,4) == 8.144
person Justin Wignall    schedule 08.04.2009
comment
Number(814301).toPrecision(4) == "8.143e+5". Обычно это не то, что вам нужно, если вы показываете это пользователям. - person Zaz; 05.05.2015
comment
Совершенно верно, Джош, да, я бы обычно рекомендовал .toPrecision () только для десятичных чисел, и принятый ответ (с редактированием) следует использовать / проверять в соответствии с вашими индивидуальными требованиями. - person Justin Wignall; 05.05.2015

Пиролитическое (очень красивое!) Решение все еще имеет проблему. Максимальное значение типа double в Java составляет порядка 10 ^ 308, а минимальное значение - порядка 10 ^ -324. Следовательно, вы можете столкнуться с проблемами при применении функции roundToSignificantFigures к чему-то, что находится в пределах нескольких десятичных степеней от Double.MIN_VALUE. Например, когда вы звоните

roundToSignificantFigures(1.234E-310, 3);

тогда переменная power будет иметь значение 3 - (-309) = 312. Следовательно, переменная magnitude станет Infinity, и с этого момента все это будет мусором. К счастью, это не непреодолимая проблема: это только фактор magnitude, который переполняет. Что действительно важно, так это product num * magnitude, и он не переполняется. Один из способов решить эту проблему - разделить умножение на коэффициент magintude на два этапа:


 public static double roundToNumberOfSignificantDigits(double num, int n) {

    final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE));

    if(num == 0) {
        return 0;
    }

    final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
    final int power = n - (int) d;

    double firstMagnitudeFactor = 1.0;
    double secondMagnitudeFactor = 1.0;
    if (power > maxPowerOfTen) {
        firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen);
        secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen);
    } else {
        firstMagnitudeFactor = Math.pow(10.0, (double) power);
    }

    double toBeRounded = num * firstMagnitudeFactor;
    toBeRounded *= secondMagnitudeFactor;

    final long shifted = Math.round(toBeRounded);
    double rounded = ((double) shifted) / firstMagnitudeFactor;
    rounded /= secondMagnitudeFactor;
    return rounded;
}

person Thomas Becker    schedule 19.11.2010

Как насчет этого java-решения:

double roundToSignificantFigure(double num, int precision){
 return new BigDecimal(num)
            .round(new MathContext(precision, RoundingMode.HALF_EVEN))
            .doubleValue(); 
}
person wolfgang grinfeld    schedule 10.08.2010

Вот модифицированная версия JavaScript Ates, которая обрабатывает отрицательные числа.

function sigFigs(n, sig) {
    if ( n === 0 )
        return 0
    var mult = Math.pow(10,
        sig - Math.floor(Math.log(n < 0 ? -n: n) / Math.LN10) - 1);
    return Math.round(n * mult) / mult;
 }
person Jason Swank    schedule 04.06.2010

JavaScript:

Number( my_number.toPrecision(3) );

Функция Number изменит вывод формы "8.143e+5" на "814300".

person Zaz    schedule 04.05.2015

Это произошло с опозданием на 5 лет, но я поделюсь с другими, у которых все еще есть та же проблема. Мне это нравится, потому что это просто и никаких вычислений на стороне кода. Подробнее см. Встроенные методы для отображения значимых цифр. Информация.

Это если вы просто хотите его распечатать.

public String toSignificantFiguresString(BigDecimal bd, int significantFigures){
    return String.format("%."+significantFigures+"G", bd);
}

Это если вы хотите его преобразовать:

public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){
    String s = String.format("%."+significantFigures+"G", bd);
    BigDecimal result = new BigDecimal(s);
    return result;
}

Вот пример этого в действии:

BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);
person JackDev    schedule 22.10.2013
comment
Это отобразит большие числа в научном представлении, например 15к как 1.5e04. - person matt; 10.03.2019

Вы пробовали просто кодировать это так, как если бы вы это делали вручную?

  1. Преобразуйте число в строку
  2. Начиная с начала строки, считайте цифры - ведущие нули не имеют значения, все остальное имеет значение.
  3. Когда вы дойдете до «n-й» цифры, посмотрите вперед на следующую цифру и, если она 5 или выше, округлите в большую сторону.
  4. Замените все конечные цифры нулями.
person Mark Bessey    schedule 14.10.2008

[Исправлено, 26.10.2009]

По сути, для N значащих дробных цифр:

Умножьте число на 10 N
Добавьте 0,5
Обрежьте цифры дробной части (т.е. усеките результат до целого числа)
Разделите на 10 N

Для N значащих целых (недробных) цифр:

Разделите число на 10 N
Добавьте 0,5
Обрежьте цифры дробной части (т.е. усеките результат до целого числа)
Умножьте на 10 N

Вы можете сделать это на любом калькуляторе, например, в котором есть оператор «INT» (целочисленное усечение).

person David R Tribble    schedule 17.10.2009
comment
Неа. Прочтите вопрос еще раз. 1239451 с 3-мя фигами при использовании вашего алгоритма неверно даст 123951 - person Pyrolistical; 20.10.2009
comment
Да, я исправил это, чтобы различать округление до дробного числа цифр (справа от десятичной точки) и целого числа цифр (слева). - person David R Tribble; 27.10.2009

Вот код Pyrolistical (в настоящее время главный ответ) в Visual Basic.NET, если он кому-то понадобится:

Public Shared Function roundToSignificantDigits(ByVal num As Double, ByVal n As Integer) As Double
    If (num = 0) Then
        Return 0
    End If

    Dim d As Double = Math.Ceiling(Math.Log10(If(num < 0, -num, num)))
    Dim power As Integer = n - CInt(d)
    Dim magnitude As Double = Math.Pow(10, power)
    Dim shifted As Double = Math.Round(num * magnitude)
    Return shifted / magnitude
End Function
person Michael Zlatkovsky - Microsoft    schedule 21.06.2011

Это то, что я придумал в VB:

Function SF(n As Double, SigFigs As Integer)
    Dim l As Integer = n.ToString.Length
    n = n / 10 ^ (l - SigFigs)
    n = Math.Round(n)
    n = n * 10 ^ (l - SigFigs)
    Return n
End Function
person SomeGuy    schedule 01.06.2013

Мне это было нужно в Go, что было немного усложнено отсутствием math.Round() в стандартной библиотеке Go (до go1.10). Так что мне тоже пришлось это поднять. Вот мой перевод отличного ответа Pyrolistical:

// TODO: replace in go1.10 with math.Round()
func round(x float64) float64 {
    return float64(int64(x + 0.5))
}

// SignificantDigits rounds a float64 to digits significant digits.
// Translated from Java at https://stackoverflow.com/a/1581007/1068283
func SignificantDigits(x float64, digits int) float64 {
    if x == 0 {
        return 0
    }

    power := digits - int(math.Ceil(math.Log10(math.Abs(x))))
    magnitude := math.Pow(10, float64(power))
    shifted := round(x * magnitude)
    return shifted / magnitude
}
person Michael Hampton    schedule 28.01.2018
comment
Это получил загадочный голос против! Но я не могу найти в нем ошибку или проблему. Что тут происходит? - person Michael Hampton; 28.08.2018

Вы можете избежать выполнения всех этих вычислений со степенью 10 и т. Д., Просто используя FloatToStrF.

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

Глянь сюда:

[email protected]. //docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/SysUtils_FloatToStrF@Extended@TFloatFormat@[email protected]

person George Soden-Freeth    schedule 12.07.2020

public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

Этот код использует встроенную функцию форматирования, которая превращена в функцию округления.

person Harikrishnan    schedule 19.05.2013