Сравнение числа с плавающей точкой в ​​tcl - PullRequest
4 голосов
/ 10 марта 2011

У меня возникают проблемы при расчете расстояния между точкой и линией.Существует проблема вычисления числа с плавающей запятой (сравнение выражений).Из-за этого я не смог узнать идеальное значение $ onextensionFlag.пожалуйста, смотрите следующее ... Могу ли я знать, что не так?

proc calculateDistanceToLinefrompoint {P line} {
# solution based on FAQ 1.02 on comp.graphics.algorithms
# L = sqrt( (Bx-Ax)^2 + (By-Ay)^2 )

#     (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay)
# s = -----------------------------
#                 L^2
# dist = |s|*L # =>
#        | (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) |
# dist = ---------------------------------
#                       L
# (Ay-Cy)(Ay-By)-(Ax-Cx)(Bx-Ax)
# r = -----------------------------
                # L^2
# r=0      P = A
# r=1      P = B
# r<0      P is on the backward extension of AB
# r>1      P is on the forward extension of AB
# 0<=r<=1    P is interior to AB

set ret 0
set Ax [lindex $line 0 0]
set Ay [lindex $line 0 1]
set Az [lindex $line 0 2]

set Bx [lindex $line 1 0]
set By [lindex $line 1 1]
set Bz [lindex $line 1 2]

set Cx [lindex $P 0]
set Cy [lindex $P 1]
set Cz [lindex $P 2]

if {$Ax==$Bx && $Ay==$By && $Az==$Bz} {
    set ret [list [GetDistanceBetweenTwoPoints $P [lindex $line 0]] 1] 
} else {
    set L [expr {sqrt(pow($Bx-$Ax,2) + pow($By-$Ay,2) + pow($Bz-$Az,2))}]
    #puts "L=$L"
    set d_val [expr {($Ay-$Cy)*($Bx-$Ax)-($Ax-$Cx)*($By-$Ay)-($Az-$Bz)*($Az-$Cz)}]
    set n_rval [expr {$d_val / pow($L,2)}]
    set n_rval [format "%0.3f" $n_rval]

    if { 0 < $n_rval && $n_rval < 1} {
        set onextensionFlag 0;# inside clipping area
    } elseif {$n_rval == 0 || $n_rval == 1} {
        set onextensionFlag 1 ;# inside clipping area (but on point)
    } elseif { $n_rval > 1 || $n_rval < 0 } {
        set onextensionFlag 2 ;# outside clipping area
    } else {
        set onextensionFlag 3 ;# consider inside clipping area
    }

    set ret [list [expr {abs($d_val) / $L}] $onextensionFlag $n_rval]


     }
    }

1 Ответ

6 голосов
/ 10 марта 2011

Числа с плавающей запятой (на всех языках, не только Tcl) представляют большинство чисел несколько неточно. Таким образом, их обычно не следует сравнивать на равенство, поскольку это действительно маловероятно. Вместо этого вы должны проверить, находятся ли эти два значения в определенном количестве друг друга (сумма известна как epsilon и учитывает, что есть небольшие ошибки в вычислениях с плавающей точкой).

В вашем коде вы могли бы написать это:

set epsilon 0.001; # Small, but non-zero
if { $epsilon < $n_rval && $n_rval < 1-$epsilon} {
    set onextensionFlag 0;# inside clipping area
} elseif {abs($n_rval) < $epsilon || abs(1-$n_rval) < $epsilon} {
    set onextensionFlag 1 ;# inside clipping area (but on point)
} elseif { $n_rval >= 1+$epsilon || $n_rval <= -$epsilon } {
    set onextensionFlag 2 ;# outside clipping area
} else {
    set onextensionFlag 3 ;# consider inside clipping area
}

В основном, думайте в терминах числовой линии, где вы меняете точки на небольшие интервалы:

                0                1
————————————————|————————————————|————————————————

до

             0-ε 0+ε          1-ε 1+ε
———————————————(—)——————————————(—)———————————————

Как делать проверки для того диапазона, в котором вы находитесь, затем следовать из этого.

...