Pregunta

¿Cómo puede redondear cualquier número (no solo enteros > 0) a N dígitos significativos?

Por ejemplo, si quiero redondear a tres dígitos significativos, estoy buscando una fórmula que pueda tomar:

1,239,451 y devuelve 1,240,000

12.1257 y devuelve 12.1

.0681 y devolver .0681

5 y devolver 5

Naturalmente, el algoritmo no debe estar codificado para manejar solo N de 3, aunque eso sería un comienzo.

¿Fue útil?

Solución

Aquí está el mismo código en Java sin el error 12.100000000000001 que tienen otras respuestas

También eliminé el código repetido, cambié power a un tipo entero para evitar problemas de flotación cuando se realizó n - d, y dejé claro el intermedio largo

El error fue causado al multiplicar un número grande con un número pequeño. En cambio, divido dos números de tamaño similar.

EDITAR
Se corrigieron más errores. Comprobación agregada para 0, ya que daría como resultado NaN. Hizo que la función realmente funcionara con números negativos (el código original no maneja números negativos porque un registro de un número negativo es un número complejo)

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

Otros consejos

Aquí hay una implementación breve y dulce 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

RESUMEN:

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

Por lo tanto, debe encontrar el lugar decimal del primer dígito distinto de cero, luego guardar los siguientes dígitos N-1 y luego redondear el enésimo dígito en función del resto.

Podemos usar log para hacer lo primero.

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

Entonces, para los números > 0, toma el techo del tronco. Para los números & Lt; 0, toma el piso del registro.

Ahora tenemos el dígito d: 7 en el primer caso, 2 en el segundo, -2 en el tercero.

Tenemos que redondear el (d-N) th dígito. Algo así como:

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

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

Luego haz el redondeo estándar:

roundedrest = (int)(roundedrest + 0.5);

Y deshacer el pow.

roundednum = pow(roundedrest, -(power))

Donde el poder es el poder calculado arriba.


Acerca de la precisión: la respuesta de Pyrolistical está más cerca del resultado real. Pero tenga en cuenta que no puede representar 12.1 exactamente en ningún caso. Si imprime las respuestas de la siguiente manera:

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

Las respuestas son:

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

¡Entonces, usa la respuesta de Pyro!

¿No es " corto y dulce " Implementación de JavaScript

Number(n).toPrecision(sig)

por ejemplo

alert(Number(12345).toPrecision(3)

?

Lo siento, no estoy siendo gracioso aquí, es solo que usando " roundit " La función de Claudiu y la .toPrecision en JavaScript me dan resultados diferentes, pero solo en el redondeo del último dígito.

JavaScript:

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

.NET

roundit(8.14301,4) == 8.144

La solución de Pyrolistical (¡muy agradable!) todavía tiene un problema. El valor doble máximo en Java está en el orden de 10 ^ 308, mientras que el valor mínimo está en el orden de 10 ^ -324. Por lo tanto, puede tener problemas al aplicar la función roundToSignificantFigures a algo que esté dentro de unas pocas potencias de diez de Double.MIN_VALUE. Por ejemplo, cuando llamas

roundToSignificantFigures(1.234E-310, 3);

entonces la variable power tendrá el valor 3 - (-309) = 312. En consecuencia, la variable magnitude se convertirá en Infinity, y todo será basura de ahí en adelante. Afortunadamente, este no es un problema insuperable: es solo el factor num * magnitude lo que se desborda. Lo que realmente importa es el producto magintude, y eso no se desborda. Una forma de resolver esto es dividiendo la multiplicación por el factor <=> en dos pasos:


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

¿Qué tal esta solución de Java?

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

Aquí hay una versión modificada del JavaScript de Ates que maneja 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;
 }

Esto llegó 5 años tarde, pero lo compartiré para otros que todavía tienen el mismo problema. Me gusta porque es simple y no hay cálculos en el lado del código. Consulte Métodos incorporados para mostrar cifras significativas para obtener más información info.

Esto es si solo quieres imprimirlo.

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

Esto es si quieres convertirlo:

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

Aquí hay un ejemplo de ello en acción:

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

¿Has intentado codificarlo de la forma en que lo harías a mano?

  1. Convertir el número a una cadena
  2. Comenzando al comienzo de la cadena, contar dígitos: los ceros iniciales no son significativo, todo lo demás es.
  3. Cuando llegas a la " nth " dígito, echar un vistazo al siguiente dígito y si es 5 o más, redondear hacia arriba.
  4. Reemplace todos los dígitos finales con ceros.

[Corregido, 26-10-2009]

Esencialmente, para N dígitos fraccionales significativos:

& # 8226; Multiplique el número por 10 N
& # 8226; Agregar 0.5
& # 8226; Truncar los dígitos de la fracción (es decir, truncar el resultado en un entero)
& # 8226; Dividir por 10 N

Para N dígitos integrales (no fraccionarios) significativos:

& # 8226; Divida el número por 10 N
& # 8226; Agregar 0.5
& # 8226; Truncar los dígitos de la fracción (es decir, truncar el resultado en un entero)
& # 8226; Multiplica por 10 N

Puede hacer esto en cualquier calculadora, por ejemplo, que tenga un " INT " (truncamiento de enteros) operador.

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

Aquí está el código de Pyrolistical (actualmente la respuesta principal) en Visual Basic.NET, en caso de que alguien lo necesite:

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

La función Number cambiará la salida del formulario "8.143e+5" a "814300".

Este es uno que se me ocurrió en 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();

Necesitaba esto en Go, que era un poco complicado por la falta de math.Round() de la biblioteca estándar de Go (antes de go1.10). Así que tuve que preparar eso también. Aquí está mi traducción de Excelente respuesta de 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
}
public static double roundToSignificantDigits(double num, int n) {
    return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}

Este código utiliza la función de formato incorporada que se convierte en una función de redondeo

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top