2010-06-24 7 views
1

est ici une fonction que j'ai écrit pour imprimer tous les chiffres d'un nombre flottant en F #:F # + Net, erreur de calcul en utilisant la fonction System.Math.Floor

let rec TestFloor (fnum:float) = 
    let floor = System.Math.Floor(fnum) 
    printfn "fnum:%f floor:%f" fnum floor 
    if floor > 0.0 then TestFloor((fnum - floor) * 10.0) 

Quoi qu'il en soit le résultat est étrange, par exemple :

> TestFloor 1.23;; 
fnum:1.230000 floor:1.000000 
fnum:2.300000 floor:2.000000 
**fnum:3.000000 floor:2.000000** 
fnum:10.000000 floor:9.000000 
fnum:10.000000 floor:9.000000 
fnum:10.000000 floor:9.000000 
fnum:10.000000 floor:9.000000 
fnum:10.000000 floor:9.000000 
fnum:10.000000 floor:9.000000 
fnum:10.000000 floor:9.000000 
fnum:10.000000 floor:9.000000 
fnum:9.999998 floor:9.000000 
fnum:9.999982 floor:9.000000 
fnum:9.999822 floor:9.000000 
... 

À la quatrième ligne, l'étage 3.0 s'est avéré être 2.0 ce qui est étrange. Les calculs suivants sont tous faux. Je me demande ce qui se passe là-bas?

Merci!


Edit 2

@ sepp2k

Ceci est le résultat après l'utilisation% .30f:

> TestFloor 1.23;; 
fnum:1.230000000000000000000000000000 floor:1.000000000000000000000000000000 
fnum:2.300000000000000000000000000000 floor:2.000000000000000000000000000000 
**fnum:3.000000000000000000000000000000 floor:2.000000000000000000000000000000** 
fnum:9.999999999999980000000000000000 floor:9.000000000000000000000000000000 
fnum:9.999999999999820000000000000000 floor:9.000000000000000000000000000000 
fnum:9.999999999998220000000000000000 floor:9.000000000000000000000000000000 
fnum:9.999999999982240000000000000000 floor:9.000000000000000000000000000000 
fnum:9.999999999822360000000000000000 floor:9.000000000000000000000000000000 

Comme vous pouvez le voir à la quatrième ligne fnum est 3.00... et la valeur floor est 2.00..


Edit 3 - Résolu

Merci à tous, je comprends maintenant ce que le problème est sur le point.

Répondre

3

Ceci est juste un de ces problèmes d'arrondis que vous obtenez avec l'arithmétique à virgule flottante. Vous trouverez sa représentation 2.99999 ... probablement infiniment récurrente. L'appel à printfn arrondit clairement ceci à la représentation attendue, mais le plancher voit toujours ceci comme 2.99999 ...

C'est exactement le genre de problème que le type décimal existe pour résoudre, ainsi si nous réécrivons pour utiliser décimal, on obtient le résultat correct:

let rec TestFloor dnum = 
    let fl = floor dnum 
    printfn "fnum:%f floor:%f" dnum fl 
    if fl > 0.0M then TestFloor((dnum - fl) * 10.0M) 

Cela donne:

> TestFloor 1.23M;; 
fnum:1.230000 floor:1.000000 
fnum:2.300000 floor:2.000000 
fnum:3.000000 floor:3.000000 
fnum:0.000000 floor:0.000000 
val it : unit =() 

Bien sûr, vous pouvez coller avec flotteur, mais ajouter une valeur de tolérance très faible pour faire en sorte que ces cas de coin sont toujours un peu au-dessus de la valeur attendue, plutôt que juste un allumé ci-dessous, par exemple:

let rec TestFloor fnum = 
    let fl = floor (fnum + 0.00000000001) 
    printfn "fnum:%f floor:%f" fnum fl 
    if fl > 0.0 then TestFloor((fnum - fl) * 10.0) 

Qui donne le même résultat que ci-dessus.

+1

Merci Chris, maintenant je comprends comment et pourquoi! –

1

Vraisemblablement, la valeur de fnum est quelque chose comme 2.9999999999999999999 et printfn arrondit juste à 3.0 lors de l'affichage. Essayez d'augmenter le nombre de chiffres affichés avec quelque chose comme %.30f au lieu de %f.

+0

OK, voici le résultat en utilisant% .30f: > TestFloor 1,23 ;; fNum: 1,230000000000000000000000000000 étage: 1,000000000000000000000000000000 fNum: 2,300000000000000000000000000000 étage: 2,000000000000000000000000000000 fNum: 3,000000000000000000000000000000 étage: 2,000000000000000000000000000000 ... comme vous pouvez le voir, quand fNum = 3.00 ..., le sol est de 2,00 ... –

+0

désolé pour le format, je le posterai encore. –

+0

@ Eric.Par: Essayez encore plus de chiffres. – sepp2k

1

Même si vous obtenez 3.00000 ... avec une précision décimale maximale, il se peut qu'il y ait toujours un arrondi. N'oublions pas que la valeur est stockée en binaire, et un arrondi est toujours nécessaire pour l'impression (binaire à décimal). La réponse est, bien que votre valeur imprime comme 3.0000 ..., il est effectivement moins de 3. L'addition de plus de chiffres décimaux ne le coupera pas.

Considérez ce code:

> let num = 3.0 - 2.**(-51.);; 
> printfn "%.30f" num;; 
3.000000000000000000000000000000 
> floor num;; 
val it : float = 2.0