Domanda

Come si può round qualsiasi numero (non solo numeri interi > 0) di N cifre significative?

Per esempio, se voglio giro di tre cifre significative, sto cercando una formula che potesse prendere:

1,239,451 e ritorno 1,240,000

12.1257 e ritorno 12.1

.0681 e ritorno .0681

5 e ritorno 5

Naturalmente l'algoritmo non deve essere codificata in modo da gestire al N di 3, anche se questo sarebbe un buon inizio.

È stato utile?

Soluzione

Ecco lo stesso codice in Java senza il bug 12.100000000000001 di altre risposte

Ho anche rimosso il codice ripetuto, modificato power in un tipo intero per evitare problemi mobili quando n - d è stato fatto e reso più chiaro l'intermedio lungo

Il bug è stato causato dalla moltiplicazione di un numero grande con un numero piccolo. Divido invece due numeri di dimensioni simili.

Modifica
Risolti più bug. Aggiunto controllo per 0 in quanto si tradurrebbe in NaN. Ha reso la funzione effettivamente operativa con numeri negativi (il codice originale non gestisce i numeri negativi perché un registro di un numero negativo è un numero complesso)

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

Altri suggerimenti

Ecco una breve e dolce implementazione 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

SINTESI:

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

Quindi è necessario trovare la posizione decimale della prima cifra diversa da zero, quindi salvare le cifre N-1 successive, quindi arrotondare l'ennesima cifra in base al resto.

Possiamo usare il registro per fare il primo.

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

Quindi per i numeri > 0, prendi il ceil del registro. Per numeri & Lt; 0, prendi la parola d'ordine del registro.

Ora abbiamo la cifra d: 7 nel primo caso, 2 nel 2 °, -2 nel 3 °.

Dobbiamo arrotondare la (d-N) th cifra. Qualcosa del tipo:

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

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

Quindi esegui l'arrotondamento standard:

roundedrest = (int)(roundedrest + 0.5);

E annulla il pow.

roundednum = pow(roundedrest, -(power))

Dove potenza è la potenza calcolata sopra.


Informazioni sulla precisione: la risposta di Pyrolistical è davvero più vicina al risultato reale. Ma nota che non puoi rappresentare esattamente 12.1 in ogni caso. Se stampi le risposte come segue:

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

Le risposte sono:

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

Quindi, usa la risposta di Pyro!

Non è il "breve e dolce" implementazione di JavaScript

Number(n).toPrecision(sig)

ad es.

alert(Number(12345).toPrecision(3)

?

Scusa, non voglio essere faceto qui, è solo che con il "roundit" funzione Claudiu e l' .toPrecision in JavaScript mi dà risultati diversi, ma solo l'arrotondamento dell'ultima cifra.

JavaScript:

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

.NET

roundit(8.14301,4) == 8.144

La soluzione di Pyrolistical (molto bella!) ha ancora un problema. Il valore massimo doppio in Java è nell'ordine di 10 ^ 308, mentre il valore minimo è nell'ordine di 10 ^ -324. Pertanto, è possibile riscontrare problemi quando si applica la funzione roundToSignificantFigures a qualcosa che rientra in alcuni poteri di dieci di Double.MIN_VALUE. Ad esempio, quando chiami

roundToSignificantFigures(1.234E-310, 3);

quindi la variabile power avrà il valore 3 - (-309) = 312. Di conseguenza, la variabile magnitude diventerà Infinity, e da allora in poi sarà tutta spazzatura. Fortunatamente, questo non è un problema insormontabile: è solo il fattore num * magnitude che trabocca. Ciò che conta davvero è il prodotto magintude e non trabocca. Un modo per risolverlo è suddividere la moltiplicazione per il fattore <=> in due passaggi:


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

Che ne dici di questa soluzione java:

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

Ecco una versione modificata di JavaScript di Ates che gestisce i numeri negativi.

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

Questo è arrivato con 5 anni di ritardo, ma anche se condividerò per gli altri che hanno ancora lo stesso problema. Mi piace perché è semplice e senza calcoli sul lato del codice. Vedi Metodi integrati per la visualizzazione di figure significative per ulteriori informazioni informazioni.

Questo se vuoi solo stamparlo.

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

Questo è se vuoi convertirlo:

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

Ecco un esempio in azione:

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

Hai provato solo la codifica è il modo in cui ti piacerebbe farlo a mano?

  1. Convertire il numero in una stringa
  2. A partire dall'inizio del stringa, il conte cifre - zeri iniziali non sono significativo, tutto il resto è.
  3. Quando si arriva al "ennesimo" cifre, guarda avanti alla prossima cifra e se si tratta di 5 o superiore, round up.
  4. Sostituire tutte le cifre finali con zeri.

[Corretto, 26/10/2009]

In sostanza, per N cifre frazionarie significative:

&

# 8226; Moltiplica il numero per 10 N
& # 8226; Aggiungi 0,5
& # 8226; Tronca le cifre della frazione (ovvero, tronca il risultato in un numero intero)
& # 8226; Dividi per 10 N

Per N cifre integrali (non frazionarie) significative:

&

# 8226; Dividi il numero per 10 N
& # 8226; Aggiungi 0,5
& # 8226; Tronca le cifre della frazione (ovvero, tronca il risultato in un numero intero)
& # 8226; Moltiplicare per 10 N

Puoi farlo su qualsiasi calcolatrice, ad esempio, che ha un " INT " (troncamento dei numeri interi) operatore.

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

Ecco il codice di Pyrolistical (attualmente la risposta migliore) in Visual Basic.NET, nel caso qualcuno ne avesse bisogno:

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 funzione Number cambierà l'output del modulo "8.143e+5" in "814300".

Questo è quello che ho ideato in 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();

Ne avevo bisogno in Go, il che era un po 'complicato dalla mancanza della libreria standard Go di math.Round() (prima di go1.10). Quindi ho dovuto montare anche quello. Ecco la mia traduzione di la risposta eccellente di 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());
}

Questo codice utilizza la funzione di formattazione integrata che viene trasformata in una funzione di arrotondamento

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top