Repository: keras-team/keras-tuner

Issue:
Revise the docstring for any hp with sampling="log", give code and concrete value examples
**Is your feature request related to a problem? Please describe.**
<!---
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-->

**Describe the solution you'd like**
<!---
A clear and concise description of what you want to happen.
-->

**Describe alternatives you've considered**
<!---
A clear and concise description of any alternative solutions or features you've considered.
-->

**Additional context**
<!---
Add any other context or screenshots about the feature request here.
-->


Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/keras-team/keras-tuner/pull/725?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) Report
> Merging [#725](https://codecov.io/gh/keras-team/keras-tuner/pull/725?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) (8f58cd5) into [master](https://codecov.io/gh/keras-team/keras-tuner/commit/ec2281dbc485cde5dde784a9be8e4742759b94ca?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) (ec2281d) will **decrease** coverage by `0.38%`.
> The diff coverage is `80.95%`.

> :exclamation: Current head 8f58cd5 differs from pull request most recent head 078fddf. Consider uploading reports for the commit 078fddf to get more accurate results

```diff
@@            Coverage Diff             @@
##           master     #725      +/-   ##
==========================================
- Coverage   92.29%   91.91%   -0.39%     
==========================================
  Files          36       36              
  Lines        2974     2995      +21     
==========================================
+ Hits         2745     2753       +8     
- Misses        229      242      +13     
```


| [Impacted Files](https://codecov.io/gh/keras-team/keras-tuner/pull/725?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team) | Coverage Δ | |
|---|---|---|
| [keras\_tuner/engine/hyperparameters.py](https://codecov.io/gh/keras-team/keras-tuner/pull/725/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team#diff-a2VyYXNfdHVuZXIvZW5naW5lL2h5cGVycGFyYW1ldGVycy5weQ==) | `89.40% <80.58%> (-2.17%)` | :arrow_down: |
| [keras\_tuner/tuners/bayesian.py](https://codecov.io/gh/keras-team/keras-tuner/pull/725/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team#diff-a2VyYXNfdHVuZXIvdHVuZXJzL2JheWVzaWFuLnB5) | `94.59% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keras-team)


Patch:
diff --git a/.gitignore b/.gitignore
index 3ddbb69..6963089 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+cov.xml
 *.swp
 .idea
 *.pyc
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..640bd89
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,5 @@
+coverage:
+  status:
+    patch:
+      default:
+        target: 100%
diff --git a/keras_tuner/engine/hyperparameters.py b/keras_tuner/engine/hyperparameters.py
index 24bd22f..db45caf 100644
--- a/keras_tuner/engine/hyperparameters.py
+++ b/keras_tuner/engine/hyperparameters.py
@@ -20,7 +20,6 @@ import copy
 import math
 import random
 
-import numpy as np
 import six
 from tensorflow import keras
 
@@ -28,32 +27,20 @@ from keras_tuner.engine import conditions as conditions_mod
 from keras_tuner.protos import keras_tuner_pb2
 
 
-def _check_sampling_arg(sampling, step, min_value, max_value, hp_type="int"):
-    if sampling is None:
-        return None
+def _check_sampling_arg(sampling, min_value, max_value):
     if min_value > max_value:
         raise ValueError(
-            "`sampling` `min_value` "
-            + str(min_value)
-            + " is greater than the `max_value` "
-            + str(max_value)
-        )
-    if hp_type == "int" and step != 1:
-        raise ValueError("`sampling` can only be set on an `Int` when `step=1`.")
-    if hp_type != "int" and step is not None:
-        raise ValueError(
-            "`sampling` and `step` cannot both be set, found "
-            "`sampling`: " + str(sampling) + ", `step`: " + str(step)
+            f"`min_value` {str(min_value)} is greater than "
+            f"the `max_value` {str(max_value)}."
         )
-
-    _sampling_values = {"linear", "log", "reverse_log"}
+    sampling_values = {"linear", "log", "reverse_log"}
     sampling = sampling.lower()
-    if sampling not in _sampling_values:
-        raise ValueError("`sampling` must be one of " + str(_sampling_values))
+    if sampling not in sampling_values:
+        raise ValueError(f"`sampling` must be one of {str(sampling_values)}")
     if sampling in {"log", "reverse_log"} and min_value <= 0:
         raise ValueError(
-            '`sampling="' + str(sampling) + '" is not supported for '
-            "negative values, found `min_value`: " + str(min_value)
+            f"`sampling='{str(sampling)}' does not support "
+            f"negative values, found `min_value`: {str(min_value)}."
         )
     return sampling
 
@@ -97,6 +84,16 @@ class HyperParameter(object):
         return self._default
 
     def random_sample(self, seed=None):
+        random_state = random.Random(seed)
+        prob = float(random_state.random())
+        return self.prob_to_value(prob)
+
+    def prob_to_value(self, prob):
+        """Convert cumulative probability in range [0.0, 1.0) to hp value."""
+        raise NotImplementedError
+
+    def value_to_prob(self, value):
+        """Convert a hp value to cumulative probability in range [0.0, 1.0)."""
         raise NotImplementedError
 
     @classmethod
@@ -179,9 +176,11 @@ class Choice(HyperParameter):
             return self.values[0]
         return self._default
 
-    def random_sample(self, seed=None):
-        random_state = random.Random(seed)
-        return random_state.choice(self.values)
+    def prob_to_value(self, prob):
+        return self.values[_prob_to_index(prob, len(self.values))]
+
+    def value_to_prob(self, value):
+        return _index_to_prob(self.values.index(value), len(self.values))
 
     def get_config(self):
         config = super(Choice, self).get_config()
@@ -223,23 +222,157 @@ class Choice(HyperParameter):
         )
 
 
-class Int(HyperParameter):
-    """Integer range.
+class Numerical(HyperParameter):
+    """Super class for all numerical type hyperparameters."""
+
+    def _sample_numerical_value(self, prob, max_value=None):
+        """Sample a value with the cumulative prob in the given range."""
+        if max_value is None:
+            max_value = self.max_value
+        if self.sampling == "linear":
+            return prob * (max_value - self.min_value) + self.min_value
+        elif self.sampling == "log":
+            return self.min_value * math.pow(max_value / self.min_value, prob)
+        elif self.sampling == "reverse_log":
+            return (
+                max_value
+                + self.min_value
+                - self.min_value * math.pow(max_value / self.min_value, 1 - prob)
+            )
+
+    def _numerical_to_prob(self, value, max_value=None):
+        """Convert a numerical value to range [0.0, 1.0)."""
+        if max_value is None:
+            max_value = self.max_value
+        if max_value == self.min_value:
+            # Center the prob
+            return 0.5
+        if self.sampling == "linear":
+            return (value - self.min_value) / (max_value - self.min_value)
+        if self.sampling == "log":
+            return math.log(value / self.min_value) / math.log(
+                max_value / self.min_value
+            )
+        if self.sampling == "reverse_log":
+            return 1.0 - math.log(
+                (max_value + self.min_value - value) / self.min_value
+            ) / math.log(max_value / self.min_value)
+
+    def _sample_with_step(self, prob):
+        """Sample a value with the cumulative prob in the given range.
+
+        The range is divided evenly by `step`. So only sampling from a finite set of
+        values. When calling the function, no need to use (max_value + 1) since the
+        function takes care of the inclusion of max_value.
+        """
+        if self.sampling == "linear":
+            # +1 so that max_value may be sampled.
+            n_values = (self.max_value - self.min_value) // self.step + 1
+            index = _prob_to_index(prob, n_values)
+            return self.min_value + index * self.step
+        if self.sampling == "log":
+            # +1 so that max_value may be sampled.
+            n_values = int(math.log(self.max_value / self.min_value, self.step)) + 1
+            index = _prob_to_index(prob, n_values)
+            return self.min_value * math.pow(self.step, index)
+        if self.sampling == "reverse_log":
+            # +1 so that max_value may be sampled.
+            n_values = int(math.log(self.max_value / self.min_value, self.step)) + 1
+            index = _prob_to_index(prob, n_values)
+            return (
+                self.max_value
+                + self.min_value
+                - self.min_value * math.pow(self.step, index)
+            )
+
+    def _to_prob_with_step(self, value):
+        """Convert to cumulative prob with step specified.
+
+        When calling the function, no need to use (max_value + 1) since the function
+        takes care of the inclusion of max_value.
+        """
+        if self.sampling == "linear":
+            index = (value - self.min_value) // self.step
+            # +1 so that max_value may be sampled.
+            n_values = (self.max_value - self.min_value) // self.step + 1
+            return _index_to_prob(index, n_values)
+        if self.sampling == "log":
+            index = math.log(value / self.min_value, self.step)
+            # +1 so that max_value may be sampled.
+            n_values = int(math.log(self.max_value / self.min_value, self.step)) + 1
+            return _index_to_prob(index, n_values)
+        if self.sampling == "reverse_log":
+            index = math.log(
+                (self.max_value - value + self.min_value) / self.min_value, self.step
+            )
+            # +1 so that max_value may be sampled.
+            n_values = int(math.log(self.max_value / self.min_value, self.step)) + 1
+            return _index_to_prob(index, n_values)
+
+
+class Int(Numerical):
+    """Integer hyperparameter.
 
     Note that unlike Python's `range` function, `max_value` is *included* in
     the possible values this parameter can take on.
 
+
+    Example #1:
+
+    ```py
+    hp.Int(
+        "n_layers",
+        min_value=6,
+        max_value=12)
+    ```
+
+    The possible values are [6, 7, 8, 9, 10, 11, 12].
+
+    Example #2:
+
+    ```py
+    hp.Int(
+        "n_layers",
+        min_value=6,
+        max_value=13,
+        step=3)
+    ```
+
+    `step` is the minimum distance between samples.
+    The possible values are [6, 9, 12].
+
+    Example #3:
+
+    ```py
+    hp.Int(
+        "batch_size",
+        min_value=2,
+        max_value=32,
+        step=2,
+        sampling="log")
+    ```
+
+    When `sampling="log"` the `step` is multiplied between samples.
+    The possible values are [2, 4, 8, 16, 32].
+
     Args:
         name: A string. the name of parameter. Must be unique for each
             `HyperParameter` instance in the search space.
         min_value: Integer, the lower limit of range, inclusive.
         max_value: Integer, the upper limit of range, inclusive.
-        step: Integer, the distance between two consecutive samples in the
-            range. Defaults to 1.
-        sampling: Optional string. One of "linear", "log", "reverse_log". Acts
-            as a hint for an initial prior probability distribution for how
-            this value should be sampled, e.g. "log" will assign equal
-            probabilities to each order of magnitude range.
+        step: Optional integer, the distance between two consecutive samples in the
+            range. If left unspecified, it is possible to sample any integers in
+            the interval. If `sampling="linear"`, it will be the minimum additve
+            between two samples. If `sampling="log"`, it will be the minimum
+            multiplier between two samples.
+        sampling: String. One of "linear", "log", "reverse_log". Defaults to
+            "linear". When sampling value, it always start from a value in range
+            [0.0, 1.0). The `sampling` argument decides how the value is
+            projected into the range of [min_value, max_value].
+            "linear": min_value + value * (max_value - min_value)
+            "log": min_value * (max_value / min_value) ^ value
+            "reverse_log":
+                max_value - min_value * ((max_value / min_value) ^ (1 - value) - 1)
         default: Integer, default value to return for the parameter. If
             unspecified, the default value will be `min_value`.
     """
@@ -249,18 +382,20 @@ class Int(HyperParameter):
         name,
         min_value,
         max_value,
-        step=1,
-        sampling=None,
+        step=None,
+        sampling="linear",
         default=None,
         **kwargs,
     ):
         super(Int, self).__init__(name=name, default=default, **kwargs)
         self.max_value = _check_int(max_value, arg="max_value")
         self.min_value = _check_int(min_value, arg="min_value")
-        self.step = _check_int(step, arg="step")
-        self.sampling = _check_sampling_arg(
-            sampling, step, min_value, max_value, hp_type="int"
-        )
+        self.step = None
+        if step is not None:
+            self.step = _check_int(step, arg="step")
+        elif sampling == "linear":
+            self.step = 1
+        self.sampling = _check_sampling_arg(sampling, min_value, max_value)
 
     def __repr__(self):
         return (
@@ -275,10 +410,22 @@ class Int(HyperParameter):
             self.default,
         )
 
-    def random_sample(self, seed=None):
-        random_state = random.Random(seed)
-        prob = float(random_state.random())
-        return cumulative_prob_to_value(prob, self)
+    def prob_to_value(self, prob):
+        if self.step is None:
+            # prob is in range [0.0, 1.0), use max_value + 1 so that
+            # max_value may be sampled.
+            return int(self._sample_numerical_value(prob, self.max_value + 1))
+        return int(self._sample_with_step(prob))
+
+    def value_to_prob(self, value):
+        if self.step is None:
+            return self._numerical_to_prob(
+                # + 0.5 to center the prob
+                value + 0.5,
+                # + 1 to include the max_value
+                self.max_value + 1,
+            )
+        return self._to_prob_with_step(value)
 
     @property
     def default(self):
@@ -322,22 +469,65 @@ class Int(HyperParameter):
         )
 
 
-class Float(HyperParameter):
-    """Floating point range, can be evenly divided.
+class Float(Numerical):
+    """Floating point value hyperparameter.
+
+    Example #1:
+
+    ```py
+    hp.Float(
+        "image_rotation_factor",
+        min_value=0,
+        max_value=1)
+    ```
+
+    All values in interval [0, 1] have equal probability of being sampled.
+
+    Example #2:
+
+    ```py
+    hp.Float(
+        "image_rotation_factor",
+        min_value=0,
+        max_value=1,
+        step=0.2)
+    ```
+
+    `step` is the minimum distance between samples.
+    The possible values are [0, 0.2, 0.4, 0.6, 0.8, 1.0].
+
+    Example #3:
+
+    ```py
+    hp.Float(
+        "learning_rate",
+        min_value=0.001,
+        max_value=10,
+        step=10,
+        sampling="log")
+    ```
+
+    When `sampling="log"`, the `step` is multiplied between samples.
+    The possible values are [0.001, 0.01, 0.1, 1, 10].
 
     Args:
         name: A string. the name of parameter. Must be unique for each
             `HyperParameter` instance in the search space.
         min_value: Float, the lower bound of the range.
         max_value: Float, the upper bound of the range.
-        step: Optional float, e.g. 0.1, the smallest meaningful distance
-            between two values. Whether step should be specified is Oracle
-            dependent, since some Oracles can infer an optimal step
-            automatically.
-        sampling: Optional string. One of "linear", "log", "reverse_log". Acts
-            as a hint for an initial prior probability distribution for how
-            this value should be sampled, e.g. "log" will assign equal
-            probabilities to each order of magnitude range.
+        step: Optional float, the distance between two consecutive samples in the
+            range. If left unspecified, it is possible to sample any value in
+            the interval. If `sampling="linear"`, it will be the minimum additve
+            between two samples. If `sampling="log"`, it will be the minimum
+            multiplier between two samples.
+        sampling: String. One of "linear", "log", "reverse_log". Defaults to
+            "linear". When sampling value, it always start from a value in range
+            [0.0, 1.0). The `sampling` argument decides how the value is
+            projected into the range of [min_value, max_value].
+            "linear": min_value + value * (max_value - min_value)
+            "log": min_value * (max_value / min_value) ^ value
+            "reverse_log":
+                max_value - min_value * ((max_value / min_value) ^ (1 - value) - 1)
         default: Float, the default value to return for the parameter. If
             unspecified, the default value will be `min_value`.
     """
@@ -348,7 +538,7 @@ class Float(HyperParameter):
         min_value,
         max_value,
         step=None,
-        sampling=None,
+        sampling="linear",
         default=None,
         **kwargs,
     ):
@@ -359,9 +549,7 @@ class Float(HyperParameter):
             self.step = float(step)
         else:
             self.step = None
-        self.sampling = _check_sampling_arg(
-            sampling, step, min_value, max_value, hp_type="float"
-        )
+        self.sampling = _check_sampling_arg(sampling, min_value, max_value)
 
     def __repr__(self):
         return (
@@ -382,10 +570,15 @@ class Float(HyperParameter):
             return self._default
         return self.min_value
 
-    def random_sample(self, seed=None):
-        random_state = random.Random(seed)
-        prob = float(random_state.random())
-        return cumulative_prob_to_value(prob, self)
+    def prob_to_value(self, prob):
+        if self.step is None:
+            return self._sample_numerical_value(prob)
+        return self._sample_with_step(prob)
+
+    def value_to_prob(self, value):
+        if self.step is None:
+            return self._numerical_to_prob(value)
+        return self._to_prob_with_step(value)
 
     def get_config(self):
         config = super(Float, self).get_config()
@@ -442,9 +635,14 @@ class Boolean(HyperParameter):
     def __repr__(self):
         return f'Boolean(name: "{self.name}", default: {self.default})'
 
-    def random_sample(self, seed=None):
-        random_state = random.Random(seed)
-        return random_state.choice((True, False))
+    def prob_to_value(self, prob):
+        return bool(prob >= 0.5)
+
+    def value_to_prob(self, value):
+        # Center the value in its probability bucket.
+        if value:
+            return 0.75
+        return 0.25
 
     @classmethod
     def from_proto(cls, proto):
@@ -490,9 +688,12 @@ class Fixed(HyperParameter):
     def __repr__(self):
         return f"Fixed(name: {self.name}, value: {self.value})"
 
-    def random_sample(self, seed=None):
+    def prob_to_value(self, prob):
         return self.value
 
+    def value_to_prob(self, value):
+        return 0.5
+
     @property
     def default(self):
         return self.value
@@ -799,27 +1000,74 @@ class HyperParameters(object):
         min_value,
         max_value,
         step=1,
-        sampling=None,
+        sampling="linear",
         default=None,
         parent_name=None,
         parent_values=None,
     ):
-        """Integer range.
+        """Integer hyperparameter.
 
         Note that unlike Python's `range` function, `max_value` is *included* in
         the possible values this parameter can take on.
 
+
+        Example #1:
+
+        ```py
+        hp.Int(
+            "n_layers",
+            min_value=6,
+            max_value=12)
+        ```
+
+        The possible values are [6, 7, 8, 9, 10, 11, 12].
+
+        Example #2:
+
+        ```py
+        hp.Int(
+            "n_layers",
+            min_value=6,
+            max_value=13,
+            step=3)
+        ```
+
+        `step` is the minimum distance between samples.
+        The possible values are [6, 9, 12].
+
+        Example #3:
+
+        ```py
+        hp.Int(
+            "batch_size",
+            min_value=2,
+            max_value=32,
+            step=2,
+            sampling="log")
+        ```
+
+        When `sampling="log"` the `step` is multiplied between samples.
+        The possible values are [2, 4, 8, 16, 32].
+
         Args:
             name: A string. the name of parameter. Must be unique for each
                 `HyperParameter` instance in the search space.
             min_value: Integer, the lower limit of range, inclusive.
             max_value: Integer, the upper limit of range, inclusive.
-            step: Integer, the distance between two consecutive samples in the
-                range. Defaults to 1.
-            sampling: Optional string. One of "linear", "log", "reverse_log". Acts
-                as a hint for an initial prior probability distribution for how
-                this value should be sampled, e.g. "log" will assign equal
-                probabilities to each order of magnitude range.
+            step: Optional integer, the distance between two consecutive samples
+                in the range. If left unspecified, it is possible to sample any
+                integers in the interval. If `sampling="linear"`, it will be the
+                minimum additve between two samples. If `sampling="log"`, it
+                will be the minimum multiplier between two samples.
+            sampling: String. One of "linear", "log", "reverse_log". Defaults to
+                "linear". When sampling value, it always start from a value in
+                range [0.0, 1.0). The `sampling` argument decides how the value
+                is projected into the range of [min_value, max_value].
+                "linear": min_value + value * (max_value - min_value)
+                "log": min_value * (max_value / min_value) ^ value
+                "reverse_log":
+                    (max_value -
+                     min_value * ((max_value / min_value) ^ (1 - value) - 1))
             default: Integer, default value to return for the parameter. If
                 unspecified, the default value will be `min_value`.
             parent_name: Optional string, specifying the name of the parent
@@ -851,26 +1099,70 @@ class HyperParameters(object):
         min_value,
         max_value,
         step=None,
-        sampling=None,
+        sampling="linear",
         default=None,
         parent_name=None,
         parent_values=None,
     ):
-        """Floating point range, can be evenly divided.
+        """Floating point value hyperparameter.
+
+        Example #1:
+
+        ```py
+        hp.Float(
+            "image_rotation_factor",
+            min_value=0,
+            max_value=1)
+        ```
+
+        All values in interval [0, 1] have equal probability of being sampled.
+
+        Example #2:
+
+        ```py
+        hp.Float(
+            "image_rotation_factor",
+            min_value=0,
+            max_value=1,
+            step=0.2)
+        ```
+
+        `step` is the minimum distance between samples.
+        The possible values are [0, 0.2, 0.4, 0.6, 0.8, 1.0].
+
+        Example #3:
+
+        ```py
+        hp.Float(
+            "learning_rate",
+            min_value=0.001,
+            max_value=10,
+            step=10,
+            sampling="log")
+        ```
+
+        When `sampling="log"`, the `step` is multiplied between samples.
+        The possible values are [0.001, 0.01, 0.1, 1, 10].
 
         Args:
             name: A string. the name of parameter. Must be unique for each
                 `HyperParameter` instance in the search space.
             min_value: Float, the lower bound of the range.
             max_value: Float, the upper bound of the range.
-            step: Optional float, e.g. 0.1, the smallest meaningful distance
-                between two values. Whether step should be specified is Oracle
-                dependent, since some Oracles can infer an optimal step
-                automatically.
-            sampling: Optional string. One of "linear", "log", "reverse_log". Acts
-                as a hint for an initial prior probability distribution for how
-                this value should be sampled, e.g. "log" will assign equal
-                probabilities to each order of magnitude range.
+            step: Optional float, the distance between two consecutive samples
+                in the range. If left unspecified, it is possible to sample any
+                value in the interval. If `sampling="linear"`, it will be the
+                minimum additve between two samples. If `sampling="log"`, it
+                will be the minimum multiplier between two samples.
+            sampling: String. One of "linear", "log", "reverse_log". Defaults to
+                "linear". When sampling value, it always start from a value in
+                range [0.0, 1.0). The `sampling` argument decides how the value
+                is projected into the range of [min_value, max_value].
+                "linear": min_value + value * (max_value - min_value)
+                "log": min_value * (max_value / min_value) ^ value
+                "reverse_log":
+                    (max_value -
+                     min_value * ((max_value / min_value) ^ (1 - value) - 1))
             default: Float, the default value to return for the parameter. If
                 unspecified, the default value will be `min_value`.
             parent_name: Optional string, specifying the name of the parent
@@ -1129,78 +1421,21 @@ def serialize(obj):
     return keras.utils.serialize_keras_object(obj)
 
 
-def cumulative_prob_to_value(prob, hp):
-    """Convert a value from [0, 1] to a hyperparameter value."""
-    if isinstance(hp, Fixed):
-        return hp.value
-    elif isinstance(hp, Boolean):
-        return bool(prob >= 0.5)
-    elif isinstance(hp, Choice):
-        ele_prob = 1 / len(hp.values)
-        index = int(math.floor(prob / ele_prob))
-        # Can happen when `prob` is very close to 1.
-        if index == len(hp.values):
-            index = index - 1
-        return hp.values[index]
-    elif isinstance(hp, (Int, Float)):
-        sampling = hp.sampling or "linear"
-        if sampling == "linear":
-            value = prob * (hp.max_value - hp.min_value) + hp.min_value
-        elif sampling == "log":
-            value = hp.min_value * math.pow(hp.max_value / hp.min_value, prob)
-        elif sampling == "reverse_log":
-            value = (
-                hp.max_value
-                + hp.min_value
-                - hp.min_value * math.pow(hp.max_value / hp.min_value, 1 - prob)
-            )
-        else:
-            raise ValueError(f"Unrecognized sampling value: {sampling}")
-
-        if hp.step is not None:
-            values = np.arange(hp.min_value, hp.max_value + 1e-7, step=hp.step)
-            closest_index = np.abs(values - value).argmin()
-            value = values[closest_index]
-
-        if isinstance(hp, Int):
-            return int(value)
-        return value
-    else:
-        raise ValueError(f"Unrecognized `HyperParameter` type: {hp}")
+def _prob_to_index(prob, n_index):
+    """Convert a cumulative probability to a 0-based index in the given range."""
+    ele_prob = 1 / n_index
+    index = int(math.floor(prob / ele_prob))
+    # Can happen when `prob` is very close to 1.
+    if index == n_index:
+        index = index - 1
+    return index
 
 
-def value_to_cumulative_prob(value, hp):
-    """Convert a hyperparameter value to [0, 1]."""
-    if isinstance(hp, Fixed):
-        return 0.5
-    if isinstance(hp, Boolean):
-        # Center the value in its probability bucket.
-        if value:
-            return 0.75
-        return 0.25
-    elif isinstance(hp, Choice):
-        ele_prob = 1 / len(hp.values)
-        index = hp.values.index(value)
-        # Center the value in its probability bucket.
-        return (index + 0.5) * ele_prob
-    elif isinstance(hp, (Int, Float)):
-        if hp.max_value == hp.min_value:
-            return 1.0
-        sampling = hp.sampling or "linear"
-        if sampling == "linear":
-            return (value - hp.min_value) / (hp.max_value - hp.min_value)
-        elif sampling == "log":
-            return math.log(value / hp.min_value) / math.log(
-                hp.max_value / hp.min_value
-            )
-        elif sampling == "reverse_log":
-            return 1.0 - math.log(
-                (hp.max_value + hp.min_value - value) / hp.min_value
-            ) / math.log(hp.max_value / hp.min_value)
-        else:
-            raise ValueError(f"Unrecognized sampling value: {sampling}")
-    else:
-        raise ValueError(f"Unrecognized `HyperParameter` type: {hp}")
+def _index_to_prob(index, n_index):
+    """Convert a 0-based index in the given range to cumulative probability."""
+    ele_prob = 1 / n_index
+    # Center the value in its probability bucket.
+    return (index + 0.5) * ele_prob
 
 
 def _sampling_from_proto(sampling):
diff --git a/keras_tuner/tuners/bayesian.py b/keras_tuner/tuners/bayesian.py
index 1b5b88e..49e05b9 100644
--- a/keras_tuner/tuners/bayesian.py
+++ b/keras_tuner/tuners/bayesian.py
@@ -322,7 +322,7 @@ class BayesianOptimizationOracle(oracle_module.Oracle):
                     trial_value = hp.default
 
                 # Embed an HP value into the continuous space [0, 1].
-                prob = hp_module.value_to_cumulative_prob(trial_value, hp)
+                prob = hp.value_to_prob(trial_value)
                 vector.append(prob)
 
             if trial in ongoing_trials and self.gpr.can_predict():
@@ -358,7 +358,7 @@ class BayesianOptimizationOracle(oracle_module.Oracle):
             else:
                 prob = vector[vector_index]
                 vector_index += 1
-                value = hp_module.cumulative_prob_to_value(prob, hp)
+                value = hp.prob_to_value(prob)
 
             if hps.is_active(hp):
                 hps.values[hp.name] = value
diff --git a/shell/coverage.sh b/shell/coverage.sh
new file mode 100644
index 0000000..718a278
--- /dev/null
+++ b/shell/coverage.sh
@@ -0,0 +1,1 @@
+pytest --cov-report xml:cov.xml --cov keras_tuner $1


Test patch:
diff --git a/keras_tuner/engine/hyperparameters_test.py b/keras_tuner/engine/hyperparameters_test.py
index 87f3bb4..dc78ef1 100644
--- a/keras_tuner/engine/hyperparameters_test.py
+++ b/keras_tuner/engine/hyperparameters_test.py
@@ -217,6 +217,7 @@ def test_Choice():
     assert choice.default == 2
     assert choice.random_sample() in [1, 2, 3]
     assert choice.random_sample(123) == choice.random_sample(123)
+    assert abs(choice.value_to_prob(1) - 1.0 / 6) < 1e-4
     # No default
     choice = hp_module.Choice("choice", [1, 2, 3])
     assert choice.default == 1
@@ -279,6 +280,39 @@ def test_Float():
     assert linear.default == 0.5
 
 
+def test_float_linear_value_to_prob_no_step():
+    rg = hp_module.Float("rg", min_value=1.0, max_value=11.0)
+    assert abs(rg.value_to_prob(4.5) - 0.35) < 1e-4
+    assert rg.prob_to_value(0.35) == 4.5
+
+
+def test_float_log_with_step():
+    rg = hp_module.Float(
+        "rg", min_value=0.01, max_value=100, step=10, sampling="log"
+    )
+    for i in range(10):
+        assert rg.random_sample() in [0.01, 0.1, 1.0, 10.0, 100.0]
+    assert abs(rg.value_to_prob(0.1) - 0.3) < 1e-4
+    assert rg.prob_to_value(0.3) == 0.1
+
+
+def test_float_reverse_log_with_step():
+    rg = hp_module.Float(
+        "rg", min_value=0.01, max_value=100, step=10, sampling="reverse_log"
+    )
+    for i in range(10):
+        # print(rg.random_sample())
+        # assert rg.random_sample() in [0.01, 0.1, 1.0, 10.0, 100.0]
+        # [0.09, 0.9, 9, 90]
+        # [90, 9, 0.9, 0.09]
+        sample = rg.random_sample()
+        assert any(
+            [abs(sample - x) < 1e-4 for x in [0.01, 90.01, 99.01, 99.91, 100.0]]
+        )
+    assert abs(rg.value_to_prob(99.91) - 0.3) < 1e-4
+    assert abs(rg.prob_to_value(0.3) - 99.91) < 1e-4
+
+
 def test_sampling_arg():
     f = hp_module.Float("f", 1e-20, 1e10, sampling="log")
     f = hp_module.Float.from_config(f.get_config())
@@ -293,13 +327,13 @@ def test_sampling_arg():
 
     with pytest.raises(
         ValueError,
-        match="`sampling` `min_value` 1 is greater than the `max_value` 0",
+        match="`min_value` 1 is greater than the `max_value` 0",
     ):
         hp_module.Int("k", 1, 0, sampling="linear")
 
     with pytest.raises(
         ValueError,
-        match="`sampling` `min_value` 1 is greater than the `max_value` 0",
+        match="`min_value` 1 is greater than the `max_value` 0",
     ):
         hp_module.Int("k", 1, 0, sampling="linear")
 
@@ -310,8 +344,8 @@ def test_sampling_zero_length_intervals():
     assert rand_sample == 2
 
     val = 2
-    prob = hp_module.value_to_cumulative_prob(val, f)
-    assert prob == 1
+    prob = f.value_to_prob(val)
+    assert prob == 0.5
 
 
 def test_log_sampling_random_state():
@@ -321,21 +355,21 @@ def test_log_sampling_random_state():
     assert rand_sample <= f.max_value
 
     val = 1e-3
-    prob = hp_module.value_to_cumulative_prob(val, f)
+    prob = f.value_to_prob(val)
     assert prob == 0
-    new_val = hp_module.cumulative_prob_to_value(prob, f)
+    new_val = f.prob_to_value(prob)
     assert np.isclose(val, new_val)
 
     val = 1
-    prob = hp_module.value_to_cumulative_prob(val, f)
+    prob = f.value_to_prob(val)
     assert prob == 0.5
-    new_val = hp_module.cumulative_prob_to_value(prob, f)
+    new_val = f.prob_to_value(prob)
     assert np.isclose(val, new_val)
 
     val = 1e3
-    prob = hp_module.value_to_cumulative_prob(val, f)
+    prob = f.value_to_prob(val)
     assert prob == 1
-    new_val = hp_module.cumulative_prob_to_value(prob, f)
+    new_val = f.prob_to_value(prob)
     assert np.isclose(val, new_val)
 
 
@@ -346,15 +380,15 @@ def test_reverse_log_sampling_random_state():
     assert rand_sample <= f.max_value
 
     val = 1e-3
-    prob = hp_module.value_to_cumulative_prob(val, f)
+    prob = f.value_to_prob(val)
     assert prob == 0
-    new_val = hp_module.cumulative_prob_to_value(prob, f)
+    new_val = f.prob_to_value(prob)
     assert np.isclose(val, new_val)
 
     val = 1
-    prob = hp_module.value_to_cumulative_prob(val, f)
+    prob = f.value_to_prob(val)
     assert prob > 0 and prob < 1
-    new_val = hp_module.cumulative_prob_to_value(prob, f)
+    new_val = f.prob_to_value(prob)
     assert np.isclose(val, new_val)
 
 
@@ -365,11 +399,25 @@ def test_Int():
     assert 5 <= rg.random_sample() <= 9
     assert isinstance(rg.random_sample(), int)
     assert rg.random_sample(123) == rg.random_sample(123)
+    assert abs(rg.value_to_prob(6) - 0.3) < 1e-4
     # No default
     rg = hp_module.Int("rg", min_value=5, max_value=9, step=1)
     assert rg.default == 5
 
 
+def test_int_log_with_step():
+    rg = hp_module.Int("rg", min_value=2, max_value=32, step=2, sampling="log")
+    for i in range(10):
+        assert rg.random_sample() in [2, 4, 8, 16, 32]
+    assert abs(rg.value_to_prob(4) - 0.3) < 1e-4
+    assert rg.prob_to_value(0.3) == 4
+
+
+def test_int_log_without_step():
+    rg = hp_module.Int("rg", min_value=2, max_value=32, sampling="log")
+    assert rg.prob_to_value(rg.value_to_prob(4)) == 4
+
+
 def test_Boolean():
     # Test default default
     boolean = hp_module.Boolean("bool")
@@ -389,6 +437,10 @@ def test_Boolean():
     # Test random_sample
     assert boolean.random_sample() in {True, False}
     assert boolean.random_sample(123) == boolean.random_sample(123)
+    assert {boolean.value_to_prob(True), boolean.value_to_prob(False)} == {
+        0.25,
+        0.75,
+    }
 
 
 def test_Fixed():
@@ -413,6 +465,7 @@ def test_Fixed():
     fixed = hp_module.Fixed("fixed", 8.2)
     assert fixed.value == 8.2
     assert fixed.random_sample() == 8.2
+    assert fixed.value_to_prob(fixed.value) == 0.5
 
     with pytest.raises(ValueError, match="value must be an"):
         hp_module.Fixed("fixed", None)
@@ -469,7 +522,7 @@ def test_int_proto():
     assert proto.sampling == keras_tuner_pb2.Sampling.LOG
     # Proto stores the implicit default.
     assert proto.default == 1
-    assert proto.step == 1
+    assert proto.step == 0
 
     new_hp = hp_module.Int.from_proto(proto)
     assert new_hp._default == 1
@@ -568,10 +621,10 @@ def test_dict_methods():
 def test_prob_one_choice():
     hp = hp_module.Choice("a", [0, 1, 2])
     # Check that boundaries are valid.
-    value = hp_module.cumulative_prob_to_value(1, hp)
+    value = hp.prob_to_value(1)
     assert value == 2
 
-    value = hp_module.cumulative_prob_to_value(0, hp)
+    value = hp.prob_to_value(0)
     assert value == 0