Scala Doubles e Precision


118

Esiste una funzione che può troncare o arrotondare un Double? A un certo punto del mio codice vorrei che un numero del tipo: 1.23456789fosse arrotondato a1.23


9
Dopo aver esaminato tutte le risposte immagino che la risposta breve sia no? :)
Marsellus Wallace

4
@Gevorg Mi hai fatto ridere. Sono nuovo in Scala da altri linguaggi numerici e la mia mascella è quasi caduta a terra leggendo questo thread. Questo è uno stato di cose folle per un linguaggio di programmazione.
ely

Risposte:


150

Puoi usare scala.math.BigDecimal:

BigDecimal(1.23456789).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble

Ci sono un certo numero di altre modalità di arrotondamento , che sfortunatamente non sono molto ben documentate al momento (sebbene i loro equivalenti Java lo siano ).


5
Abbastanza probabile, direi. Tutto ciò che riguarda le reti o la finanza può richiedere arrotondamenti e anche prestazioni.
Rex Kerr

28
Suppongo che ci siano persone per le quali una lunga chiamata a una biblioteca goffa è più comprensibile della semplice matematica. Lo consiglierei "%.2f".format(x).toDoublein quel caso. Solo 2 volte più lento e devi usare solo una libreria che già conosci.
Rex Kerr

6
@ RexKerr, in questo caso non stai arrotondando .. semplicemente troncando.
José Leal,

16
@ JoséLeal - Eh? scala> "%.2f".format(0.714999999999).toDouble res13: Double = 0.71ma scala> "%.2f".format(0.715).toDouble res14: Double = 0.72.
Rex Kerr

5
@RexKerr Preferisco il tuo modo string.format, ma in locale come il mio (finlandese), bisogna fare attenzione a correggere il locale ROOT. Ad esempio "% .2f" .formatLocal (java.util.Locale.ROOT, x) .toDouble. Sembra che il formato usi ',' a causa della locale mentre toDouble non è in grado di accettarlo e lancia un'eccezione NumberFormatException. Questo ovviamente si basa su dove viene eseguito il codice , non su dove viene sviluppato.
akauppi

77

Ecco un'altra soluzione senza BigDecimals

Troncare:

(math floor 1.23456789 * 100) / 100

Il giro:

(math rint 1.23456789 * 100) / 100

O per qualsiasi doppia ne precisione p:

def truncateAt(n: Double, p: Int): Double = { val s = math pow (10, p); (math floor n * s) / s }

Simile può essere fatto per la funzione di arrotondamento, questa volta usando il curry:

def roundAt(p: Int)(n: Double): Double = { val s = math pow (10, p); (math round n * s) / s }

che è più riutilizzabile, ad esempio quando si arrotondano gli importi in denaro si potrebbe utilizzare quanto segue:

def roundAt2(n: Double) = roundAt(2)(n)

8
roundAt2 dovrebbe essere def roundAt2 (n: Double) = roundAt (2) (n) no?
C4stor

questo sembra restituire un risultato errato per NaN, non è vero?
jangorecki

il problema floorè che truncateAt(1.23456789, 8)tornerà 1.23456788mentre roundAt(1.23456789, 8)restituirà il valore corretto di1.23456789
Todor Kolev

35

Dato che nessuno ha %ancora menzionato l' operatore, ecco che arriva. Esegue solo il troncamento e non puoi fare affidamento sul valore restituito per non avere imprecisioni in virgola mobile, ma a volte è utile:

scala> 1.23456789 - (1.23456789 % 0.01)
res4: Double = 1.23

2
Non lo consiglierei anche se è la mia risposta: gli stessi problemi di imprecisione menzionati da @ryryguy nel commento di un'altra risposta influiscono anche qui. Usa string.format con le impostazioni locali di Java ROOT (ne parlerò qui).
akauppi

questo è perfetto se devi solo renderizzare il valore e non usarlo mai in operazioni successive. grazie
Alexander Arendar

3
ecco qualcosa di divertente: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi

12

Che ne dite di :

 val value = 1.4142135623730951

//3 decimal places
println((value * 1000).round / 1000.toDouble)

//4 decimal places
println((value * 10000).round / 10000.toDouble)

soluzione abbastanza pulita. Ecco il mio per il troncamento: ((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDoublenon l'ho testato troppo però. Questo codice farebbe 2 cifre decimali.
robert

7

Modifica: risolto il problema segnalato da @ryryguy. (Grazie!)

Se vuoi che sia veloce, Kaito ha l'idea giusta. math.powè lento, però. Per qualsiasi uso standard è meglio con una funzione ricorsiva:

def trunc(x: Double, n: Int) = {
  def p10(n: Int, pow: Long = 10): Long = if (n==0) pow else p10(n-1,pow*10)
  if (n < 0) {
    val m = p10(-n).toDouble
    math.round(x/m) * m
  }
  else {
    val m = p10(n).toDouble
    math.round(x*m) / m
  }
}

Questo è circa 10 volte più veloce se ti trovi nell'intervallo di Long(cioè 18 cifre), quindi puoi arrotondare ovunque tra 10 ^ 18 e 10 ^ -18.


3
Attenzione, la moltiplicazione per il reciproco non funziona in modo affidabile, perché potrebbe non essere rappresentabile in modo affidabile come un doppio: scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> res12: Double = 0.023514999999999998. Dividi invece per il significato:math.round(x*100000)/100000.0
ryryguy

Può anche essere utile sostituire la p10funzione ricorsiva con una ricerca di array: l'array aumenterà il consumo di memoria di circa 200 byte ma probabilmente salverà diverse iterazioni per chiamata.
Levi Ramsey

4

Puoi usare classi implicite:

import scala.math._

object ExtNumber extends App {
  implicit class ExtendedDouble(n: Double) {
    def rounded(x: Int) = {
      val w = pow(10, x)
      (n * w).toLong.toDouble / w
    }
  }

  // usage
  val a = 1.23456789
  println(a.rounded(2))
}

1
Specificare che questo metodo serve solo per troncare e non per arrotondare correttamente.
bobo32

4

Per chi è interessato, ecco alcune volte per le soluzioni suggerite ...

Rounding
Java Formatter: Elapsed Time: 105
Scala Formatter: Elapsed Time: 167
BigDecimal Formatter: Elapsed Time: 27

Truncation
Scala custom Formatter: Elapsed Time: 3 

Il troncamento è il più veloce, seguito da BigDecimal. Tieni presente che questi test sono stati eseguiti eseguendo un'esecuzione normale scala, senza utilizzare strumenti di benchmarking.

object TestFormatters {

  val r = scala.util.Random

  def textFormatter(x: Double) = new java.text.DecimalFormat("0.##").format(x)

  def scalaFormatter(x: Double) = "$pi%1.2f".format(x)

  def bigDecimalFormatter(x: Double) = BigDecimal(x).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble

  def scalaCustom(x: Double) = {
    val roundBy = 2
    val w = math.pow(10, roundBy)
    (x * w).toLong.toDouble / w
  }

  def timed(f: => Unit) = {
    val start = System.currentTimeMillis()
    f
    val end = System.currentTimeMillis()
    println("Elapsed Time: " + (end - start))
  }

  def main(args: Array[String]): Unit = {

    print("Java Formatter: ")
    val iters = 10000
    timed {
      (0 until iters) foreach { _ =>
        textFormatter(r.nextDouble())
      }
    }

    print("Scala Formatter: ")
    timed {
      (0 until iters) foreach { _ =>
        scalaFormatter(r.nextDouble())
      }
    }

    print("BigDecimal Formatter: ")
    timed {
      (0 until iters) foreach { _ =>
        bigDecimalFormatter(r.nextDouble())
      }
    }

    print("Scala custom Formatter (truncation): ")
    timed {
      (0 until iters) foreach { _ =>
        scalaCustom(r.nextDouble())
      }
    }
  }

}

1
Caro scalaCustom non sta arrotondando, sta solo troncando
Ravinder Payal

hmm, OP non era specifico per l'arrotondamento o il troncamento; ...truncate or round a Double.
cevaris

Ma a mio parere confrontare la velocità / tempo di esecuzione della funzione di troncamento di con le funzioni di arrotondamento è inadeguato. Ecco perché ti ho chiesto di chiarire al lettore che la funzionalità personalizzata si limita a troncare. E la funzione troncare / personalizzata menzionata da te può essere ulteriormente semplificata. val doubleParts = double. toString.split (".") Ora ottieni i primi due caratteri di doubleParts.taile concatena con le stringhe "." e doubleParts. heade analizza per raddoppiare.
Ravinder Payal

1
aggiornato, guarda meglio? anche il tuo suggerimento toString.split(".")e doubleParts.head/tailsuggerimento potrebbe risentire dell'allocazione di array extra più la concatenazione di stringhe. avrebbe bisogno di fare un test per essere sicuro però.
cevaris

3

Recentemente, ho affrontato un problema simile e l'ho risolto utilizzando il seguente approccio

def round(value: Either[Double, Float], places: Int) = {
  if (places < 0) 0
  else {
    val factor = Math.pow(10, places)
    value match {
      case Left(d) => (Math.round(d * factor) / factor)
      case Right(f) => (Math.round(f * factor) / factor)
    }
  }
}

def round(value: Double): Double = round(Left(value), 0)
def round(value: Double, places: Int): Double = round(Left(value), places)
def round(value: Float): Double = round(Right(value), 0)
def round(value: Float, places: Int): Double = round(Right(value), places)

Ho usato questo problema SO. Ho un paio di funzioni sovraccaricate per entrambe le opzioni Float \ Double e implicit \ explicit. Si noti che è necessario menzionare esplicitamente il tipo restituito in caso di funzioni sovraccaricate.


Inoltre, puoi usare l'approccio per il potere di @ rex-kerr invece di Math.pow
Khalid Saifullah


1

Non userei BigDecimal se ti interessano le prestazioni. BigDecimal converte i numeri in stringa e quindi li analizza nuovamente:

  /** Constructs a `BigDecimal` using the decimal text representation of `Double` value `d`, rounding if necessary. */
  def decimal(d: Double, mc: MathContext): BigDecimal = new BigDecimal(new BigDec(java.lang.Double.toString(d), mc), mc)

Mi atterrò alle manipolazioni matematiche come suggerito da Kaito .


0

Un po 'strano ma carino. Uso String e non BigDecimal

def round(x: Double)(p: Int): Double = {
    var A = x.toString().split('.')
    (A(0) + "." + A(1).substring(0, if (p > A(1).length()) A(1).length() else p)).toDouble
}

0

Tu puoi fare:Math.round(<double precision value> * 100.0) / 100.0 Ma Math.round è più veloce ma si rompe male nei casi d'angolo con un numero molto alto di cifre decimali (ad esempio round (1000.0d, 17)) o una parte intera grande (ad esempio round (90080070060.1d, 9) ).

Usa Bigdecimal è un po 'inefficiente in quanto converte i valori in stringa ma più pertinente: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() usa la tua preferenza per la modalità Arrotondamento.

Se sei curioso e vuoi sapere più in dettaglio perché questo accade puoi leggere questo: inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.