Close Menu
GeekBlog

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    Trump Renamed AI “Super Intelligence.” The Word Already Meant Something Else.

    October 1, 2026

    Pixel Watch 2 Support Ends Today: What You Lose and What to Do Next

    October 1, 2026

    Singapore Built a Dating App That Pays for Your First Date. There Is a Catch About Who Can Use It.

    October 1, 2026
    Facebook
    GeekBlog
    • Home
    • Mobile
    • Tech News
    • Blog
    • Gaming
    • Smartwatch
    • How-To Guides
    • AI & Software
    Facebook
    GeekBlog
    Home»Blog»How to Compare Float Numbers in Jinja2 (Without Getting Bitten by Strings)
    Blog

    How to Compare Float Numbers in Jinja2 (Without Getting Bitten by Strings)

    Ethan CaldwellBy Ethan CaldwellSeptember 9, 202612 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Email Copy Link
    Close up of a calculator display with decimal numbers for comparing floats in Jinja2
    Share
    Facebook Twitter LinkedIn Pinterest Email Copy Link

    To compare float numbers in Jinja2, cast both sides with the float filter before using >, < or ==, because values that arrive from YAML, environment variables, form input or Ansible extra vars are very often strings, and Jinja2 compares strings lexically rather than numerically. For equality checks, compare the absolute difference against a small tolerance instead of using == directly, exactly as you would in Python.

    Quick answer: Use {% if value | float > 1.5 %} for ordering, and {% if (a | float - b | float) | abs < 0.0001 %} for equality. Add a default, value | float(0), when the variable might be empty or malformed. Never rely on version_compare for floats.

    This article covers the string versus number trap, how the float and round filters behave, tolerance comparisons, the differences between plain Jinja2 and Ansible’s Jinja2, and how to format the result once you have it. If you first need to confirm a variable is actually set before comparing it, the guide on checking whether a variable exists in Jinja2 pairs well with this one.

    Why float comparisons go wrong in Jinja2

    Jinja2 is a Python templating engine, and it inherits Python’s comparison semantics. In Python, "10.5" > "9.2" is False, because string comparison looks at the first character and "1" sorts before "9". Jinja2 does exactly the same thing when both operands are strings. When one operand is a string and the other is a number, Python 3 raises a TypeError, and Jinja2 surfaces that as a template rendering error.

    The reason this bites people is that most data reaching a template is text. A few common sources:

    • Environment variables are always strings. os.environ["THRESHOLD"] gives you "0.75", not 0.75.
    • Ansible extra vars passed with -e key=value on the command line are strings unless you use JSON or YAML syntax.
    • Inventory host variables in INI format inventories are strings.
    • Command output captured with register is a string in stdout.
    • Web form input in Flask or Django is a string.
    • YAML values with quotes such as ratio: "1.5" are strings. Unquoted ratio: 1.5 is a float.

    Here is a minimal reproduction using plain Jinja2 in Python:

    from jinja2 import Template
    
    t = Template("{{ 'yes' if load > threshold else 'no' }}")
    print(t.render(load="10.5", threshold="9.2"))   # prints: no   (string comparison)
    print(t.render(load=10.5, threshold=9.2))       # prints: yes  (numeric comparison)
    
    t2 = Template("{{ 'yes' if load > threshold else 'no' }}")
    print(t2.render(load="10.5", threshold=9.2))    # TypeError: '>' not supported between str and float

    The float filter

    The fix is the built in float filter. It converts the value with Python’s float() and, crucially, it does not raise on bad input. If the conversion fails it returns a default, which is 0.0 unless you pass another value.

    {# Both sides cast, works whether the inputs are strings or numbers #}
    {% if load | float > threshold | float %}
    High load
    {% endif %}
    
    {# Explicit default when the variable may be empty or garbage #}
    {% if cpu_pct | float(0) >= 90 %}
    Alert
    {% endif %}
    
    {# Filter binds tighter than the comparison operator, so this is (a|float) > (b|float) #}
    {{ a | float > b | float }}

    Two things to keep in mind. First, float applied to an undefined variable follows the same rule as any other filter: with the default Undefined class it converts to the default value, but with StrictUndefined (which Ansible uses) it raises. Guard with default first when the variable may not exist: {{ x | default(0) | float }}. Second, the filter is silent about failure. "abc" | float quietly becomes 0.0, so if 0 is a meaningful value in your logic, validate the input elsewhere or use a sentinel default such as float(-1).

    Tip: Cast at the boundary, not in every expression. In Ansible, do set_fact: threshold_f: "{{ threshold | float }}" once and compare the fact afterward. In Flask, convert in the view and pass a real float into render_template.

    Recommended for you:

    How to Embed TikTok Videos on a Website or App
    Blog·Sep 9, 2026

    How to Embed TikTok Videos on a Website or App

    Equality: compare with a tolerance

    Even when both sides are genuine floats, == is unreliable for values that came out of arithmetic. 0.1 + 0.2 is not exactly 0.3 in IEEE 754 binary floating point, and Jinja2 will happily tell you so. The standard answer is to check whether the absolute difference is below a tolerance you choose based on the precision your data actually has.

    {% set a = 0.1 + 0.2 %}
    {% set b = 0.3 %}
    
    {{ a == b }}                              {# False #}
    {{ (a - b) | abs < 0.000001 }}            {# True #}
    
    {# Reusable pattern with string inputs #}
    {% set eps = 0.0001 %}
    {% if (measured | float - expected | float) | abs < eps %}
    Within spec
    {% else %}
    Out of spec by {{ (measured | float - expected | float) | abs }}
    {% endif %}

    Jinja2 ships abs as a built in filter, so nothing extra is needed. If you prefer relative tolerance for numbers that span several orders of magnitude, express it in the template: (a - b) | abs <= rel * ([a | abs, b | abs] | max). The max filter accepts a list, which is why the two values are wrapped in brackets.

    Rounding as a comparison tool

    A second way to sidestep float noise is to round both sides to the precision you care about and compare the results. The round filter takes the number of decimal places and a method: common (the default, rounds half up), ceil or floor.

    {{ 3.14159 | round(2) }}          {# 3.14 #}
    {{ 3.14159 | round(2, 'ceil') }}  {# 3.15 #}
    {{ 42.55 | round }}               {# 43.0, still a float #}
    {{ 42.55 | round | int }}         {# 43, now an integer #}
    
    {% if price | float | round(2) == target | float | round(2) %}
    Prices match to the cent
    {% endif %}

    Note that round returns a float, not an integer, so 42.55 | round renders as 43.0. Chain | int if you want a whole number. Also note that the common method calls Python’s own round(), which rounds ties to the nearest even digit and is subject to the usual binary representation quirks, so values sitting exactly on a half may not round the way a spreadsheet would. If that matters, round both sides the same way and compare, and the quirk cancels out.

    Ansible specifics

    Ansible uses Jinja2 for every templated value, but with two twists that matter here. The first is that until a task actually evaluates the expression, everything in a playbook is text, and Ansible then attempts to convert results back to native types. The second is that Ansible exposes extra filters and tests, and one of them, version_compare (aliased as the version test), is regularly misused for floats.

    version_compare is not a float comparison

    version compares dotted version strings segment by segment. Under that rule "1.10" is greater than "1.9", which is correct for versions and wrong for decimals. Use it for software versions only:

    # Correct: version strings
    - name: Require a recent kernel
      ansible.builtin.assert:
        that: ansible_kernel is version('5.15', '>=')
    
    # Wrong for decimals: '1.10' is version 1.10, not one point one
    - ansible.builtin.debug:
        msg: "{{ '1.10' is version('1.9', '>') }}"   # True, because it is a version test
    
    # Correct for decimals
    - ansible.builtin.debug:
        msg: "{{ '1.10' | float > '1.9' | float }}"    # False, 1.1 is less than 1.9

    Typical playbook pattern

    - name: Read disk usage percentage
      ansible.builtin.shell: df --output=pcent / | tail -1 | tr -dc '0-9.'
      register: disk_pct
      changed_when: false
    
    - name: Store as a float once
      ansible.builtin.set_fact:
        disk_pct_f: "{{ disk_pct.stdout | float(0) }}"
    
    - name: Fail when usage is above the limit
      ansible.builtin.fail:
        msg: "Disk at {{ disk_pct_f }}%, limit is {{ disk_limit }}%"
      when: disk_pct_f | float > disk_limit | float

    Notice the second | float in the when clause even though the fact was already cast. A set_fact value goes through templating and may come back as a string depending on the Ansible version and whether native Jinja2 types are enabled, so casting again in the comparison costs nothing and removes the ambiguity. The Ansible tests documentation describes the version test and its operators.

    Comparison approaches side by side

    GoalExpressionNotes
    Greater or less thana | float > b | floatSafe for strings and numbers alike
    Equality with tolerance(a | float - b | float) | abs < 0.0001Pick the tolerance from your data’s precision
    Equality to N decimalsa | float | round(2) == b | float | round(2)Readable for money and percentages
    Missing or empty inputx | default(0) | floatRequired under StrictUndefined (Ansible)
    Software versionsv is version('2.10', '>=')Ansible only, never for decimals
    Range check0.0 <= x | float <= 1.0Chained comparisons work as in Python

    Formatting the result

    Once a comparison passes, you usually want to print the number. Jinja2 supports Python’s format mini language through the format filter and through the % operator, and both accept float precision specifiers.

    {{ "%.2f" | format(ratio | float) }}       {# 0.75 #}
    {{ "%.1f%%" | format(pct | float) }}        {# 87.5% #}
    {{ "{:,.2f}".format(total | float) }}       {# 12,345.68 with thousands separator #}
    {{ ratio | float | round(3) }}              {# 0.746 #}

    Formatting is also a cheap way to see what type you are dealing with. If {{ x }} renders 1.5 but {{ x | float | round(1) }} renders something different, the value was a string containing whitespace or a stray character. For messier inputs, the article on regular expressions in Jinja2 shows how to strip everything except digits and the decimal point before casting. And if you want to wrap the whole comparison in a reusable block, see how to wrap a block in Jinja2 for macro and call block patterns.

    Note: Locale aware inputs such as "1,5" (comma decimal separator) convert to 0.0 with the float filter. Replace the comma first: {{ x | replace(',', '.') | float }}.

    Troubleshooting

    TypeError: ‘>’ not supported between instances of ‘str’ and ‘float’

    One side is a string. Apply | float to both operands. In Ansible, the offending value is almost always an extra var, a registered stdout, or an INI inventory variable.

    The comparison silently picks the wrong branch

    Both sides are strings, so lexical comparison ran without an error. "10" < "9" is the classic symptom. Same fix: cast both sides.

    Value is always 0 after the float filter

    The input was not parseable, so the filter returned its default. Print the raw value with {{ x | pprint }} to see hidden whitespace, quotes, units such as % or MB, or a comma decimal separator, then clean it with trim, replace or regex_replace before casting.

    Recommended for you:

    How to Use the TikTok for Developers Documentation
    Blog·Sep 9, 2026

    How to Use the TikTok for Developers Documentation

    Equality passes in Python but fails in the template

    You are comparing a float to a string representation, for example 1.5 == "1.5", which is False in Python and therefore in Jinja2. Cast the string side, or better, compare with a tolerance.

    Ansible says the fact is a string even after set_fact with float

    Templated values are rendered to text unless native Jinja2 types are in effect. Cast again in the comparison (fact | float) rather than trusting the stored type. That pattern works on every Ansible version.

    Frequently asked questions

    Does Jinja2 have a built in float type check?

    Jinja2 provides the number test, which is true for integers and floats, and the float and integer tests for the specific types. Ansible adds these too. Use {% if x is float %} to branch on type, but remember a numeric string still fails that test.

    Why does 0.1 + 0.2 == 0.3 return False in a template?

    Because Jinja2 evaluates arithmetic with Python floats, and those are binary IEEE 754 values that cannot represent 0.1 or 0.2 exactly. The sum lands a tiny fraction above 0.3. Compare the absolute difference to a small tolerance, or round both values to the precision you care about.

    Can I compare a float to an integer in Jinja2?

    Yes. Python compares ints and floats numerically, so 1.0 == 1 is true and 2.5 > 2 is true. The only problem case is when either operand is a string. Cast strings with the float or int filter and the comparison behaves as expected.

    What is the difference between float and int filters for comparisons?

    int truncates toward zero and turns "2.9" into 2, which changes the outcome of a comparison. Use float whenever the data can carry a fractional part, and reserve int for counts and identifiers. Both accept a default argument for unparseable input.

    Is version_compare ever appropriate for numbers?

    Only for dotted version strings such as 2.10.1, where each segment is an independent integer. For decimals it produces wrong answers, treating 1.10 as larger than 1.9. For ordinary floats, always cast with the float filter and use standard comparison operators.

    The bottom line

    Float comparison in Jinja2 is Python float comparison with one extra hazard: your inputs are usually strings. Cast both sides with | float, give it a default when the variable may be empty, and the ordering operators work exactly as you expect.

    For equality, skip == and compare the absolute difference to a tolerance, or round both sides to a fixed number of decimals. Keep version_compare for version strings, and do your casting once at the boundary so the rest of the template stays readable.

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Telegram Email Copy Link
    Previous ArticleHow to Wrap a Block in Jinja2 (super, call Blocks, Filters and Conditional Wrappers)
    Next Article How to Use the Google Analytics Debugger to Verify GA4 Events
    Ethan Caldwell

      Ethan Caldwell is GeekBlog's resident Apple specialist, covering the entire Apple ecosystem: iPhone, iPad, Mac, Apple Watch, AirPods and the software that ties them together. A longtime iOS user and gadget collector, Ethan tracks Cupertino's every move, breaking down Apple keynotes, A- and M-series chip benchmarks, iOS feature updates and the rumor mill into clear, practical takes that help readers decide whether the latest Apple hardware is worth the upgrade.

      Related Posts

      10 Mins Read

      Google Ads Keyword Planner: How It Shows the Most Relevant Keywords

      11 Mins Read

      How to Use Remarketing Techniques for Better Conversions

      11 Mins Read

      How to Conduct A/B Testing for Marketing Campaigns

      10 Mins Read

      AMP for WP Plugin Vulnerability: What Was Fixed and What to Do

      11 Mins Read

      How to Create a Facebook Business Page in 2026

      11 Mins Read

      How to Convert GMT Time to Other Time Zones in C++

      Top Posts

      Best Free Online Music Apps in 2026

      July 7, 20263 Views

      Chromebook vs PC: Which One Should You Actually Buy?

      July 7, 20262 Views

      The Light Flip Wants to Sell You a Phone That Does Less

      July 28, 20261 Views
      Stay In Touch
      • Facebook

      Subscribe to Updates

      Get the latest tech news from FooBar about tech, design and biz.

      Most Popular

      How to Convert HEIC to JPG on iPhone, Mac, Android and Windows

      September 3, 20266 Views

      Gal Gadot’s Lawyers Spent Six Months on One AI Clause. Then SAG Called Them for Pointers.

      September 2, 20265 Views

      The Mesh Router Placement Strategy That Finally Gave Me Full Home Coverage

      September 9, 20263 Views
      Our Picks

      Trump Renamed AI “Super Intelligence.” The Word Already Meant Something Else.

      October 1, 2026

      Pixel Watch 2 Support Ends Today: What You Lose and What to Do Next

      October 1, 2026

      Singapore Built a Dating App That Pays for Your First Date. There Is a Catch About Who Can Use It.

      October 1, 2026

      Subscribe to Updates

      Get the latest creative news from FooBar about art, design and business.

      HEICJPG.online - Convert HEIC to JPG online
      Facebook
      • About Us
      • Contact us
      • Privacy Policy
      • Disclaimer
      • Terms and Conditions
      • Editorial Policy
      • Cookie Policy
      • Your Privacy Choices
      © 2026 GeekBlog

      Type above and press Enter to search. Press Esc to cancel.