Округление до произвольного числа значащих цифр - PullRequest
78 голосов
/ 14 октября 2008

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

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

1 239 451 и возврат 1 240 000

12,1257 и возврат 12,1

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

5 и возврат 5

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

Ответы [ 17 ]

100 голосов
/ 17 октября 2009

Вот тот же код в Java без ошибки 12.100000000000001, другие ответы имеют

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

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

EDIT
Исправлено больше ошибок. Добавлена ​​проверка на 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;
}
15 голосов
/ 14 октября 2008

Вот короткая и приятная реализация 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
15 голосов
/ 14 октября 2008

РЕЗЮМЕ:

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 возьмите ceil из журнала. Для чисел <0, взять слово журнала. </p>

Теперь у нас есть цифра 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

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

10 голосов
/ 08 апреля 2009

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

Number(n).toPrecision(sig)

, например

alert(Number(12345).toPrecision(3)

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

JavaScript:

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

.NET

roundit(8.14301,4) == 8.144
7 голосов
/ 19 ноября 2010

Пиролистическое (очень хорошее!) Решение все еще имеет проблему. Максимальное двойное значение в 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;
}

6 голосов
/ 10 августа 2010

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

double roundToSignificantFigure(double num, int precision){
 return new BigDecimal(num)
            .round(new MathContext(precision, RoundingMode.HALF_EVEN))
            .doubleValue(); 
}
3 голосов
/ 04 июня 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;
 }
2 голосов
/ 22 октября 2013

Это произошло на 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);
1 голос
/ 05 мая 2015

JavaScript:

Number( my_number.toPrecision(3) );

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

1 голос
/ 21 июня 2011

Вот код Pyrolistics (в настоящее время самый лучший ответ) в 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
...