summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/semfold.nim2
-rw-r--r--lib/pure/math.nim117
-rw-r--r--tests/overload/tstmtoverload.nim2
-rw-r--r--web/news.txt7
4 files changed, 101 insertions, 27 deletions
diff --git a/compiler/semfold.nim b/compiler/semfold.nim
index c5a8cc2a2..feea6ce34 100644
--- a/compiler/semfold.nim
+++ b/compiler/semfold.nim
@@ -489,7 +489,7 @@ proc leValueConv(a, b: PNode): bool =
   of nkCharLit..nkUInt64Lit:
     case b.kind
     of nkCharLit..nkUInt64Lit: result = a.intVal <= b.intVal
-    of nkFloatLit..nkFloat128Lit: result = a.intVal <= round(b.floatVal)
+    of nkFloatLit..nkFloat128Lit: result = a.intVal <= round(b.floatVal).int
     else: internalError(a.info, "leValueConv")
   of nkFloatLit..nkFloat128Lit:
     case b.kind
diff --git a/lib/pure/math.nim b/lib/pure/math.nim
index ce418d72c..2b903bedb 100644
--- a/lib/pure/math.nim
+++ b/lib/pure/math.nim
@@ -138,19 +138,10 @@ when not defined(JS):
   proc exp*(x: float64): float64 {.importc: "exp", header: "<math.h>".}
     ## Computes the exponential function of `x` (pow(E, x))
 
-  proc frexp*(x: float32, exponent: var int): float32 {.
-    importc: "frexp", header: "<math.h>".}
-  proc frexp*(x: float64, exponent: var int): float64 {.
-    importc: "frexp", header: "<math.h>".}
-    ## Split a number into mantissa and exponent.
-    ## `frexp` calculates the mantissa m (a float greater than or equal to 0.5
-    ## and less than 1) and the integer value n such that `x` (the original
-    ## float value) equals m * 2**n. frexp stores n in `exponent` and returns
-    ## m.
-
-  proc round*(x: float32): int {.importc: "lrintf", header: "<math.h>".}
-  proc round*(x: float64): int {.importc: "lrint", header: "<math.h>".}
-    ## converts a float to an int by rounding.
+  proc round0(x: float32): float32 {.importc: "roundf", header: "<math.h>".}
+  proc round0(x: float64): float64 {.importc: "round", header: "<math.h>".}
+    ## Converts a float to an int by rounding.  Used internally by the round
+    ## function when the specified number of places is 0.
 
   proc arccos*(x: float32): float32 {.importc: "acosf", header: "<math.h>".}
   proc arccos*(x: float64): float64 {.importc: "acos", header: "<math.h>".}
@@ -256,22 +247,11 @@ else:
 
   proc exp*(x: float32): float32 {.importc: "Math.exp", nodecl.}
   proc exp*(x: float64): float64 {.importc: "Math.exp", nodecl.}
-  proc round*(x: float): int {.importc: "Math.round", nodecl.}
+  proc round0(x: float): float {.importc: "Math.round", nodecl.}
 
   proc pow*(x, y: float32): float32 {.importC: "Math.pow", nodecl.}
   proc pow*(x, y: float64): float64 {.importc: "Math.pow", nodecl.}
 
-  proc frexp*[T: float32|float64](x: T, exponent: var int): T =
-    if x == 0.0:
-      exponent = 0
-      result = 0.0
-    elif x < 0.0:
-      result = -frexp(-x, exponent)
-    else:
-      var ex = floor(log2(x))
-      exponent = round(ex)
-      result = x / pow(2.0, ex)
-
   proc arccos*(x: float32): float32 {.importc: "Math.acos", nodecl.}
   proc arccos*(x: float64): float64 {.importc: "Math.acos", nodecl.}
   proc arcsin*(x: float32): float32 {.importc: "Math.asin", nodecl.}
@@ -295,6 +275,60 @@ else:
     var y = exp(2.0*x)
     return (y-1.0)/(y+1.0)
 
+proc round*[T: float32|float64](x: T, places: int = 0): T =
+  ## Round a floating point number.
+  ##
+  ## If `places` is 0 (or omitted), round to the nearest integral value
+  ## following normal mathematical rounding rules (e.g. `round(54.5) -> 55.0`).
+  ## If `places` is greater than 0, round to the given number of decimal
+  ## places, e.g. `round(54.346, 2) -> 54.35`.
+  ## If `places` is negative, round to the left of the decimal place, e.g.
+  ## `round(537.345, -1) -> 540.0`
+  if places == 0:
+    result = round0(x)
+  else:
+    var mult = pow(10.0, places.T)
+    result = round0(x*mult)/mult
+
+when not defined(JS):
+  proc frexp*(x: float32, exponent: var int): float32 {.
+    importc: "frexp", header: "<math.h>".}
+  proc frexp*(x: float64, exponent: var int): float64 {.
+    importc: "frexp", header: "<math.h>".}
+    ## Split a number into mantissa and exponent.
+    ## `frexp` calculates the mantissa m (a float greater than or equal to 0.5
+    ## and less than 1) and the integer value n such that `x` (the original
+    ## float value) equals m * 2**n. frexp stores n in `exponent` and returns
+    ## m.
+else:
+  proc frexp*[T: float32|float64](x: T, exponent: var int): T =
+    if x == 0.0:
+      exponent = 0
+      result = 0.0
+    elif x < 0.0:
+      result = -frexp(-x, exponent)
+    else:
+      var ex = floor(log2(x))
+      exponent = round(ex)
+      result = x / pow(2.0, ex)
+
+proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] =
+  ## Breaks `x` into an integral and a fractional part.
+  ##
+  ## Returns a tuple containing intpart and floatpart representing
+  ## the integer part and the fractional part respectively.
+  ##
+  ## Both parts have the same sign as `x`.  Analogous to the `modf`
+  ## function in C.
+  var
+    absolute: T
+  absolute = abs(x)
+  result.intpart = floor(absolute)
+  result.floatpart = absolute - result.intpart
+  if x < 0:
+    result.intpart = -result.intpart
+    result.floatpart = -result.floatpart
+
 {.pop.}
 
 proc degToRad*[T: float32|float64](d: T): T {.inline.} =
@@ -357,3 +391,36 @@ when isMainModule and not defined(JS):
   assert(lgamma(1.0) == 0.0) # ln(1.0) == 0.0
   assert(erf(6.0) > erf(5.0))
   assert(erfc(6.0) < erfc(5.0))
+when isMainModule:
+  # Function for approximate comparison of floats
+  proc `==~`(x, y: float): bool = (abs(x-y) < 1e-9)
+
+  block: # round() tests
+    # Round to 0 decimal places
+    doAssert round(54.652) ==~ 55.0
+    doAssert round(54.352) ==~ 54.0
+    doAssert round(-54.652) ==~ -55.0
+    doAssert round(-54.352) ==~ -54.0
+    doAssert round(0.0) ==~ 0.0
+    # Round to positive decimal places
+    doAssert round(-547.652, 1) ==~ -547.7
+    doAssert round(547.652, 1) ==~ 547.7
+    doAssert round(-547.652, 2) ==~ -547.65
+    doAssert round(547.652, 2) ==~ 547.65
+    # Round to negative decimal places
+    doAssert round(547.652, -1) ==~ 550.0
+    doAssert round(547.652, -2) ==~ 500.0
+    doAssert round(547.652, -3) ==~ 1000.0
+    doAssert round(547.652, -4) ==~ 0.0
+    doAssert round(-547.652, -1) ==~ -550.0
+    doAssert round(-547.652, -2) ==~ -500.0
+    doAssert round(-547.652, -3) ==~ -1000.0
+    doAssert round(-547.652, -4) ==~ 0.0
+
+  block: # splitDecimal() tests
+    doAssert splitDecimal(54.674).intpart ==~ 54.0
+    doAssert splitDecimal(54.674).floatpart ==~ 0.674
+    doAssert splitDecimal(-693.4356).intpart ==~ -693.0
+    doAssert splitDecimal(-693.4356).floatpart ==~ -0.4356
+    doAssert splitDecimal(0.0).intpart ==~ 0.0
+    doAssert splitDecimal(0.0).floatpart ==~ 0.0
diff --git a/tests/overload/tstmtoverload.nim b/tests/overload/tstmtoverload.nim
index f1944b637..75584bcab 100644
--- a/tests/overload/tstmtoverload.nim
+++ b/tests/overload/tstmtoverload.nim
@@ -10,7 +10,7 @@ template test(loopCount: int, extraI: int, testBody: stmt): stmt =
 
 template test(loopCount: int, extraF: float, testBody: stmt): stmt =
   block:
-    test(loopCount, round(extraF), testBody)
+    test(loopCount, round(extraF).int, testBody)
 
 template test(loopCount: int, testBody: stmt): stmt =
   block:
diff --git a/web/news.txt b/web/news.txt
index c100c8aee..3290baa5c 100644
--- a/web/news.txt
+++ b/web/news.txt
@@ -45,6 +45,11 @@ Changes affecting backwards compatibility
 - The path handling changed. The project directory is not added to the
   search path automatically anymore. Add this line to your project's
   config to get back the old behaviour: ``--path:"$projectdir"``.
+- The ``round`` function in ``math.nim`` now returns a float and has been
+  corrected such that the C implementation always rounds up from .5 rather
+  than changing the operation for even and odd numbers.
+- The ``round`` function now accepts a ``places`` argument to round to a
+  given number of places (e.g. round 4.35 to 4.4 if ``places`` is 1).
 
 
 Library Additions
@@ -56,6 +61,8 @@ Library Additions
 - Added ``strscans`` module that implements a ``scanf`` for easy input extraction.
 - Added a version of ``parseutils.parseUntil`` that can deal with a string ``until`` token. The other
   versions are for ``char`` and ``set[char]``.
+- Added ``splitDecimal`` to ``math.nim`` to split a floating point value
+  into an integer part and a floating part (in the range -1<x<1).
 
 
 Compiler Additions