137 votes

Scala Doubles, et Précision

Existe-t-il une fonction permettant de tronquer ou d'arrondir un double ? À un moment donné dans mon code, je voudrais un nombre comme : 1.23456789 à arrondir à 1.23

12 votes

Après avoir regardé toutes les réponses, je suppose que la réponse courte est non ? :)

5 votes

@Gevorg Vous m'avez fait rire. Je suis un nouveau venu à Scala, venant d'autres langages à forte composante numérique, et ma mâchoire a presque touché le sol en lisant ce fil. C'est un état de fait insensé pour un langage de programmation.

175voto

Travis Brown Points 56342

Vous pouvez utiliser scala.math.BigDecimal :

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

Il existe un certain nombre d'autres modes d'arrondi qui ne sont malheureusement pas très bien documentés à l'heure actuelle (bien que leurs équivalents Java sont ).

1 votes

Notez que cette opération est très coûteuse - 100x plus lente que mult/round/mult tous avec des flottants. Elle n'est même pas plus compacte.

0 votes

@Rex : Quelle est la probabilité que vous fassiez des arrondis dans un code dont les performances sont critiques ? L'approche de la bibliothèque standard est plus sûre, plus flexible et exprime l'intention plus clairement à toute personne lisant le code dans le futur.

5 votes

Assez probable, je dirais. Tout ce qui implique des grilles ou des finances peut nécessiter des arrondis et aussi des performances.

82voto

Kaito Points 1037

Voici une autre solution sans BigDecimals

Coupé :

(math floor 1.23456789 * 100) / 100

Ronde :

(math rint 1.23456789 * 100) / 100

Ou pour tout double n et toute précision p :

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

On peut faire de même pour la fonction d'arrondi, en utilisant cette fois-ci la curée :

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

qui est plus réutilisable, par exemple, pour arrondir des montants d'argent, on peut utiliser ce qui suit :

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

8 votes

RoundAt2 devrait être def roundAt2(n : Double) = roundAt(2)(n) non ?

0 votes

Cela semble renvoyer un résultat incorrect pour NaN n'est-ce pas ?

0 votes

Le problème avec floor c'est que truncateAt(1.23456789, 8) retournera 1.23456788 tandis que roundAt(1.23456789, 8) retournera la valeur correcte de 1.23456789

38voto

akauppi Points 3125

Puisque personne n'a mentionné le % opérateur pourtant, voilà. Il ne fait que tronquer, et vous ne pouvez pas compter sur la valeur de retour pour ne pas avoir d'imprécisions en virgule flottante, mais parfois c'est pratique :

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

2 votes

Je ne le recommanderais pas, même si c'est ma propre réponse : les mêmes problèmes d'imprécision que ceux mentionnés par @ryryguy dans le commentaire d'une autre réponse se posent ici aussi. Utilisez string.format avec la locale Java Root (je ferai des commentaires à ce sujet).

0 votes

C'est parfait si vous avez juste besoin de rendre la valeur et de ne jamais l'utiliser dans les opérations suivantes. merci.

3 votes

Voici quelque chose de drôle : 26.257391515826225 - 0.057391515826223094 = 26.200000000000003

7voto

Rex Kerr Points 94401

Edit : corrigé le problème que @ryryguy a signalé. (Merci !)

Si vous voulez que ce soit rapide, Kaito a la bonne idée. math.pow est lent, cependant. Pour toute utilisation standard, il est préférable d'utiliser une fonction récursive :

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

C'est environ 10x plus rapide si vous êtes dans la gamme de Long (c'est-à-dire 18 chiffres), vous pouvez donc arrondir à n'importe quel chiffre entre 10^18 et 10^-18.

3 votes

Attention, la multiplication par la réciproque ne fonctionne pas de manière fiable, car elle peut ne pas être représentable de manière fiable comme un double : scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515) ==> res12: Double = 0.023514999999999998 . Divisez plutôt par l'importance : math.round(x*100000)/100000.0

0 votes

Il peut également être utile de remplacer la récursive p10 avec une recherche dans un tableau : le tableau augmentera la consommation de mémoire d'environ 200 octets mais permettra d'économiser plusieurs itérations par appel.

3voto

Khalid Saifullah Points 104

Récemment, j'ai rencontré un problème similaire et je l'ai résolu en utilisant l'approche suivante

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)

J'ai utilisé ce Un problème. J'ai quelques fonctions surchargées pour les deux Float \Double \explicit options. Notez que, vous devez mentionner explicitement le type de retour en cas de fonctions surchargées.

0 votes

Vous pouvez également utiliser l'approche de @rex-kerr pour la puissance au lieu de Math.pow.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X