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

StackOverflow https://stackoverflow.com/questions/202302

Вопрос

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

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

1,239,451 и вернуть 1,240,000

12.1257 и вернуть 12.1

.0681 и возвращаем .0681

5 и верните 5

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

Это было полезно?

Решение

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

Я также удалил повторяющийся код, изменил power к типу integer, чтобы предотвратить проблемы с плавающей запятой, когда 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;
}

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

Вот короткая и приятная реализация 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

Краткие сведения:

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 для выполнения первого.

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

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

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

Мы должны обогнуть (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))

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


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

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

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

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

Итак, используйте ответ Поджигателя!

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

Number(n).toPrecision(sig)

например ,

alert(Number(12345).toPrecision(3)

?

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

JavaScript:

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

.NET

roundit(8.14301,4) == 8.144

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

roundToSignificantFigures(1.234E-310, 3);

тогда переменная power будет иметь значение 3 - (-309) = 312.Следовательно, переменная magnitude станет Infinity, и с тех пор все это мусор.К счастью, это не является непреодолимой проблемой:это всего лишь фактор magnitude это переполняет меня.Что действительно важно, так это продукт 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;
}

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

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

Вот модифицированная версия 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;
 }

Это произошло с опозданием на 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. Преобразуйте число в строку
  2. Начиная с начала строки, считайте цифры - начальные нули не являются значимыми, все остальное имеет значение.
  3. Когда вы дойдете до "n-ой" цифры, посмотрите вперед на следующую цифру и, если она 5 или выше, округлите в большую сторону.
  4. Замените все конечные цифры нулями.

[Исправлено, 2009-10-26]

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

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

Для N значимых интегральный (не дробные) цифры:

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

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

/**
 * Set Significant Digits.
 * @param value value
 * @param digits digits
 * @return
 */
public static BigDecimal setSignificantDigits(BigDecimal value, int digits) {
    //# Start with the leftmost non-zero digit (e.g. the "1" in 1200, or the "2" in 0.0256).
    //# Keep n digits. Replace the rest with zeros.
    //# Round up by one if appropriate.
    int p = value.precision();
    int s = value.scale();
    if (p < digits) {
        value = value.setScale(s + digits - p); //, RoundingMode.HALF_UP
    }
    value = value.movePointRight(s).movePointLeft(p - digits).setScale(0, RoundingMode.HALF_UP)
        .movePointRight(p - digits).movePointLeft(s);
    s = (s > (p - digits)) ? (s - (p - digits)) : 0;
    return value.setScale(s);
}

Вот код 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

JavaScript:

Number( my_number.toPrecision(3) );

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

Это тот, который я придумал в 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

return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();

Мне это было нужно в Go, что было немного осложнено отсутствием в стандартной библиотеке Go math.Round() (перед выходом 1.10).Так что мне пришлось и это придумать на скорую руку.Вот мой перевод Отличный ответ Пиролистика:

// 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
}
public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top