Support RDF.Term coercion on all arithmetic operations in RDF.Numeric

This commit is contained in:
Marcel Otto 2018-09-16 15:11:51 +02:00
parent d72c5ebd1f
commit 1cd3a0c342
2 changed files with 111 additions and 1 deletions

View file

@ -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),

View file

@ -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