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
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: of nkCharLit..nkUInt64Lit:
case b.kind case b.kind
of nkCharLit..nkUInt64Lit: result = a.intVal <= b.intVal 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") else: internalError(a.info, "leValueConv")
of nkFloatLit..nkFloat128Lit: of nkFloatLit..nkFloat128Lit:
case b.kind case b.kind

View file

@ -138,19 +138,10 @@ when not defined(JS):
proc exp*(x: float64): float64 {.importc: "exp", header: "<math.h>".} proc exp*(x: float64): float64 {.importc: "exp", header: "<math.h>".}
## Computes the exponential function of `x` (pow(E, x)) ## Computes the exponential function of `x` (pow(E, x))
proc frexp*(x: float32, exponent: var int): float32 {. proc round0(x: float32): float32 {.importc: "roundf", header: "<math.h>".}
importc: "frexp", header: "<math.h>".} proc round0(x: float64): float64 {.importc: "round", header: "<math.h>".}
proc frexp*(x: float64, exponent: var int): float64 {. ## Converts a float to an int by rounding. Used internally by the round
importc: "frexp", header: "<math.h>".} ## function when the specified number of places is 0.
## 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 arccos*(x: float32): float32 {.importc: "acosf", header: "<math.h>".} proc arccos*(x: float32): float32 {.importc: "acosf", header: "<math.h>".}
proc arccos*(x: float64): float64 {.importc: "acos", 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: float32): float32 {.importc: "Math.exp", nodecl.}
proc exp*(x: float64): float64 {.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: float32): float32 {.importC: "Math.pow", nodecl.}
proc pow*(x, y: float64): float64 {.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: float32): float32 {.importc: "Math.acos", nodecl.}
proc arccos*(x: float64): float64 {.importc: "Math.acos", nodecl.} proc arccos*(x: float64): float64 {.importc: "Math.acos", nodecl.}
proc arcsin*(x: float32): float32 {.importc: "Math.asin", nodecl.} proc arcsin*(x: float32): float32 {.importc: "Math.asin", nodecl.}
@ -295,6 +275,43 @@ else:
var y = exp(2.0*x) var y = exp(2.0*x)
return (y-1.0)/(y+1.0) 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.} {.pop.}
proc degToRad*[T: float32|float64](d: T): T {.inline.} = 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(lgamma(1.0) == 0.0) # ln(1.0) == 0.0
assert(erf(6.0) > erf(5.0)) assert(erf(6.0) > erf(5.0))
assert(erfc(6.0) < erfc(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 = template test(loopCount: int, extraF: float, testBody: stmt): stmt =
block: block:
test(loopCount, round(extraF), testBody) test(loopCount, round(extraF).int, testBody)
template test(loopCount: int, testBody: stmt): stmt = template test(loopCount: int, testBody: stmt): stmt =
block: block:

View file

@ -45,6 +45,11 @@ Changes affecting backwards compatibility
- The path handling changed. The project directory is not added to the - The path handling changed. The project directory is not added to the
search path automatically anymore. Add this line to your project's search path automatically anymore. Add this line to your project's
config to get back the old behaviour: ``--path:"$projectdir"``. 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 Library Additions