From 0cc7c9a13ceb1afe703d328222dace69cf84a81b Mon Sep 17 00:00:00 2001 From: "A. S. Budden" Date: Tue, 31 May 2016 13:17:40 +0100 Subject: [PATCH 1/4] 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]. --- compiler/semfold.nim | 2 +- lib/pure/math.nim | 92 +++++++++++++++++++++++--------- tests/overload/tstmtoverload.nim | 2 +- web/news.txt | 5 ++ 4 files changed, 74 insertions(+), 27 deletions(-) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index c5a8cc2a2a..feea6ce347 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 ce418d72c9..bd67644333 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: "".} ## Computes the exponential function of `x` (pow(E, x)) - proc frexp*(x: float32, exponent: var int): float32 {. - importc: "frexp", header: "".} - proc frexp*(x: float64, exponent: var int): float64 {. - importc: "frexp", header: "".} - ## 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: "".} - proc round*(x: float64): int {.importc: "lrint", header: "".} - ## converts a float to an int by rounding. + proc round0(x: float32): float32 {.importc: "roundf", header: "".} + proc round0(x: float64): float64 {.importc: "round", header: "".} + ## 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: "".} proc arccos*(x: float64): float64 {.importc: "acos", header: "".} @@ -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: "".} + proc frexp*(x: float64, exponent: var int): float64 {. + importc: "frexp", header: "".} + ## 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) diff --git a/tests/overload/tstmtoverload.nim b/tests/overload/tstmtoverload.nim index f1944b6379..75584bcab4 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 c100c8aee9..3dd6c4e1ec 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 From 46a2993917edd73fe55623a06999f4811067a754 Mon Sep 17 00:00:00 2001 From: "A. S. Budden" Date: Tue, 31 May 2016 13:26:41 +0100 Subject: [PATCH 2/4] Implemented function to split floating point numbers at the decimal place (equivalent to C's modf function). Fixes #4195. --- lib/pure/math.nim | 25 +++++++++++++++++++++++++ web/news.txt | 2 ++ 2 files changed, 27 insertions(+) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index bd67644333..1edbfcab6a 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -312,6 +312,23 @@ else: 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.} = @@ -399,3 +416,11 @@ when isMainModule: doAssert floatIsEqual(round(-547.652, -2), -500.0) doAssert floatIsEqual(round(-547.652, -3), -1000.0) doAssert floatIsEqual(round(-547.652, -4), 0.0) + + block: # splitDecimal() tests + doAssert floatIsEqual(splitDecimal(54.674).intpart, 54.0) + doAssert floatIsEqual(splitDecimal(54.674).floatpart, 0.674) + doAssert floatIsEqual(splitDecimal(-693.4356).intpart, -693.0) + doAssert floatIsEqual(splitDecimal(-693.4356).floatpart, -0.4356) + doAssert floatIsEqual(splitDecimal(0.0).intpart, 0.0) + doAssert floatIsEqual(splitDecimal(0.0).floatpart, 0.0) diff --git a/web/news.txt b/web/news.txt index 3dd6c4e1ec..3290baa5c0 100644 --- a/web/news.txt +++ b/web/news.txt @@ -61,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 Date: Tue, 31 May 2016 15:16:50 +0100 Subject: [PATCH 3/4] Correction to round0 following review. --- lib/pure/math.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 1edbfcab6a..8a1c802be2 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -247,7 +247,7 @@ else: proc exp*(x: float32): float32 {.importc: "Math.exp", nodecl.} proc exp*(x: float64): float64 {.importc: "Math.exp", nodecl.} - proc round0*(x: float): float {.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.} From 695b25100c11b1d3c03b770b13c64af20d7d9981 Mon Sep 17 00:00:00 2001 From: "A. S. Budden" Date: Tue, 31 May 2016 15:53:18 +0100 Subject: [PATCH 4/4] Changed math.nim tests to use newly defined ==~ operator --- lib/pure/math.nim | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 8a1c802be2..2b903bedbf 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -393,34 +393,34 @@ when isMainModule and not defined(JS): 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) + proc `==~`(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) + 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 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) + 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 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) + 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 floatIsEqual(splitDecimal(54.674).intpart, 54.0) - doAssert floatIsEqual(splitDecimal(54.674).floatpart, 0.674) - doAssert floatIsEqual(splitDecimal(-693.4356).intpart, -693.0) - doAssert floatIsEqual(splitDecimal(-693.4356).floatpart, -0.4356) - doAssert floatIsEqual(splitDecimal(0.0).intpart, 0.0) - doAssert floatIsEqual(splitDecimal(0.0).floatpart, 0.0) + 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