Pergunta

Como você pode arredondar algum Número (não apenas números inteiros> 0) para n dígitos significativos?

Por exemplo, se eu quiser arredondar para três dígitos significativos, estou procurando uma fórmula que possa levar:

1.239.451 e retornar 1.240.000

12.1257 e retornar 12.1

.0681 e retornar .0681

5 e retornar 5

Naturalmente, o algoritmo não deve ser codificado para lidar apenas com n de 3, embora isso seja um começo.

Foi útil?

Solução

Aqui está o mesmo código em Java sem o 12.100000000000001 Bug, outras respostas têm

Eu também removi o código repetido, alterado power para um número inteiro para evitar problemas flutuantes quando n - d é feito e deixou o longo intermediário mais claro

O bug foi causado pela multiplicação de um número grande com um pequeno número. Em vez disso, divido dois números de tamanho semelhante.

EDITAR
Corrigido mais bugs. Adicionado verificação para 0, pois resultaria em NAN. Fez a função realmente funcionar com números negativos (o código original não lida com números negativos porque um log de um número negativo é um número complexo)

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

Outras dicas

Aqui está uma implementação curta e doce de 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

RESUMO:

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

Portanto, você precisa encontrar o local decimal do primeiro dígito diferente de zero e, em seguida, salve os próximos dígitos N-1 e depois arredondar o enésimo dígito com base no restante.

Podemos usar o log para fazer o primeiro.

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

Portanto, para números> 0, pegue o teto do log. Para números <0, pegue o piso do tronco.

Agora temos o dígito d: 7 No primeiro caso, 2 no 2º, -2 no 3º.

Temos que arredondar o (d-N)th dígito. Algo como:

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

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

Então faça a coisa de arredondamento padrão:

roundedrest = (int)(roundedrest + 0.5);

E desfazer o prisioneiro de guerra.

roundednum = pow(roundedrest, -(power))

Onde a potência é a energia calculada acima.


Sobre a precisão: a resposta do pirrólico está realmente mais próxima do resultado real. Mas observe que você não pode representar 12.1 exatamente em qualquer caso. Se você imprimir as respostas da seguinte forma:

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

As respostas são:

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

Então, use a resposta de Pyro!

Não é a implementação de JavaScript "curta e doce"

Number(n).toPrecision(sig)

por exemplo

alert(Number(12345).toPrecision(3)

?

Desculpe, não estou sendo faceto aqui, é apenas que o uso da função "Roundit" de Claudiu e a Toprecisão no JavaScript me dê resultados diferentes, mas apenas no arredondamento do último dígito.

JavaScript:

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

.INTERNET

roundit(8.14301,4) == 8.144

A solução pirólica (muito boa!) Ainda tem um problema. O valor duplo máximo em Java é da ordem de 10^308, enquanto o valor mínimo está da ordem de 10^-324. Portanto, você pode ter problemas ao aplicar a função roundToSignificantFigures para algo que está dentro de alguns poderes de dez de Double.MIN_VALUE. Por exemplo, quando você liga

roundToSignificantFigures(1.234E-310, 3);

então a variável power terá o valor 3 - (-309) = 312. Consequentemente, a variável magnitude se tornará Infinity, e é tudo lixo a partir de então. Felizmente, este não é um problema intransponível: é apenas o fator magnitude Isso está transbordando. O que realmente importa é o produtos num * magnitude, e isso não transborda. Uma maneira de resolver isso é interromper a multiplicação pelo fator magintude em duas etapas:


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

Que tal esta solução Java:

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

Aqui está uma versão modificada do JavaScript da ATES que lida com números negativos.

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

Isso chegou 5 anos atrasado, mas embora eu compartilhe para que outras pessoas ainda tenham o mesmo problema. Eu gosto porque é simples e nenhum cálculo no lado do código. Ver Métodos incorporados para exibir números significativos para mais informações.

Isso é se você quiser apenas imprimi -lo.

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

Isso é se você quiser convertê -lo:

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

Aqui está um exemplo disso em ação:

BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);

Você já tentou codificá -lo da maneira que faria manualmente?

  1. Converter o número em uma string
  2. A partir do início da corda, os dígitos da contagem - os zeros principais não são significativos, tudo o mais é.
  3. Quando você chegar ao dígito "Nth", espreite à frente no próximo dígito e, se for 5 ou mais, arredonde.
  4. Substitua todos os dígitos à direita por zeros.

Corrigido, 2009-10-26

Essencialmente, para n significativo fracionário dígitos:

• Multiplique o número por 10N
• Adicione 0,5
• truncar os dígitos da fração (ou seja, truncar o resultado em um número inteiro)
• Divida por 10N

Para n significativo integrante dígitos (não fracionários):

• Divida o número por 10N
• Adicione 0,5
• truncar os dígitos da fração (ou seja, truncar o resultado em um número inteiro)
• Multiplique por 10N

Você pode fazer isso em qualquer calculadora, por exemplo, que possui um operador "int" (truncamento inteiro).

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

Aqui está o código do pirólico (atualmente a melhor resposta) no Visual Basic.net, caso alguém precise:

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

o Number A função mudará a saída do formulário "8.143e+5" para "814300".

Este é um que eu criei em 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();

Eu precisava disso em Go, o que foi um pouco complicado pela falta de biblioteca padrão da biblioteca math.Round() (antes de GO1.10). Então eu tive que preparar isso também. Aqui está minha tradução de Excelente resposta do pirólico:

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

Este código usa a função de formatação embutida que é transformada em uma função de arredondamento

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top