This is an automated email from the ASF dual-hosted git repository.
zhengruifeng pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.x by this push:
new 73cfdce0095c [SPARK-59093][PS] Fix incorrect results and arithmetic
errors in floor division
73cfdce0095c is described below
commit 73cfdce0095ca293c96c37744f727c43ed452ef7
Author: Spenser Sun <[email protected]>
AuthorDate: Tue Sep 1 09:10:12 2026 +0800
[SPARK-59093][PS] Fix incorrect results and arithmetic errors in floor
division
### What changes were proposed in this pull request?
`IntegralOps` and `FractionalOps` now compute `//` and its reflected form
with `_floor_divide_func`, the expression added in SPARK-58581. The first
commit moves that helper from `numpy_compat.py` to `pyspark.pandas.utils` so
the core operator does not import the NumPy compatibility layer.
### Why are the changes needed?
`F.floor(a / b)` rounds before flooring, casts integral operands to double,
and returns a bigint, so `//` gives wrong values (`ps.Series([1.0]) // 0.1`
returns 10.0, pandas 9.0) and raises `ARITHMETIC_OVERFLOW` when the quotient is
infinite. SPARK-58581 fixed this arithmetic in the `np.floor_divide` mapping,
but that entry is unreachable, so the operator users actually call was left
unchanged.
### Does this PR introduce _any_ user-facing change?
Yes. `//` matches pandas where it previously did not: `ps.Series([1.0,
10.0]) // 0.1` returns `[9.0, 99.0]` instead of `[10.0, 100.0]`. A quotient
above `2**53` is still rounded, since the result type has to stay double.
### How was this patch tested?
Rows added to the existing `test_floordiv` and `test_rfloordiv`, each
confirmed to fail without the change, plus the existing pandas-on-Spark suites
with ANSI mode on and off.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 5)
Closes #58397 from Spenserrrr/numpy-floordiv-dunder.
Authored-by: Spenser Sun <[email protected]>
Signed-off-by: Ruifeng Zheng <[email protected]>
(cherry picked from commit 37bca2cad98125891a5c08ffc7f31b448c9de69c)
Signed-off-by: Ruifeng Zheng <[email protected]>
---
python/pyspark/pandas/data_type_ops/num_ops.py | 47 ++---------
python/pyspark/pandas/numpy_compat.py | 97 +---------------------
.../pandas/tests/data_type_ops/test_num_mul_div.py | 45 ++++++++++
.../pandas/tests/data_type_ops/test_num_reverse.py | 22 +++++
python/pyspark/pandas/tests/test_numpy_compat.py | 2 +-
python/pyspark/pandas/utils.py | 96 +++++++++++++++++++++
6 files changed, 171 insertions(+), 138 deletions(-)
diff --git a/python/pyspark/pandas/data_type_ops/num_ops.py
b/python/pyspark/pandas/data_type_ops/num_ops.py
index 8baeacf3b018..c1926d4d6878 100644
--- a/python/pyspark/pandas/data_type_ops/num_ops.py
+++ b/python/pyspark/pandas/data_type_ops/num_ops.py
@@ -17,7 +17,7 @@
import decimal
import numbers
-from typing import Any, Callable, Union, cast
+from typing import Any, Union, cast
import numpy as np
import pandas as pd
@@ -52,7 +52,7 @@ from pyspark.pandas.typedef.typehints import (
handle_dtype_as_extension_dtype,
pandas_on_spark_type,
)
-from pyspark.pandas.utils import is_ansi_mode_enabled
+from pyspark.pandas.utils import _floor_divide_func, is_ansi_mode_enabled
from pyspark.sql import Column as PySparkColumn
from pyspark.sql import functions as F
from pyspark.sql.types import (
@@ -373,23 +373,9 @@ class IntegralOps(NumericOps):
_sanitize_list_like(right)
if not is_valid_operand_for_numeric_arithmetic(right):
raise TypeError("Floor division can not be applied to given
types.")
- spark_session = left._internal.spark_frame.sparkSession
- use_try_divide = is_ansi_mode_enabled(spark_session)
-
- def fallback_div(x: PySparkColumn, y: PySparkColumn) -> PySparkColumn:
- return x.__div__(y)
-
- safe_div: Callable[[PySparkColumn, PySparkColumn], PySparkColumn] = (
- F.try_divide if use_try_divide else fallback_div
- )
def floordiv(left: PySparkColumn, right: Any) -> PySparkColumn:
- return F.when(F.lit(right is np.nan), np.nan).otherwise(
- F.when(
- F.lit(right != 0) | F.lit(right).isNull(),
- F.floor(left.__div__(right)),
- ).otherwise(safe_div(F.lit(np.inf), left))
- )
+ return _floor_divide_func(left, F.lit(right))
right = transform_boolean_operand_to_numeric(right,
spark_type=left.spark.data_type)
return numpy_column_op(floordiv)(left, right)
@@ -413,9 +399,7 @@ class IntegralOps(NumericOps):
raise TypeError("Floor division can not be applied to given
types.")
def rfloordiv(left: PySparkColumn, right: Any) -> PySparkColumn:
- return F.when(F.lit(left == 0),
F.lit(np.inf).__div__(right)).otherwise(
- F.floor(F.lit(right).__div__(left))
- )
+ return _floor_divide_func(F.lit(right), left)
right = transform_boolean_operand_to_numeric(right,
spark_type=left.spark.data_type)
return numpy_column_op(rfloordiv)(left, right)
@@ -502,25 +486,8 @@ class FractionalOps(NumericOps):
raise TypeError("Floor division can not be applied to given
types.")
left_dtype = left.dtype
- def fallback_div(x: PySparkColumn, y: PySparkColumn) -> PySparkColumn:
- return x.__div__(y)
-
- safe_div: Callable[[PySparkColumn, PySparkColumn], PySparkColumn] = (
- F.try_divide if is_ansi else fallback_div
- )
-
def floordiv(lc: PySparkColumn, rc: Any) -> PySparkColumn:
- expr = F.when(F.lit(rc is np.nan), np.nan).otherwise(
- F.when(
- F.lit(rc != 0) | F.lit(rc).isNull(),
- F.floor(lc.__div__(rc)),
- ).otherwise(
- F.when(F.lit(lc == np.inf) | F.lit(lc == -np.inf),
lc).otherwise(
- safe_div(F.lit(np.inf), lc)
- )
- )
- )
- return _cast_back_float(expr, left_dtype, right)
+ return _cast_back_float(_floor_divide_func(lc, F.lit(rc)),
left_dtype, right)
new_right = transform_boolean_operand_to_numeric(right,
spark_type=left.spark.data_type)
return numpy_column_op(floordiv)(left, new_right)
@@ -552,9 +519,7 @@ class FractionalOps(NumericOps):
raise TypeError("Floor division can not be applied to given
types.")
def rfloordiv(left: PySparkColumn, right: Any) -> PySparkColumn:
- return F.when(F.lit(left == 0),
F.lit(np.inf).__div__(right)).otherwise(
- F.when(F.lit(left) == np.nan,
np.nan).otherwise(F.floor(F.lit(right).__div__(left)))
- )
+ return _floor_divide_func(F.lit(right), left)
right = transform_boolean_operand_to_numeric(right,
spark_type=left.spark.data_type)
return numpy_column_op(rfloordiv)(left, right)
diff --git a/python/pyspark/pandas/numpy_compat.py
b/python/pyspark/pandas/numpy_compat.py
index 5775e46b49a9..0c93be229904 100644
--- a/python/pyspark/pandas/numpy_compat.py
+++ b/python/pyspark/pandas/numpy_compat.py
@@ -21,6 +21,7 @@ import numpy as np
from pyspark.loose_version import LooseVersion
from pyspark.pandas._typing import SeriesOrIndex
from pyspark.pandas.base import IndexOpsMixin
+from pyspark.pandas.utils import _floor_divide_func
from pyspark.sql import Column
from pyspark.sql import functions as F
from pyspark.sql.pandas.functions import pandas_udf
@@ -197,102 +198,6 @@ def _logaddexp_func(c1: Column, c2: Column, base2: bool =
False) -> Column:
)
-def _floor_divide_floating(c1: Column, c2: Column) -> Column:
- """Return floor(c1 / c2) for finite non-zero double operands, derived from
the remainder.
-
- Flooring the quotient is wrong when the division rounds up across an
integer: 1.0 / 0.1
- rounds to exactly 10.0, so its floor is 10 where NumPy, pandas and Python
return 9. A
- remainder is exact, so NumPy's npy_divmod derives the quotient from it, as
this does.
- """
- remainder = F.try_mod(c1, c2)
- # The remainder carries the dividend's sign, so this is the truncating
quotient.
- truncated = (c1 - remainder) / c2
- # Truncating and flooring differ by one on opposite signs with a remainder
left over.
- quotient = F.when(
- (remainder != 0) & ((remainder < 0) != (c2 < 0)), truncated -
F.lit(1.0)
- ).otherwise(truncated)
- # The quotient is whole in exact arithmetic, but the division can leave it
a few bits off, so
- # round it back. F.floor cannot do this: it returns a bigint, which raises
on an infinity.
- floor = quotient - F.pmod(quotient, F.lit(1.0))
- return (
- # An infinite quotient is its own floor, and has to be returned before
the line above
- # is used, since pmod of an infinity is nan and leaves `floor` nan.
- F.when(quotient.isin(float("inf"), float("-inf")), quotient)
- # Flooring goes one too low when the division landed just under the
whole number.
- .when(quotient - floor > F.lit(0.5), floor + F.lit(1.0))
- .otherwise(floor)
- )
-
-
-def _floor_divide_integral(c1: Column, c2: Column) -> Column:
- """Return floor(c1 / c2) for integral operands, keeping the quotient in
integer space.
-
- Casting an operand above 2**53 to double drops its low bits, turning
9007199254740993 into
- 9007199254740992, and Spark's `/` always divides as double. The long casts
are no-ops for
- the integral types the caller admits; they are there because `div` rejects
a double even in
- a branch the guard turns off.
- """
- c1_long = c1.cast("long")
- c2_long = c2.cast("long")
- # `div` is integer division, truncating toward zero, so it needs the same
flooring
- # correction as the floating helper. Integer arithmetic cannot round, so
nothing more.
- truncated = F.call_function("div", c1_long, c2_long)
- remainder = F.try_mod(c1_long, c2_long)
- return F.when(
- # The one quotient a long cannot hold, where NumPy wraps around and
`div` would raise.
- (c1_long == F.lit(-(2**63))) & (c2_long == F.lit(-1)),
- F.lit(float(-(2**63))),
- ).otherwise(
- F.when((remainder != 0) & ((remainder < 0) != (c2_long < 0)),
truncated - F.lit(1))
- .otherwise(truncated)
- .cast("double")
- )
-
-
-def _floor_divide_func(c1: Column, c2: Column) -> Column:
- c1_double = c1.cast("double")
- c2_double = c2.cast("double")
- integral_types = ["tinyint", "smallint", "int", "bigint"]
-
- return (
- # Null, nan and a zero divisor are handled the same way for every
operand type.
- F.when(c1.isNull() | F.isnan(c1), c1_double)
- .when(c2.isNull() | F.isnan(c2), c2_double)
- # pandas upcasts a zero divisor instead of raising. A negative zero
divisor negates the
- # result, and no comparison can see that sign, so the string form is
used. A nullable Int64
- # returns 0 instead, but arrives as bigint like a default int64, whose
answer this follows.
- .when(
- c2_double == 0,
- F.when(c1_double == 0, F.lit(float("nan")))
- .when((c1_double < 0) != (c2_double.cast("string") == "-0.0"),
F.lit(float("-inf")))
- .otherwise(F.lit(float("inf"))),
- )
- # Integral operands divide as integers, so operands above 2**53 keep
their low bits.
- .when(
- F.typeof(c1).isin(integral_types) &
F.typeof(c2).isin(integral_types),
- _floor_divide_integral(c1, c2),
- )
- # Only floating operands can be infinite or a negative zero, handled
before the division.
- .when(
- F.typeof(c1).isin("float", "double") | F.typeof(c2).isin("float",
"double"),
- # An infinite dividend has no remainder, so NumPy's quotient is
nan for any divisor.
- F.when(c1_double.isin(float("-inf"), float("inf")),
F.lit(float("nan")))
- # An infinite divisor gives a quotient between -1 and 1, so the
floor is 0 or -1.
- .when(
- c2_double.isin(float("-inf"), float("inf")),
- F.when(c1_double == 0, c1_double / c2_double)
- .when((c1_double < 0) != (c2_double < 0), F.lit(-1.0))
- .otherwise(F.lit(0.0)),
- )
- # Dividing a zero dividend keeps its sign, so -0.0 // 3.0 is -0.0.
- .when(c1_double == 0, c1_double / c2_double)
- .otherwise(_floor_divide_floating(c1_double, c2_double)),
- )
- # A decimal cannot be matched by name: typeof reports its precision,
as in decimal(10,2).
- .otherwise(_floor_divide_floating(c1_double, c2_double))
- )
-
-
# NumPy 2.3.0 changed how fmax/fmin break a signed-zero tie: for equal operands
# (for example +0.0 and -0.0) it returns the first operand, while older
versions
# returned the second. Track the installed NumPy so the result keeps the
matching
diff --git a/python/pyspark/pandas/tests/data_type_ops/test_num_mul_div.py
b/python/pyspark/pandas/tests/data_type_ops/test_num_mul_div.py
index d272b22cc475..2bb2d024f3c2 100644
--- a/python/pyspark/pandas/tests/data_type_ops/test_num_mul_div.py
+++ b/python/pyspark/pandas/tests/data_type_ops/test_num_mul_div.py
@@ -118,6 +118,51 @@ class NumMulDivTestsMixin:
self.assertRaises(TypeError, lambda: psdf["decimal"] // 0.1)
self.assertRaises(TypeError, lambda: 0.1 // psdf["decimal"])
+ # A divisor that is not exactly representable: 1.0 / 0.1 rounds up to
exactly 10.0,
+ # so flooring the quotient gives 10 where pandas returns 9.
+ pser = pd.Series([1.0, 10.0, -1.0, 2.5])
+ psser = ps.from_pandas(pser)
+ self.assert_eq(pser // 0.1, psser // 0.1)
+
+ # An integral operand above 2**53 loses its low bits when divided as
double. pandas
+ # returns int64 here, which Spark's division cannot, so compare the
values.
+ pser = pd.Series([9007199254740993, -9007199254740993])
+ psser = ps.from_pandas(pser)
+ self.assert_eq((pser // 2).astype(float), psser // 2)
+ self.assert_eq((pser // 3).astype(float), psser // 3)
+
+ # An infinite divisor leaves a quotient of 0 or -1, and an infinite
dividend has no
+ # finite floor, which pandas reports as nan.
+ pser = pd.Series([1.0, -1.0, np.inf, -np.inf])
+ psser = ps.from_pandas(pser)
+ self.assert_eq(pser // np.inf, psser // np.inf)
+ self.assert_eq(pser // -np.inf, psser // -np.inf)
+ pser = pd.Series([np.inf, -np.inf, np.nan, 1.0])
+ psser = ps.from_pandas(pser)
+ self.assert_eq(pser // 2.0, psser // 2.0)
+
+ # Finite operands whose quotient overflows to an infinity, which is
its own floor.
+ edge_pdf = pd.DataFrame({"a": [1e300, -1e300], "b": [1e-300, 1e-300]})
+ edge_psdf = ps.from_pandas(edge_pdf)
+ self.assert_eq(edge_pdf.a // edge_pdf.b, edge_psdf.a // edge_psdf.b)
+
+ # A negative zero divisor negates the result, and a zero dividend
keeps its own sign.
+ pser = pd.Series([1.0, -1.0, 2.5])
+ psser = ps.from_pandas(pser)
+ self.assert_eq(pser // -0.0, psser // -0.0)
+ pser = pd.Series([-0.0, 0.0])
+ psser = ps.from_pandas(pser)
+ self.assert_eq(pser // 3.0, psser // 3.0)
+ # An equality check cannot see the sign of a zero, so compare it
directly.
+ self.assertEqual(
+ np.signbit(pser // 3.0).tolist(), np.signbit((psser //
3.0).to_pandas()).tolist()
+ )
+
+ # The only quotient that does not fit in a long, where pandas wraps
around.
+ pser = pd.Series([-(2**63)])
+ psser = ps.from_pandas(pser)
+ self.assert_eq((pser // -1).astype(float), psser // -1)
+
def test_mod(self):
pdf, psdf = self.pdf, self.psdf
diff --git a/python/pyspark/pandas/tests/data_type_ops/test_num_reverse.py
b/python/pyspark/pandas/tests/data_type_ops/test_num_reverse.py
index f2b85085e7d1..83e52e45f6ce 100644
--- a/python/pyspark/pandas/tests/data_type_ops/test_num_reverse.py
+++ b/python/pyspark/pandas/tests/data_type_ops/test_num_reverse.py
@@ -103,6 +103,28 @@ class ReverseTestsMixin:
self.assertRaises(TypeError, lambda: datetime.date(1994, 1, 1) //
psser)
self.assertRaises(TypeError, lambda: datetime.datetime(1994, 1, 1)
// psser)
+ # A divisor that is not exactly representable: 1.0 / 0.1 rounds up to
exactly 10.0,
+ # so flooring the quotient gives 10 where pandas returns 9.
+ pser = pd.Series([0.1, 0.3, 0.7])
+ psser = ps.from_pandas(pser)
+ self.assert_eq(1.0 // pser, 1.0 // psser)
+
+ # An integral operand above 2**53 loses its low bits when divided as
double. pandas
+ # returns int64 here, which Spark's division cannot, so compare the
values.
+ pser = pd.Series([2, -2])
+ psser = ps.from_pandas(pser)
+ self.assert_eq((9007199254740993 // pser).astype(float),
9007199254740993 // psser)
+
+ # A zero dividend and a zero divisor, which pandas reports as nan.
+ pser = pd.Series([0, 2])
+ psser = ps.from_pandas(pser)
+ self.assert_eq(0 // pser, 0 // psser)
+
+ # A quotient that overflows to an infinity, which is its own floor.
+ pser = pd.Series([1e-300, -1e-300])
+ psser = ps.from_pandas(pser)
+ self.assert_eq(1e300 // pser, 1e300 // psser)
+
def test_rpow(self):
pdf, psdf = self.pdf, self.psdf
for col in self.numeric_df_cols:
diff --git a/python/pyspark/pandas/tests/test_numpy_compat.py
b/python/pyspark/pandas/tests/test_numpy_compat.py
index 13a8594ca011..da4ed34aa121 100644
--- a/python/pyspark/pandas/tests/test_numpy_compat.py
+++ b/python/pyspark/pandas/tests/test_numpy_compat.py
@@ -391,7 +391,7 @@ class NumPyCompatTestsMixin:
self.assert_eq(ps_integral, pd_integral, almost=True)
def test_floor_divide_func(self):
- from pyspark.pandas.numpy_compat import _floor_divide_func
+ from pyspark.pandas.utils import _floor_divide_func
def floor_divided(pdf):
psdf = ps.from_pandas(pdf)
diff --git a/python/pyspark/pandas/utils.py b/python/pyspark/pandas/utils.py
index 7d36f8431b81..6a3eb14daac9 100644
--- a/python/pyspark/pandas/utils.py
+++ b/python/pyspark/pandas/utils.py
@@ -1046,6 +1046,102 @@ def compare_allow_null(
return left.isNull() | right.isNull() | comp(left, right)
+def _floor_divide_floating(c1: Column, c2: Column) -> Column:
+ """Return floor(c1 / c2) for finite non-zero double operands, derived from
the remainder.
+
+ Flooring the quotient is wrong when the division rounds up across an
integer: 1.0 / 0.1
+ rounds to exactly 10.0, so its floor is 10 where NumPy, pandas and Python
return 9. A
+ remainder is exact, so NumPy's npy_divmod derives the quotient from it, as
this does.
+ """
+ remainder = F.try_mod(c1, c2)
+ # The remainder carries the dividend's sign, so this is the truncating
quotient.
+ truncated = (c1 - remainder) / c2
+ # Truncating and flooring differ by one on opposite signs with a remainder
left over.
+ quotient = F.when(
+ (remainder != 0) & ((remainder < 0) != (c2 < 0)), truncated -
F.lit(1.0)
+ ).otherwise(truncated)
+ # The quotient is whole in exact arithmetic, but the division can leave it
a few bits off, so
+ # round it back. F.floor cannot do this: it returns a bigint, which raises
on an infinity.
+ floor = quotient - F.pmod(quotient, F.lit(1.0))
+ return (
+ # An infinite quotient is its own floor, and has to be returned before
the line above
+ # is used, since pmod of an infinity is nan and leaves `floor` nan.
+ F.when(quotient.isin(float("inf"), float("-inf")), quotient)
+ # Flooring goes one too low when the division landed just under the
whole number.
+ .when(quotient - floor > F.lit(0.5), floor + F.lit(1.0))
+ .otherwise(floor)
+ )
+
+
+def _floor_divide_integral(c1: Column, c2: Column) -> Column:
+ """Return floor(c1 / c2) for integral operands, keeping the quotient in
integer space.
+
+ Casting an operand above 2**53 to double drops its low bits, turning
9007199254740993 into
+ 9007199254740992, and Spark's `/` always divides as double. The long casts
are no-ops for
+ the integral types the caller admits; they are there because `div` rejects
a double even in
+ a branch the guard turns off.
+ """
+ c1_long = c1.cast("long")
+ c2_long = c2.cast("long")
+ # `div` is integer division, truncating toward zero, so it needs the same
flooring
+ # correction as the floating helper. Integer arithmetic cannot round, so
nothing more.
+ truncated = F.call_function("div", c1_long, c2_long)
+ remainder = F.try_mod(c1_long, c2_long)
+ return F.when(
+ # The one quotient a long cannot hold, where NumPy wraps around and
`div` would raise.
+ (c1_long == F.lit(-(2**63))) & (c2_long == F.lit(-1)),
+ F.lit(float(-(2**63))),
+ ).otherwise(
+ F.when((remainder != 0) & ((remainder < 0) != (c2_long < 0)),
truncated - F.lit(1))
+ .otherwise(truncated)
+ .cast("double")
+ )
+
+
+def _floor_divide_func(c1: Column, c2: Column) -> Column:
+ c1_double = c1.cast("double")
+ c2_double = c2.cast("double")
+ integral_types = ["tinyint", "smallint", "int", "bigint"]
+
+ return (
+ # Null, nan and a zero divisor are handled the same way for every
operand type.
+ F.when(c1.isNull() | F.isnan(c1), c1_double)
+ .when(c2.isNull() | F.isnan(c2), c2_double)
+ # pandas upcasts a zero divisor instead of raising. A negative zero
divisor negates the
+ # result, and no comparison can see that sign, so the string form is
used. A nullable Int64
+ # returns 0 instead, but arrives as bigint like a default int64, whose
answer this follows.
+ .when(
+ c2_double == 0,
+ F.when(c1_double == 0, F.lit(float("nan")))
+ .when((c1_double < 0) != (c2_double.cast("string") == "-0.0"),
F.lit(float("-inf")))
+ .otherwise(F.lit(float("inf"))),
+ )
+ # Integral operands divide as integers, so operands above 2**53 keep
their low bits.
+ .when(
+ F.typeof(c1).isin(integral_types) &
F.typeof(c2).isin(integral_types),
+ _floor_divide_integral(c1, c2),
+ )
+ # Only floating operands can be infinite or a negative zero, handled
before the division.
+ .when(
+ F.typeof(c1).isin("float", "double") | F.typeof(c2).isin("float",
"double"),
+ # An infinite dividend has no remainder, so NumPy's quotient is
nan for any divisor.
+ F.when(c1_double.isin(float("-inf"), float("inf")),
F.lit(float("nan")))
+ # An infinite divisor gives a quotient between -1 and 1, so the
floor is 0 or -1.
+ .when(
+ c2_double.isin(float("-inf"), float("inf")),
+ F.when(c1_double == 0, c1_double / c2_double)
+ .when((c1_double < 0) != (c2_double < 0), F.lit(-1.0))
+ .otherwise(F.lit(0.0)),
+ )
+ # Dividing a zero dividend keeps its sign, so -0.0 // 3.0 is -0.0.
+ .when(c1_double == 0, c1_double / c2_double)
+ .otherwise(_floor_divide_floating(c1_double, c2_double)),
+ )
+ # A decimal cannot be matched by name: typeof reports its precision,
as in decimal(10,2).
+ .otherwise(_floor_divide_floating(c1_double, c2_double))
+ )
+
+
def log_advice(message: str) -> None:
"""
Display advisory logs for functions to be aware of when using pandas API
on Spark
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]