Why does [expr {NaN}] results in "domain error: argument not in valid range"

793 views
Skip to first unread message

Yusuke Yamasaki

unread,
May 15, 2018, 5:35:48 AM5/15/18
to tcl-core
Hi, 

I wanted to handle NaN or Inf safely in a long math expression.
For example, sqrt(-1) results in domain error so I wrote a user defined function under tcl::mathfunc namespace.

proc is_computable_double {val} {
expr {[string is double -strict $val] && ![is_nan $val]}
}

proc is_nan {val} {
string match -nocase [string trimleft $val +-] "nan"
}

proc sqrtf {a} {
if {[is_computable_double $a] && $a >= 0} {
expr {sqrt($a)}
} else {
return NaN
}
}

proc tcl::mathfunc::sqrtf {a} {
::sqrtf $a

tcl::mathfunc::sqrtf -1; # => NaN
expr {sqrtf(-1)}; # => domain error: argument not in valid range

I noticed that expr never returns NaN. In such a case, it causes a domain error.
Is there any way to avoid it?

I had a danger expression like this.
set tau [expr {$uxy / (sqrt($uxx) * sqrt($uyy))}]

I made it safer by chaining some procedures.
set tau [divf $uxy [mulf [sqrtf $uxx] [sqrtf $uyy]]]

But I wanted to rewrite it as follows.
set tau [expr {divf($uxy,mulf(sqrtf($uxx),sqrtf($uyy)))}]

Above is a relatively short example but I have much longer expressions in my actual code.
So I wanted to make use of [expr] parser instead of nested procedures or something like tcl::mathfunc::sqrt...

Regards,
Yusuke

Yusuke Yamasaki

unread,
May 15, 2018, 6:21:12 AM5/15/18
to tcl-core
Hi, 

I came up with a solution.

proc myexpr {args} {
if {[catch {expr {*}$args} res]} {
if {$res eq "domain error: argument not in valid range"} {
return NaN
} else {
return -code error $res
}
} else {
return $res
}
}

I think this is enough for me.

Note: I found it's not a good idea to replace expr using rename.
rename expr _expr; proc expr {args} {...}

When this is done in wish, the history caller dies.
(bin) 23 % set errorInfo
can't read "history(nextid)": no such variable
    while executing
"_expr {*}$args"
    (procedure "expr" line 1)
    invoked from within
"expr {$history(nextid)-1}"
    (procedure "HistIndex" line 4)
    invoked from within
"HistIndex $event"
    (procedure "tcl::HistEvent" line 3)
    invoked from within
"tcl::HistEvent $event"
    (procedure "history" line 64)
    invoked from within
"history event [expr {[history nextid] -1}]"

Regards,
Yusuke

2018年5月15日火曜日 18時35分48秒 UTC+9 Yusuke Yamasaki:
Reply all
Reply to author
Forward
0 new messages