Modification to implementation of round() such that it returns a float and accepts a places argument (fixes #3473).

This also involved moving some functions around to get the hierarchy correct and the documentation for frexp was modified such that it was clear that it can return a float in either the range [-1, -0.5] or [0.5, 1].
This commit is contained in:
A. S. Budden
2016-05-31 13:17:40 +01:00
parent f3fdad0eda
commit 0cc7c9a13c
4 changed files with 74 additions and 27 deletions

View File

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

View File

@@ -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,43 @@ 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)
{.pop.}
proc degToRad*[T: float32|float64](d: T): T {.inline.} =
@@ -357,3 +374,28 @@ 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 floatIsEqual(x, y: float): bool = (abs(x-y) < 1e-9)
block: # round() tests
# Round to 0 decimal places
doAssert floatIsEqual(round(54.652), 55.0)
doAssert floatIsEqual(round(54.352), 54.0)
doAssert floatIsEqual(round(-54.652), -55.0)
doAssert floatIsEqual(round(-54.352), -54.0)
doAssert floatIsEqual(round(0.0), 0.0)
# Round to positive decimal places
doAssert floatIsEqual(round(-547.652, 1), -547.7)
doAssert floatIsEqual(round(547.652, 1), 547.7)
doAssert floatIsEqual(round(-547.652, 2), -547.65)
doAssert floatIsEqual(round(547.652, 2), 547.65)
# Round to negative decimal places
doAssert floatIsEqual(round(547.652, -1), 550.0)
doAssert floatIsEqual(round(547.652, -2), 500.0)
doAssert floatIsEqual(round(547.652, -3), 1000.0)
doAssert floatIsEqual(round(547.652, -4), 0.0)
doAssert floatIsEqual(round(-547.652, -1), -550.0)
doAssert floatIsEqual(round(-547.652, -2), -500.0)
doAssert floatIsEqual(round(-547.652, -3), -1000.0)
doAssert floatIsEqual(round(-547.652, -4), 0.0)

View File

@@ -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:

View File

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