Skip to main content
Engineering LibreTexts

15.1: Never Test Equality on Floats

  • Page ID
    43750
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    The first basic principle is to never compare float equality. Let’s take a simple case: the addition of two floats may not be equal to the float representing their sum. For example 0.1 + 0.2 is not equal to 0.3.

    (0.1 + 0.2) = 0.3
        → false
    

    Hey, this is unexpected, you did not learn that in school, did you? This behavior is surprising indeed, but it’s normal since floats are inexact numbers. What is important to understand is that the way floats are printed is also influencing our understanding. Some approaches print a simpler representation of reality than others. In early versions of Pharo printing 0.1 + 0.2 were printing 0.3, now it prints 0.30000000000000004. This change was guided by the idea that it is better not to lie to the user. Showing the inexactness of a float is better than hiding it because one day or another we can be deeply bitten by them.

    (0.2 + 0.1) printString
        → '0.30000000000000004'
    
    0.3 printString
        → '0.3'
    

    We can see that we are in presence of two different numbers by looking at the hexadecimal values.

    (0.1 + 0.2) hex
        → '3FD3333333333334'
    0.3 hex
        → '3FD3333333333333'
    

    The method storeString also conveys that we are in presence of two different numbers.

    (0.1 + 0.2) storeString
        → '0.30000000000000004'
    0.3 storeString
        → '0.3'
    

    About closeTo: One way to know if two floats are probably close enough to look like the same number is to use the message closeTo:

    (0.1 + 0.2) closeTo: 0.3
        → true
    0.3 closeTo: (0.1 + 0.2)
        → true
    

    The method closeTo: verify that the two compared numbers have less than 0.0001 of difference. Here is its source code.

    closeTo: num
        "are these two numbers close?"
        num isNumber ifFalse: [^[self = num] ifError: [false]].
        self = 0.0 ifTrue: [^num abs < 0.0001].
        num = 0 ifTrue: [^self abs < 0.0001].
        ^self = num asFloat
            or: [(self - num) abs / (self abs max: num abs) < 0.0001]
    

    About Scaled Decimals. There is a solution if you absolutely need exact floating point numbers: Scaled Decimals. They are exact numbers so they exhibit the behavior you expected.

    0.1s2 + 0.2s2 = 0.3s2
        → true
    

    Now, if you execute the following line, you will see that the expressions are not equals.

    (0.1 asScaledDecimal: 2) + (0.2 asScaledDecimal: 2) = (0.3 asScaledDecimal: 2)
        → false
    

    What is different ? When you execute 0.1s2 for example, a fraction is created from scratch, hence 0.1s2 asFraction returns 1/10. The message asScaledDecimal: has a different behavior. (0.1 asScaledDecimal: 2) represents exactly the same number as the float 0.1. (0.1 asScaledDecimal: 2) asFraction returns the value 0.1 asFraction. Since 0.1 + 0.2 = 0.3 returns false, you would of course expect the scaled decimal of this expression to return false.


    This page titled 15.1: Never Test Equality on Floats is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by Alexandre Bergel, Damien Cassou, Stéphane Ducasse, Jannik Laval (Square Bracket Associates) via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.