Repository: streamlit/streamlit

Issue:
Certain float values not allowed with select_slider
### Summary

Certain values are not allowed when defining `select_slider` of floats due to float precision errors.

### Steps to reproduce

Code snippet:

```
import numpy as np
import streamlit as st


st.select_slider(
    label="Select slider of floats",
    options=np.arange(0.0, 0.25, 0.05),
    value=(0.1, 0.15),
)
```

**Expected behavior:**

A `select_slider` widget where options are float numbers: `[0.0, 0.05, 0.1, 0.15, 0.2]`, and default values are float numbers: `[0.1, 0.15]`.

**Actual behavior:**

The following traceback:

```
2022-04-28 21:47:19.181 Traceback (most recent call last):
  File "/home/rguedes/.virtualenvs/streamlit/lib/python3.8/site-packages/streamlit/scriptrunner/script_runner.py", line 443, in _run_script
    exec(code, module.__dict__)
  File "/source/streamlit/app-report-port-taranaki/app/test.py", line 5, in <module>
    st.select_slider(
  File "/home/rguedes/.virtualenvs/streamlit/lib/python3.8/site-packages/streamlit/elements/select_slider.py", line 122, in select_slider
    return self._select_slider(
  File "/home/rguedes/.virtualenvs/streamlit/lib/python3.8/site-packages/streamlit/elements/select_slider.py", line 180, in _select_slider
    slider_value = as_index_list(value)
  File "/home/rguedes/.virtualenvs/streamlit/lib/python3.8/site-packages/streamlit/elements/select_slider.py", line 164, in as_index_list
    slider_value = [index_(opt, val) for val in v]
  File "/home/rguedes/.virtualenvs/streamlit/lib/python3.8/site-packages/streamlit/elements/select_slider.py", line 164, in <listcomp>
    slider_value = [index_(opt, val) for val in v]
  File "/home/rguedes/.virtualenvs/streamlit/lib/python3.8/site-packages/streamlit/util.py", line 130, in index_
    raise ValueError("{} is not in iterable".format(str(x)))
ValueError: 0.15 is not in iterable
```

Inspection from [utils.py](https://github.com/streamlit/streamlit/blob/develop/lib/streamlit/util.py#L110-L129) shows that `x` does not match values in `iterable` due to float precision:

```
> /home/rguedes/.virtualenvs/streamlit/lib/python3.8/site-packages/streamlit/util.py(130)index_()
    129     import ipdb; ipdb.set_trace()
--> 130     raise ValueError("{} is not in iterable".format(str(x)))
    131 

ipdb> p x
0.15
ipdb> p iterable
[0.0, 0.05, 0.1, 0.15000000000000002, 0.2]
```
### Is this a regression?

Don't know

### Debug info

- Streamlit version: 1.8.1
- Python version: 3.8.10
- Using Python virtualenv
- OS version: Ubuntu 20.04
- Browser version: Google Chrome 100.0.4896.127



Discussion:
Hi @rafa-guedes! 😃 
Thanks for your submission - I was able to confirm the float issue with `st.select_slider`.
We are continually looking for ways & areas to improve Streamlit, and appreciate submissions like yours that help us focus our roadmap where people feel the most pain. We will take a look at how we can incorporate this work into our future development plans.


Resolving patch:
diff --git a/lib/streamlit/util.py b/lib/streamlit/util.py
--- a/lib/streamlit/util.py
+++ b/lib/streamlit/util.py
@@ -18,6 +18,7 @@
 import hashlib
 import os
 import subprocess
+import numpy as np
 
 from typing import Any, Dict, List, Mapping, TypeVar
 from typing_extensions import Final
@@ -26,6 +27,7 @@
 
 # URL of Streamlit's help page.
 HELP_DOC: Final = "https://docs.streamlit.io/"
+FLOAT_EQUALITY_EPSILON: Final = 0.000000000005
 
 
 def memoize(func):
@@ -126,6 +128,11 @@ def index_(iterable, x) -> int:
     """
 
     for i, value in enumerate(iterable):
+        # https://stackoverflow.com/questions/588004/is-floating-point-math-broken
+        # https://github.com/streamlit/streamlit/issues/4663
+        if isinstance(iterable, np.ndarray):
+            if abs(x - value) < FLOAT_EQUALITY_EPSILON:
+                return i
         if x == value:
             return i
     raise ValueError("{} is not in iterable".format(str(x)))


Test patch:
diff --git a/lib/tests/streamlit/util_test.py b/lib/tests/streamlit/util_test.py
--- a/lib/tests/streamlit/util_test.py
+++ b/lib/tests/streamlit/util_test.py
@@ -15,6 +15,7 @@
 import random
 import unittest
 from unittest.mock import patch
+import numpy as np
 
 from parameterized import parameterized
 
@@ -90,3 +91,30 @@ def f():
     def test_lower_clean_dict_keys(self, input_dict, answer_dict):
         return_dict = util.lower_clean_dict_keys(input_dict)
         self.assertEqual(return_dict, answer_dict)
+
+    @parameterized.expand(
+        [
+            (np.array([1, 2, 3, 4, 5]), 5, 4),
+            # This one will have 0.15000000000000002 because of floating point precision
+            (np.arange(0.0, 0.25, 0.05), 0.15, 3),
+            ([0, 1, 2, 3], 3, 3),
+            ([0.1, 0.2, 0.3], 0.2, 1),
+            (["He", "ello w", "orld"], "He", 0),
+        ]
+    )
+    def test_successful_index_(self, input, find_value, expected_index):
+        actual_index = util.index_(input, find_value)
+        self.assertEqual(actual_index, expected_index)
+
+    @parameterized.expand(
+        [
+            (np.array([1, 2, 3, 4, 5]), 6),
+            (np.arange(0.0, 0.25, 0.05), 0.1500002),
+            ([0, 1, 2, 3], 3.00001),
+            ([0.1, 0.2, 0.3], 0.3000004),
+            (["He", "ello w", "orld"], "world"),
+        ]
+    )
+    def test_unsuccessful_index_(self, input, find_value):
+        with self.assertRaises(ValueError):
+            util.index_(input, find_value)