Existe-t-il une fonction qui peut tronquer ou arrondir un double? À un moment donné dans mon code, j'aimerais qu'un chiffre comme 1.23456789
soit arrondi à 1.23
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 malheureusement ne sont pas très bien documentés à l'heure actuelle (bien que leurs équivalents Java soient ).
Voici une autre solution sans BigDecimals
Tronquer:
(math floor 1.23456789 * 100) / 100
Rond:
(math rint 1.23456789 * 100) / 100
Ou pour tout double n et précision p:
def truncateAt(n: Double, p: Int): Double = { val s = math pow (10, p); (math floor n * s) / s }
Une opération similaire peut être réalisée pour la fonction d'arrondi, cette fois en utilisant le currying:
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 en arrondissant les montants, on peut utiliser:
def roundAt2(p: Int) = roundAt(2)(p)
Puisque personne n'a encore mentionné l'opérateur %
, voici. Il ne fait que la troncature, et vous ne pouvez pas compter sur la valeur de retour pour ne pas avoir d'inexactitudes en virgule flottante, mais parfois c'est pratique:
scala> 1.23456789 - (1.23456789 % 0.01)
res4: Double = 1.23
Que diriez-vous :
val value = 1.4142135623730951
//3 decimal places
println((value * 1000).round / 1000.toDouble)
//4 decimal places
println((value * 10000).round / 10000.toDouble)
Edit: correction du problème signalé par @ryryguy. (Merci!)
Si vous voulez que ce soit rapide, Kaito a la bonne idée. math.pow
est lent, cependant. Pour toute utilisation standard, il vaut mieux 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
}
}
Ceci est environ 10 fois plus rapide si vous êtes dans la plage de Long
(18 chiffres), vous pouvez donc arrondir n'importe où entre 10 ^ 18 et 10 ^ -18.
Vous pouvez utiliser des classes implicites:
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))
}
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é this SO issue. J'ai quelques fonctions surchargées pour les options Float\Double et implicite\explicit. Notez que vous devez mentionner explicitement le type de retour en cas de fonctions surchargées.
Pour ceux qui sont intéressés, voici quelques moments pour les solutions suggérées ...
Rounding
Java Formatter: Elapsed Time: 105
Scala Formatter: Elapsed Time: 167
BigDecimal Formatter: Elapsed Time: 27
Truncation
Scala custom Formatter: Elapsed Time: 3
La troncature est la plus rapide, suivie de BigDecimal. N'oubliez pas que ces tests ont été effectués avec l'exécution de norma scala, sans utiliser aucun outil d'analyse comparative.
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())
}
}
}
}
Je n'utiliserais pas BigDecimal si vous vous souciez de la performance. BigDecimal convertit les nombres en chaîne, puis les analyse à nouveau:
/** 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)
Je vais m'en tenir aux manipulations mathématiques comme Kaito a suggéré.
Vous pouvez faire: Math.round(<double precision value> * 100.0) / 100.0
Mais Math.round est le plus rapide, mais il tombe mal en cas de virages avec un très grand nombre de décimales (par exemple, rond (1000.0d, 17)) ou un grand entier (par exemple, rond (90080070060.1d, )).
Utilisez Bigdecimal car il est peu efficace car il convertit les valeurs en chaîne, mais plus relâche: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue()
, utilisez votre préférence pour le mode Arrondi.
Si vous êtes curieux et que vous voulez en savoir plus sur les raisons pour lesquelles cela se produit, vous pouvez lire ceci:
Un peu étrange mais sympa. J'utilise String et 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
}