Repository: ivy-llc/ivy

Issue:
[Bug]: `ivy_paddle_backend.round()` function inconsistent with `numpy.round()`
### Bug Explanation

When using the [ivy_paddle_backend.round()](https://github.com/unifyai/ivy/blob/main/ivy/functional/backends/paddle/elementwise.py#L833) function, I found that its results are inconsistent with those of [numpy.round()](https://numpy.org/doc/stable/reference/generated/numpy.round.html) for several input numbers. I ran a test comparing the two and discovered that they produce different results in `7` cases.

### Steps to Reproduce Bug

```py
import numpy as np
import paddle
import ivy
import ivy.functional.frontends.paddle as ivy_paddle

def test_rounding_against_numpy():
    test_numbers = [
        0.1, 0.5, 0.9, # generic decimals
        1.5, 2.5, 3.5, 4.5, # even .5s
        1.4, 2.6, 3.7, 4.8, # other decimals
        -1.5, -2.5, -3.5, -4.5, # negative even .5s
        -1.4, -2.6, -3.7, -4.8, # negative other decimals
    ]

    errors = 0

    for num in test_numbers:
        numpy_res = np.round(num)
        ivy.set_paddle_backend()
        paddle_input = paddle.to_tensor(num, dtype=paddle.float32)
        paddle_res = ivy.round(paddle_input)
        paddle_res = ivy.to_numpy(paddle_res)
        if numpy_res != paddle_res:
            print(f"Error: For input {num}, numpy gives {numpy_res} but paddle gives {paddle_res}.")
            errors += 1

    if errors == 0:
        print("All tests passed for ivy_paddle.round()!")
    else:
        print(f"{errors} tests failed.")

test_rounding_against_numpy()

"""
Error: For input 0.9, numpy gives 1.0 but paddle gives 0.0.
Error: For input 1.4, numpy gives 1.0 but paddle gives 2.0.
Error: For input 2.6, numpy gives 3.0 but paddle gives 2.0.
Error: For input 4.8, numpy gives 5.0 but paddle gives 4.0.
Error: For input -1.4, numpy gives -1.0 but paddle gives -2.0.
Error: For input -2.6, numpy gives -3.0 but paddle gives -2.0.
Error: For input -4.8, numpy gives -5.0 but paddle gives -4.0.
7 tests failed.
"""
```

### Environment

linux+docker+vscode

### Ivy Version

v1.1.9

### Backend

- [ ] NumPy
- [ ] TensorFlow
- [ ] PyTorch
- [ ] JAX
- [x] Paddle

### Device

CPU


Discussion:

Resolving patch:
diff --git a/ivy/functional/backends/paddle/elementwise.py b/ivy/functional/backends/paddle/elementwise.py
--- a/ivy/functional/backends/paddle/elementwise.py
+++ b/ivy/functional/backends/paddle/elementwise.py
@@ -830,39 +830,48 @@ def pow(
     return paddle.pow(x1, x2)
 
 
-def round(
-    x: paddle.Tensor, /, *, decimals: int = 0, out: Optional[paddle.Tensor] = None
-) -> paddle.Tensor:
-    def _np_round(x):
-        # this is a logic to mimic np.round behaviour
-        # which rounds odd numbers up and even numbers down at limits like 0.5
+# Implementation based on TensorFlow's scalar_round_half_to_even_op logic
+# Reference: https://github.com/tensorflow/tensorflow/blob/7f1050a6976d11bfb0bb37bdfc82350c0a238faa/tensorflow/core/kernels/cwise_ops.h#L510  # noqa: E501
+def _round_half_to_even(x):
+    round_val = paddle_backend.floor(x + 0.5)
+    fraction = round_val - x
 
-        one = paddle.to_tensor(1, dtype="int64")
+    # Identify elements with a fractional part of 0.5
+    mask = paddle_backend.equal(fraction, paddle.to_tensor(0.5, dtype=fraction.dtype))
 
-        # check if the number is even or odd
-        is_even = paddle.bitwise_and(paddle_backend.trunc(x).astype("int64"), one) == 0
+    # Round to the nearest even number if the fraction is 0.5
+    even_round_val = 2 * paddle_backend.floor(0.5 * x + 0.5)
 
-        # round the number to the nearest integer
-        round_x = paddle.sign(x) * paddle.where(
-            is_even, paddle.floor(x.abs()), paddle.ceil(x.abs())
-        )
+    # Combine the results
+    return paddle.where(mask, even_round_val, round_val)
 
-        # if the number was rounded up from an even number
-        #   round the number down to the nearest even number
-        return paddle.where(
-            paddle.logical_and(
-                paddle.bitwise_and(round_x.astype("int64"), one) == 1.0,
-                is_even,
-            ),
-            round_x - 1.0,
-            round_x,
-        )
 
-    if x.dtype not in [paddle.float32, paddle.float64]:
-        if paddle.is_complex(x):
-            return paddle.complex(_np_round(x.real()), _np_round(x.imag()))
-        return _np_round(x.astype("float32")).astype(x.dtype)
-    return _np_round(x).astype(x.dtype)
+# This function aims to mimic the behavior of np.round similar to how tf.experimental.numpy.round does # noqa: E501
+# Reference for tf.experimental.numpy.round:https://github.com/tensorflow/tensorflow/blob/v2.13.0/tensorflow/python/ops/numpy_ops/np_array_ops.py#L724 # noqa: E501
+@with_unsupported_device_and_dtypes(
+    {"2.5.1 and below": {"cpu": ("bfloat16", "float16", "complex")}}, backend_version
+)
+def round(
+    x: paddle.Tensor, /, *, decimals: int = 0, out: Optional[paddle.Tensor] = None
+) -> paddle.Tensor:
+    x = paddle.to_tensor(x, dtype=x.dtype)
+    dtype_ = x.dtype
+    factor = math.pow(10, decimals)
+    factor = paddle.to_tensor(factor)
+
+    # Handle floating point and complex numbers
+    if paddle.is_floating_point(x) or paddle.is_complex(x):
+        factor = paddle.to_tensor(factor)
+        factor = paddle.cast(factor, dtype_)
+    else:
+        float_dtype_ = paddle.float32  # paddle.get_default_dtype()
+        x = x.astype(float_dtype_)
+        factor = paddle.cast(factor, float_dtype_)
+
+    x = paddle.multiply(x, factor)
+    x = _round_half_to_even(x)
+    x = paddle.divide(x, factor)
+    return x.astype(dtype_)
 
 
 def trunc(x: paddle.Tensor, /, *, out: Optional[paddle.Tensor] = None) -> paddle.Tensor:
diff --git a/ivy/functional/frontends/paddle/math.py b/ivy/functional/frontends/paddle/math.py
--- a/ivy/functional/frontends/paddle/math.py
+++ b/ivy/functional/frontends/paddle/math.py
@@ -1,6 +1,10 @@
 # global
 import ivy
-from ivy.func_wrapper import with_unsupported_dtypes, with_supported_dtypes
+from ivy.func_wrapper import (
+    with_unsupported_dtypes,
+    with_supported_dtypes,
+    with_supported_device_and_dtypes,
+)
 from ivy.functional.frontends.paddle.func_wrapper import to_ivy_arrays_and_back
 
 
@@ -446,10 +450,20 @@ def remainder(x, y, name=None):
     return ivy.remainder(x, y)
 
 
-@with_unsupported_dtypes({"2.5.1 and below": ("float16", "bfloat16")}, "paddle")
+@with_supported_device_and_dtypes(
+    {
+        "2.5.1 and below": {
+            "cpu": ("float32", "float64"),
+            "gpu": ("float16", "float32", "float64"),
+        }
+    },
+    "paddle",
+)
 @to_ivy_arrays_and_back
 def round(x, name=None):
-    return ivy.round(x)
+    sign = ivy.sign(x)
+    x = sign * ivy.floor(ivy.abs(x) + 0.5)
+    return x
 
 
 @with_supported_dtypes({"2.5.1 and below": ("float32", "float64")}, "paddle")


Test patch: