summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2020-12-11 02:01:43 -0800
committerGitHub <noreply@github.com>2020-12-11 11:01:43 +0100
commit0b73106ccf983565d34654ee8b1167827b53b01a (patch)
tree2077391acd95abccf408cc832b466f3dcc3dee34
parentbb1c962286922487c5243ca7304fd95fdd27ea53 (diff)
downloadNim-0b73106ccf983565d34654ee8b1167827b53b01a.tar.gz
add math.isNaN (#16179)
* add math.isNaN
* isNaN now works with --passc:-ffast-math; tests
* Update lib/pure/math.nim

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
-rw-r--r--changelog.md2
-rw-r--r--lib/js/dom.nim1
-rw-r--r--lib/pure/math.nim21
-rw-r--r--tests/stdlib/tmath.nim31
4 files changed, 50 insertions, 5 deletions
diff --git a/changelog.md b/changelog.md
index 0e96c868a..ab323337c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -58,6 +58,8 @@
 - `strscans.scanf` now supports parsing single characters.
 
 
+- Added `math.isNaN`.
+
 ## Language changes
 
 - `nimscript` now handles `except Exception as e`.
diff --git a/lib/js/dom.nim b/lib/js/dom.nim
index b73a85291..b74a58a1a 100644
--- a/lib/js/dom.nim
+++ b/lib/js/dom.nim
@@ -1683,6 +1683,7 @@ proc decodeURIComponent*(uri: cstring): cstring {.importc, nodecl.}
 proc encodeURIComponent*(uri: cstring): cstring {.importc, nodecl.}
 proc isFinite*(x: BiggestFloat): bool {.importc, nodecl.}
 proc isNaN*(x: BiggestFloat): bool {.importc, nodecl.}
+  ## see also `math.isNaN`.
 
 proc newEvent*(name: cstring): Event {.importcpp: "new Event(@)", constructor.}
 
diff --git a/lib/pure/math.nim b/lib/pure/math.nim
index b2433d309..5f3261f1a 100644
--- a/lib/pure/math.nim
+++ b/lib/pure/math.nim
@@ -58,6 +58,10 @@ import std/private/since
 
 import bitops, fenv
 
+when defined(c) or defined(cpp):
+  proc c_isnan(x: float): bool {.importc: "isnan", header: "<math.h>".}
+    # a generic like `x: SomeFloat` might work too if this is implemented via a C macro.
+
 func binom*(n, k: int): int =
   ## Computes the `binomial coefficient <https://en.wikipedia.org/wiki/Binomial_coefficient>`_.
   runnableExamples:
@@ -133,10 +137,27 @@ type
     fcInf,           ## value is positive infinity
     fcNegInf         ## value is negative infinity
 
+func isNaN*(x: SomeFloat): bool {.inline, since: (1,5,1).} =
+  ## Returns whether `x` is a `NaN`, more efficiently than via `classify(x) == fcNan`.
+  ## Works even with: `--passc:-ffast-math`.
+  runnableExamples:
+    doAssert NaN.isNaN
+    doAssert not Inf.isNaN
+    doAssert isNaN(Inf - Inf)
+    doAssert not isNan(3.1415926)
+    doAssert not isNan(0'f32)
+
+  template fn: untyped = result = x != x
+  when nimvm: fn()
+  else:
+    when defined(js): fn()
+    else: result = c_isnan(x)
+
 func classify*(x: float): FloatClass =
   ## Classifies a floating point value.
   ##
   ## Returns ``x``'s class as specified by `FloatClass enum<#FloatClass>`_.
+  ## Doesn't work with: `--passc:-ffast-math`.
   runnableExamples:
     doAssert classify(0.3) == fcNormal
     doAssert classify(0.0) == fcZero
diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim
index ac5ce4c90..18e42cbdc 100644
--- a/tests/stdlib/tmath.nim
+++ b/tests/stdlib/tmath.nim
@@ -12,8 +12,11 @@ discard """
 
 [Suite] ^
 '''
+matrix:"; -d:nimTmathCase2 -d:danger --passc:-ffast-math"
 """
 
+# xxx: fix bugs for js then add: targets:"c js"
+
 import math, random, os
 import unittest
 import sets, tables
@@ -37,12 +40,16 @@ suite "random int":
     var rand: int
     for i in 1..1000:
       rand = rand(100..1000)
-      check rand < 1000
+      when defined(js): # xxx bug: otherwise fails
+        check rand <= 1000
+      else:
+        check rand < 1000
       check rand >= 100
   test " again gives new numbers":
 
     var rand1 = rand(1000000)
-    os.sleep(200)
+    when not defined(js):
+      os.sleep(200)
 
     var rand2 = rand(1000000)
     check rand1 != rand2
@@ -72,7 +79,8 @@ suite "random float":
   test " again gives new numbers":
 
     var rand1:float = rand(1000000.0)
-    os.sleep(200)
+    when not defined(js):
+      os.sleep(200)
 
     var rand2:float = rand(1000000.0)
     check rand1 != rand2
@@ -151,6 +159,7 @@ block:
   when not defined(js):
     # Check for no side effect annotation
     proc mySqrt(num: float): float {.noSideEffect.} =
+      # xxx unused
       return sqrt(num)
 
     # check gamma function
@@ -194,7 +203,8 @@ block:
       #special case
       doAssert(classify(trunc(1e1000000)) == fcInf)
       doAssert(classify(trunc(-1e1000000)) == fcNegInf)
-      doAssert(classify(trunc(0.0/0.0)) == fcNan)
+      when not defined(nimTmathCase2):
+        doAssert(classify(trunc(0.0/0.0)) == fcNan)
       doAssert(classify(trunc(0.0)) == fcZero)
 
       #trick the compiler to produce signed zero
@@ -211,7 +221,8 @@ block:
       doAssert(trunc(0.1'f32) == 0)
       doAssert(classify(trunc(1e1000000'f32)) == fcInf)
       doAssert(classify(trunc(-1e1000000'f32)) == fcNegInf)
-      doAssert(classify(trunc(f_nan.float32)) == fcNan)
+      when not defined(nimTmathCase2):
+        doAssert(classify(trunc(f_nan.float32)) == fcNan)
       doAssert(classify(trunc(0.0'f32)) == fcZero)
 
     block: # sgn() tests
@@ -272,3 +283,13 @@ block:
       doAssert log2(2.0'f32) == 1.0'f32
       doAssert log2(1.0'f32) == 0.0'f32
       doAssert classify(log2(0.0'f32)) == fcNegInf
+
+template main =
+  # xxx wrap all under `main` so it also gets tested in vm.
+  block: # isNaN
+    doAssert NaN.isNaN
+    doAssert not Inf.isNaN
+    doAssert isNaN(Inf - Inf)
+
+main()
+static: main()