From 1cd3a0c342792b805c490ca533fab32a7f32f488 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Sun, 16 Sep 2018 15:11:51 +0200 Subject: [PATCH] Support RDF.Term coercion on all arithmetic operations in RDF.Numeric --- lib/rdf/datatypes/numeric.ex | 45 ++++++++++++++++++- test/unit/datatypes/numeric_test.exs | 67 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/lib/rdf/datatypes/numeric.ex b/lib/rdf/datatypes/numeric.ex index 37d5058..8de8333 100644 --- a/lib/rdf/datatypes/numeric.ex +++ b/lib/rdf/datatypes/numeric.ex @@ -9,6 +9,7 @@ defmodule RDF.Numeric do alias Elixir.Decimal, as: D import RDF.Literal.Guards + import Kernel, except: [abs: 1] @types MapSet.new [ @@ -267,6 +268,15 @@ defmodule RDF.Numeric do end end + def abs(value) do + unless RDF.term?(value) do + value + |> RDF.Term.coerce() + |> abs() + end + end + + @doc """ Rounds a value to a specified number of decimal places, rounding upwards if two such values are equally near. @@ -320,6 +330,14 @@ defmodule RDF.Numeric do end end + def round(value, precision) do + unless RDF.term?(value) do + value + |> RDF.Term.coerce() + |> round(precision) + end + end + defp xpath_round(%D{sign: -1} = decimal, precision), do: D.round(decimal, precision, :half_down) defp xpath_round(decimal, precision), @@ -364,6 +382,14 @@ defmodule RDF.Numeric do end end + def ceil(value) do + unless RDF.term?(value) do + value + |> RDF.Term.coerce() + |> ceil() + end + end + @doc """ Rounds a numeric literal downwards to a whole number literal. @@ -403,8 +429,16 @@ defmodule RDF.Numeric do end end + def floor(value) do + unless RDF.term?(value) do + value + |> RDF.Term.coerce() + |> floor() + end + end - defp arithmetic_operation(op, arg1, arg2, fun) do + + defp arithmetic_operation(op, %Literal{} = arg1, %Literal{} = arg2, fun) do if literal?(arg1) && literal?(arg2) do with result_type = result_type(op, arg1.datatype, arg2.datatype), {arg1, arg2} = type_conversion(arg1, arg2, result_type), @@ -416,6 +450,15 @@ defmodule RDF.Numeric do end end + defp arithmetic_operation(op, %Literal{} = arg1, arg2, fun), + do: arithmetic_operation(op, arg1, RDF.Term.coerce(arg2), fun) + + defp arithmetic_operation(op, arg1, %Literal{} = arg2, fun), + do: arithmetic_operation(op, RDF.Term.coerce(arg1), arg2, fun) + + defp arithmetic_operation(op, arg1, arg2, fun), + do: arithmetic_operation(op, RDF.Term.coerce(arg1), RDF.Term.coerce(arg2), fun) + defp type_conversion(%Literal{datatype: datatype} = arg1, %Literal{value: arg2}, datatype) when is_xsd_decimal(datatype), diff --git a/test/unit/datatypes/numeric_test.exs b/test/unit/datatypes/numeric_test.exs index b73d8ca..cd649d8 100644 --- a/test/unit/datatypes/numeric_test.exs +++ b/test/unit/datatypes/numeric_test.exs @@ -3,6 +3,7 @@ defmodule RDF.NumericTest do alias RDF.Numeric alias RDF.NS.XSD + alias Decimal, as: D @positive_infinity RDF.double(:positive_infinity) @negative_infinity RDF.double(:negative_infinity) @@ -97,6 +98,13 @@ defmodule RDF.NumericTest do assert Numeric.add(@positive_infinity, @negative_infinity) == RDF.double(:nan) assert Numeric.add(@negative_infinity, @positive_infinity) == RDF.double(:nan) end + + test "coercion" do + assert Numeric.add(1, 2) == RDF.integer(3) + assert Numeric.add(3.14, 42) == RDF.double(45.14) + assert RDF.decimal(3.14) |> Numeric.add(42) == RDF.decimal(45.14) + assert Numeric.add(42, RDF.decimal(3.14)) == RDF.decimal(45.14) + end end @@ -144,6 +152,13 @@ defmodule RDF.NumericTest do assert Numeric.subtract(@positive_infinity, @negative_infinity) == @positive_infinity assert Numeric.subtract(@negative_infinity, @positive_infinity) == @negative_infinity end + + test "coercion" do + assert Numeric.subtract(2, 1) == RDF.integer(1) + assert Numeric.subtract(42, 3.14) == RDF.double(38.86) + assert RDF.decimal(3.14) |> Numeric.subtract(42) == RDF.decimal(-38.86) + assert Numeric.subtract(42, RDF.decimal(3.14)) == RDF.decimal(38.86) + end end @@ -206,6 +221,13 @@ defmodule RDF.NumericTest do assert Numeric.multiply(@positive_infinity, @negative_infinity) == RDF.double(:nan) assert Numeric.multiply(@negative_infinity, @positive_infinity) == RDF.double(:nan) end + + test "coercion" do + assert Numeric.multiply(1, 2) == RDF.integer(2) + assert Numeric.multiply(2, 1.5) == RDF.double(3.0) + assert RDF.decimal(1.5) |> Numeric.multiply(2) == RDF.decimal(3.0) + assert Numeric.multiply(2, RDF.decimal(1.5)) == RDF.decimal(3.0) + end end @@ -309,6 +331,15 @@ defmodule RDF.NumericTest do # TODO: What happens when using INF/-INF on division with numbers? + test "coercion" do + assert Numeric.divide(4, 2) == RDF.decimal(2.0) + assert Numeric.divide(4, 2.0) == RDF.double(2.0) + assert RDF.decimal(4) |> Numeric.divide(2) == RDF.decimal(2.0) + assert Numeric.divide(4, RDF.decimal(2.0)) == RDF.decimal(2.0) + assert Numeric.divide("foo", "bar") == nil + assert Numeric.divide(4, "bar") == nil + assert Numeric.divide("foo", 2) == nil + end end describe "abs/1" do @@ -345,6 +376,14 @@ defmodule RDF.NumericTest do assert RDF.double("foo") |> Numeric.abs() == nil assert RDF.decimal("foo") |> Numeric.abs() == nil end + + test "coercion" do + assert Numeric.abs(42) == RDF.integer(42) + assert Numeric.abs(-42) == RDF.integer(42) + assert Numeric.abs(-3.14) == RDF.double(3.14) + assert Numeric.abs(D.new(-3.14)) == RDF.decimal(3.14) + assert Numeric.abs("foo") == nil + end end describe "round/1" do @@ -374,6 +413,13 @@ defmodule RDF.NumericTest do assert RDF.double("foo") |> Numeric.round() == nil assert RDF.decimal("foo") |> Numeric.round() == nil end + + test "coercion" do + assert Numeric.round(-42) == RDF.integer(-42) + assert Numeric.round(-3.14) == RDF.double(-3.0) + assert Numeric.round(D.new(3.14)) == RDF.decimal("3") + assert Numeric.round("foo") == nil + end end describe "round/2" do @@ -407,6 +453,13 @@ defmodule RDF.NumericTest do assert RDF.double("foo") |> Numeric.round(2) == nil assert RDF.decimal("foo") |> Numeric.round(3) == nil end + + test "coercion" do + assert Numeric.round(-42, 1) == RDF.integer(-42) + assert Numeric.round(-3.14, 1) == RDF.double(-3.1) + assert Numeric.round(D.new(3.14), 1) == RDF.decimal("3.1") + assert Numeric.round("foo", 1) == nil + end end describe "ceil/1" do @@ -434,6 +487,13 @@ defmodule RDF.NumericTest do assert RDF.double("foo") |> Numeric.ceil() == nil assert RDF.decimal("foo") |> Numeric.ceil() == nil end + + test "coercion" do + assert Numeric.ceil(-42) == RDF.integer(-42) + assert Numeric.ceil(-3.14) == RDF.double("-3") + assert Numeric.ceil(D.new(3.14)) == RDF.decimal("4") + assert Numeric.ceil("foo") == nil + end end describe "floor/1" do @@ -461,6 +521,13 @@ defmodule RDF.NumericTest do assert RDF.double("foo") |> Numeric.floor() == nil assert RDF.decimal("foo") |> Numeric.floor() == nil end + + test "coercion" do + assert Numeric.floor(-42) == RDF.integer(-42) + assert Numeric.floor(-3.14) == RDF.double("-4") + assert Numeric.floor(D.new(3.14)) == RDF.decimal("3") + assert Numeric.floor("foo") == nil + end end end