Division by zero prevention template not functioning

I have this template that attempts to prevent division by zero at the roll-over date when sensor.days_used == 0:

      value_template: >
        {% if states('sensor.days_used') not in ['0', 'unavailable', 'none', 'unknown' ] %}
          {% set gb_per_day = states('sensor.used_gb')|float / states('sensor.days_used')|float %}
          {% set remaining_gb = 500 - states('sensor.used_gb')|float %}
          {% set proj_days_left = remaining_gb / gb_per_day %}
          {% set proj_end_date = ( as_timestamp( states('sensor.date') ) + proj_days_left * 86400 )|timestamp_custom('%-d %b %Y') %}
          {{ proj_end_date }}
        {% else %}
          N/A
        {% endif %}

I still get division by zero errors when sensor.days_used == 0. Why?

Logger: homeassistant.components.template.template_entity
Source: components/template/template_entity.py:71
Integration: template (documentation, issues)
First occurred: 11 January 2021, 18:00:00 (1 occurrences)
Last logged: 11 January 2021, 18:00:00
TemplateError('ZeroDivisionError: float division by zero') while processing template 'Template("{% if states('sensor.days_used') not in [ '0', 'unavailable', 'none', 'unknown' ] %} {% set gb_per_day = states('sensor.used_gb')|float / states('sensor.days_used')|float %} {% set remaining_gb = 500 - states('sensor.used_gb')|float %} {% set proj_days_left = remaining_gb / gb_per_day %} {% set proj_end_date = ( as_timestamp( states('sensor.date') ) + proj_days_left * 86400 )|timestamp_custom('%-d %b %Y') %} {{ proj_end_date }} {% else %} N/A {% endif %}")' for attribute '_state' in entity 'sensor.projected_end' 

The state should be a string and thus should require the quotes around 0 in the list.

Iā€™ll try it this way and see what happens next month.

      value_template: >
        {% if states('sensor.days_used') not in ['unavailable', 'none', 'unknown' ] and states('sensor.days_used')|int != 0 %}
          {% set gb_per_day = states('sensor.used_gb')|float / states('sensor.days_used')|float %}
          {% set remaining_gb = 500 - states('sensor.used_gb')|float %}
          {% set proj_days_left = remaining_gb / gb_per_day %}
          {% set proj_end_date = ( as_timestamp( states('sensor.date') ) + proj_days_left * 86400 )|timestamp_custom('%-d %b %Y') %}
          {{ proj_end_date }}
        {% else %}
          N/A
        {% endif %}

Does it work i the template editor?

I put your template in the editor and substituted days_used with a sensor of mine that had a state of 0 and it worked as expected.

Yeah I see the same thing. However while searching for a sensor with state 0, I saw a few with 0.0 as their state. I wonder if thatā€™s the issue?

sensor.days_used is currently 1 not 1.0, so Iā€™m not too sure this is the problem. Iā€™ll know in 30 days.

Unless some SQL wiz can help me extract the value of sensor.days_used on the 11th of Jan 2021 from mariaDB. Scratch that. I donā€™t store it.

Iā€™m not sure I can explain why, but I thought I ran across something similar at one time. I donā€™t think the second float is being applied the way most of us would think. I will try to find the case I had. So what I think is happening is that you are getting a float divided by a string (which is zero), it does not matter what is in states(ā€˜sensor.days_usedā€™) . Weird I know.

(states('sensor.days_used')|float)

might fix it.

The wording of the error might oddly be telling:

TemplateError('ZeroDivisionError: float division by zero')

Have you tried comparing against the number 0?

{% if states('sensor.days_used') not in [0, 'unavailable', 'none', 'unknown' ] %}

David, if the state is 0 the template should be skipped by the if test. So nothing to do with the actual template evaluation.

No, because states are strings. Always.

1 Like

I believe you should be able to test it by forcing the sensor from 1 to 0 in the states tab using ā€œset stateā€. It will get updated again to the correct value but it should pop up long enough to test.

Yeah but by doing that weā€™re just repeating the previous test by forcing it to 0. What if it is actually 0.0?

Iā€™ll check anyway.

How about replacing the if with this?

{% if (states('sensor.days_used') | float) > 0 %}

This seems to work for me (at least testing in the Developer Tools), as the float filter returns 0 if the string is not numeric.

1 Like

Yep, thatā€™s a great idea. Thanks.

Edit: though you do have an errant ā€œ)ā€

Because all states are strings, I think you can run into problems trying to cast them to a float (or int) without some other checks. You can use a default value in the filter to identify something that is not really a number (I used an existing dark sky sensor because its state is a string that canā€™t be cast to a float):

{% set sensor_state = states('sensor.dark_sky_summary')|float(-999) %}

If I put this in developer/template editor, it returns the value -999 which you can easily check for. Of course, you can check for a zero in this case, too - I just always seem to default to a big negative number to tell me that it canā€™t be cast to a numberā€¦

If I leave the default value out, as in:

{% set sensor_state = states('sensor.dark_sky_summary')|float %}

it will return a 0.0, which you can also check for.

The complete template would then look like (I also used a set statement for the numerator to make sure it isnā€™t something unexpected - and you need gb_per_day to be something other than 0.0 or else youā€™ll get a division by zero error in the line starting with {% set proj_days_left =...):

      value_template: >
        {% set days_used = states('sensor.days_used')|float(-999) %}
        {% set used_gb   = states('sensor.used_gb')|float(-999) %}
        {% if sensor_state > 0 and used_gb > 0 %}
          {% set gb_per_day = used_gb / days_used %}
          {% set remaining_gb = 500 - used_gb %}
          {% set proj_days_left = remaining_gb / gb_per_day %}
          {% set proj_end_date = ( as_timestamp( states('sensor.date') ) + proj_days_left * 86400 )|timestamp_custom('%-d %b %Y') %}
          {{ proj_end_date }}
        {% else %}
          N/A
        {% endif %}

The solution @Steven_Rollason proposed will provide essentially the same thing except that Iā€™d add the check for the used_gb to prevent an error in the proj_days_left calculation (in the event used_gb is actually zero).

@tom_l
Here is the best explanation in the docs I could find. Not a great explainer, but if grok the idea of Operator Precedence, and then understand that the | filter operator comes in last it should be clear to you. Good hunting!

https://svn.python.org/projects/external/Jinja-1.1/docs/build/builtins.html
note

The filter operator has a pretty low priority. If you want to add fitered values you have to put them into parentheses. The same applies if you want to access attributes or return values:

correct:
    {{ (foo|filter) + (bar|filter) }}
wrong:
    {{ foo|filter + bar|filter }}

correct:
    {{ (foo|filter).attribute }}
wrong:
    {{ foo|filter.attribute }}

Just noticed your own proposed solution, @tom_l, at the end of your original post that adds and states('sensor.days_used')|int != 0 to the if statement. That will work, too. But Iā€™d still add the check for used_gb.

Just use an availability template and refactor a bit to avoid startup errors.

value_template: >
  {% set used = states('sensor.used_gb')|float %}
  {% set days = states('sensor.days_used')|float %}
  {% set remaining = 500 - used %}
  {% if 0 not in [ used, days ] %}
    {% set days_left = remaining / (used / days) %}
    {{ as_timestamp( states('sensor.date') ) + days_left * 86400 ) | timestamp_custom('%-d %b %Y') }}
  {% endif %}
availability_template: >
  {{ 0 not in [ states('sensor.used_gb')|float, states('sensor.days_used')|float ] }}

The reason you were getting divide by zero errors is not because states('sensor.days_used') was zero. It was because states('sensor.used_gb') was zero on day 1. This caused your second line ({% set proj_days_left = remaining_gb / gb_per_day %}) to divide by zero because your gb_per_day was zero. So your first if statement passed but you didnā€™t check the second divisor.

Just to expand on the math on day 1:

used will equal 0. days will equal 1. remaining will equal 500.

used_gb will equal to used (0) / days (1) which equals 0.

project_days_letf will be remaining (500) / used_gb (0) <-- This is where your error is happening.

Wow - I just learned something! I didnā€™t know about the availability_template. Thanks, @petro! Havenā€™t tried it yet in my own stuff, but certainly will - so that can go in something like a condition section? The only mention I can find in the docs is for a sensor

Itā€™s only used for template integrations, letting the system know if the integration is available or not.

1 Like

Got it. Thanks!

Thank you. Makes perfect sense.

Edit: Just FYI

There is a missing ā€œ(ā€ in this:

  {{ as_timestamp( states('sensor.date') ) + days_left * 86400 ) | timestamp_custom('%-d %b %Y') }}

Should be:

  {{ ( as_timestamp( states('sensor.date') ) + days_left * 86400 ) | timestamp_custom('%-d %b %Y') }}
1 Like

Godamn. Itā€™s happening again. Different sensor:

- platform: template
  sensors:
    pv_array_mismatch:
      friendly_name: 'PV Array Mismatch'
      value_template: "{{ (100 * (states('sensor.sma_pv_power_a')|float - states('sensor.sma_pv_power_b')|float) / states('sensor.sma_pv_power_a')|float )|round(2) }}"
      unit_of_measurement: '%'
      availability_template: "{{ states('sensor.sma_pv_power_a')|float != 0 }}"
      icon_template: "{{'mdi:solar-panel' }}"

Why is the availability template not preventing this?

Logger: homeassistant.components.template.template_entity
Source: components/template/template_entity.py:72
Integration: Template (documentation, issues)
First occurred: 14:28:30 (1 occurrences)
Last logged: 14:28:30

TemplateError('ZeroDivisionError: float division by zero') while processing template 'Template("{{ (100 * (states('sensor.sma_pv_power_a')|float - states('sensor.sma_pv_power_b')|float) / states('sensor.sma_pv_power_a')|float )|round(2) }}")' for attribute '_attr_state' in entity 'sensor.pv_array_mismatch'

Why do I have to do this?

- platform: template
  sensors:
    pv_array_mismatch:
      friendly_name: 'PV Array Mismatch'
      value_template: >
        {% if is_state('sensor.sma_pv_power_a', '0') %}
          unavailable
        {% else %}
          {{ (100 * (states('sensor.sma_pv_power_a')|float - states('sensor.sma_pv_power_b')|float) / states('sensor.sma_pv_power_a')|float )|round(2) }}
        {% endif %}
      unit_of_measurement: '%'
      availability_template: "{{ states('sensor.sma_pv_power_a')|float != 0 }}"
      icon_template: "{{'mdi:solar-panel' }}"

Wait. Even that doesnā€™t work.

homeassistant.exceptions.TemplateError: ZeroDivisionError: float division by zero
2021-08-02 14:38:35 ERROR (MainThread) [homeassistant.components.template.template_entity] TemplateError('ZeroDivisionError: float division by zero') while processing template 'Template("{% if is_state('sensor.sma_pv_power_a', '0') %}
unavailable
{% else %}
{{ (100 * (states('sensor.sma_pv_power_a')|float - states('sensor.sma_pv_power_b')|float) / states('sensor.sma_pv_power_a')|float )|round(2) }}
{% endif %}")' for attribute '_attr_state' in entity 'sensor.pv_array_mismatch'

Random thought, but years of programming have taught me a lesson about equality comparison of floats :wink:

Probably wonā€™t change a thing, but probably better anyway to do a is_state or conversion to int in the availability template, if possible.

1 Like