Add proper handling for derived datatypes on numeric operations

with type promotion
This commit is contained in:
Marcel Otto 2020-05-17 21:33:12 +02:00
parent bf932be26d
commit 09dceb0e35
3 changed files with 236 additions and 150 deletions

View file

@ -5,11 +5,12 @@ defmodule RDF.XSD.Numeric do
@type t :: module @type t :: module
alias RDF.{XSD, Literal}
alias Elixir.Decimal, as: D alias Elixir.Decimal, as: D
import Kernel, except: [abs: 1, floor: 1, ceil: 1] import Kernel, except: [abs: 1, floor: 1, ceil: 1]
defdelegate datatype?(value), to: RDF.Literal.Datatype.Registry, as: :numeric_datatype? defdelegate datatype?(value), to: Literal.Datatype.Registry, as: :numeric_datatype?
@doc """ @doc """
Tests for numeric value equality of two numeric XSD datatyped literals. Tests for numeric value equality of two numeric XSD datatyped literals.
@ -21,8 +22,8 @@ defmodule RDF.XSD.Numeric do
""" """
@spec equal_value?(t() | any, t() | any) :: boolean @spec equal_value?(t() | any, t() | any) :: boolean
def equal_value?(left, right) def equal_value?(left, right)
def equal_value?(left, %RDF.Literal{literal: right}), do: equal_value?(left, right) def equal_value?(left, %Literal{literal: right}), do: equal_value?(left, right)
def equal_value?(%RDF.Literal{literal: left}, right), do: equal_value?(left, right) def equal_value?(%Literal{literal: left}, right), do: equal_value?(left, right)
def equal_value?(nil, _), do: nil def equal_value?(nil, _), do: nil
def equal_value?(_, nil), do: nil def equal_value?(_, nil), do: nil
@ -34,7 +35,7 @@ defmodule RDF.XSD.Numeric do
end end
def equal_value?(%left_datatype{value: left}, %right_datatype{value: right}) def equal_value?(%left_datatype{value: left}, %right_datatype{value: right})
when left_datatype == RDF.XSD.Decimal or right_datatype == RDF.XSD.Decimal, when left_datatype == XSD.Decimal or right_datatype == XSD.Decimal,
do: not is_nil(left) and not is_nil(right) and equal_decimal_value?(left, right) do: not is_nil(left) and not is_nil(right) and equal_decimal_value?(left, right)
def equal_value?(%left_datatype{value: left}, %right_datatype{value: right}) do def equal_value?(%left_datatype{value: left}, %right_datatype{value: right}) do
@ -44,7 +45,7 @@ defmodule RDF.XSD.Numeric do
end end
def equal_value?(left, right), def equal_value?(left, right),
do: equal_value?(RDF.Literal.coerce(left), RDF.Literal.coerce(right)) do: equal_value?(Literal.coerce(left), Literal.coerce(right))
defp equal_decimal_value?(%D{} = left, %D{} = right), do: D.equal?(left, right) defp equal_decimal_value?(%D{} = left, %D{} = right), do: D.equal?(left, right)
@ -68,13 +69,13 @@ defmodule RDF.XSD.Numeric do
Returns `nil` when the given arguments are not comparable datatypes. Returns `nil` when the given arguments are not comparable datatypes.
""" """
@spec compare(RDF.Literal.t | t, RDF.Literal.t | t) :: RDF.XSD.Datatype.comparison_result() | nil @spec compare(Literal.t | t, Literal.t | t) :: XSD.Datatype.comparison_result() | nil
def compare(left, right) def compare(left, right)
def compare(left, %RDF.Literal{literal: right}), do: compare(left, right) def compare(left, %Literal{literal: right}), do: compare(left, right)
def compare(%RDF.Literal{literal: left}, right), do: compare(left, right) def compare(%Literal{literal: left}, right), do: compare(left, right)
def compare( def compare(
%RDF.XSD.Decimal{value: left}, %XSD.Decimal{value: left},
%right_datatype{value: right} %right_datatype{value: right}
) do ) do
if datatype?(right_datatype) do if datatype?(right_datatype) do
@ -84,7 +85,7 @@ defmodule RDF.XSD.Numeric do
def compare( def compare(
%left_datatype{value: left}, %left_datatype{value: left},
%RDF.XSD.Decimal{value: right} %XSD.Decimal{value: right}
) do ) do
if datatype?(left_datatype) do if datatype?(left_datatype) do
compare_decimal_value(left, right) compare_decimal_value(left, right)
@ -118,14 +119,14 @@ defmodule RDF.XSD.Numeric do
defp compare_decimal_value(_, _), do: nil defp compare_decimal_value(_, _), do: nil
@spec zero?(any) :: boolean @spec zero?(any) :: boolean
def zero?(%RDF.Literal{literal: literal}), do: zero?(literal) def zero?(%Literal{literal: literal}), do: zero?(literal)
def zero?(%{value: value}), do: zero_value?(value) def zero?(%{value: value}), do: zero_value?(value)
defp zero_value?(zero) when zero == 0, do: true defp zero_value?(zero) when zero == 0, do: true
defp zero_value?(%D{coef: 0}), do: true defp zero_value?(%D{coef: 0}), do: true
defp zero_value?(_), do: false defp zero_value?(_), do: false
@spec negative_zero?(any) :: boolean @spec negative_zero?(any) :: boolean
def negative_zero?(%RDF.Literal{literal: literal}), do: negative_zero?(literal) def negative_zero?(%Literal{literal: literal}), do: negative_zero?(literal)
def negative_zero?(%{value: zero, uncanonical_lexical: "-" <> _}) when zero == 0, do: true def negative_zero?(%{value: zero, uncanonical_lexical: "-" <> _}) when zero == 0, do: true
def negative_zero?(%{value: %D{sign: -1, coef: 0}}), do: true def negative_zero?(%{value: %D{sign: -1, coef: 0}}), do: true
def negative_zero?(_), do: false def negative_zero?(_), do: false
@ -254,7 +255,7 @@ defmodule RDF.XSD.Numeric do
arg1, arg2, result_type -> arg1, arg2, result_type ->
if zero_value?(arg2) do if zero_value?(arg2) do
cond do cond do
result_type not in [RDF.XSD.Double, RDF.XSD.Float] -> nil result_type not in [XSD.Double, XSD.Float] -> nil
zero_value?(arg1) -> :nan zero_value?(arg1) -> :nan
negative_zero and arg1 < 0 -> :positive_infinity negative_zero and arg1 < 0 -> :positive_infinity
negative_zero -> :negative_infinity negative_zero -> :negative_infinity
@ -278,22 +279,13 @@ defmodule RDF.XSD.Numeric do
""" """
def abs(literal) def abs(literal)
def abs(%RDF.Literal{literal: literal}), do: abs(literal) def abs(%Literal{literal: literal}), do: abs(literal)
def abs(%RDF.XSD.Decimal{} = literal) do
if RDF.XSD.Decimal.valid?(literal) do
literal.value
|> D.abs()
|> RDF.XSD.Decimal.new()
end
end
def abs(nil), do: nil def abs(nil), do: nil
def abs(value) do def abs(value) do
cond do cond do
datatype?(value) -> datatype?(value) ->
if RDF.Literal.Datatype.valid?(value) do if Literal.Datatype.valid?(value) do
%datatype{} = value %datatype{} = value
case value.value do case value.value do
@ -304,21 +296,29 @@ defmodule RDF.XSD.Numeric do
literal(value) literal(value)
:negative_infinity -> :negative_infinity ->
datatype.new(:positive_infinity) datatype.base_primitive().new(:positive_infinity)
%D{} = value ->
value
|> D.abs()
|> datatype.base_primitive().new()
value -> value ->
target_datatype = if XSD.Float.datatype?(datatype),
do: XSD.Float, else: datatype.base_primitive()
value value
|> Kernel.abs() |> Kernel.abs()
|> datatype.new() |> target_datatype.new()
end end
end end
RDF.Literal.datatype?(value) -> # non-numeric datatypes
Literal.datatype?(value) ->
nil nil
true -> true ->
value value
|> RDF.Literal.coerce() |> Literal.coerce()
|> abs() |> abs()
end end
end end
@ -340,55 +340,56 @@ defmodule RDF.XSD.Numeric do
""" """
def round(literal, precision \\ 0) def round(literal, precision \\ 0)
def round(%RDF.Literal{literal: literal}, precision), do: round(literal, precision) def round(%Literal{literal: literal}, precision), do: round(literal, precision)
def round(%RDF.XSD.Decimal{} = literal, precision) do
if RDF.XSD.Decimal.valid?(literal) do
literal.value
|> xpath_round(precision)
|> to_string()
|> RDF.XSD.Decimal.new()
end
end
def round(%datatype{value: value} = datatype_literal, _)
when datatype in [RDF.XSD.Double, RDF.XSD.Float] and
value in ~w[nan positive_infinity negative_infinity]a,
do: literal(datatype_literal)
def round(%datatype{} = literal, precision) when datatype in [RDF.XSD.Double, RDF.XSD.Float] do
if datatype.valid?(literal) do
literal.value
|> new_decimal()
|> xpath_round(precision)
|> D.to_float()
|> datatype.new()
end
end
def round(nil, _), do: nil def round(nil, _), do: nil
def round(value, precision) do def round(value, precision) do
cond do cond do
datatype?(value) -> datatype?(value) ->
if RDF.Literal.Datatype.valid?(value) do if Literal.Datatype.valid?(value) do
if precision < 0 do %datatype{value: literal_value} = value
value.value
|> new_decimal() cond do
|> xpath_round(precision) XSD.Integer.datatype?(datatype) ->
|> D.to_integer() if precision < 0 do
|> RDF.XSD.Integer.new() literal_value
else |> new_decimal()
literal(value) |> xpath_round(precision)
|> D.to_integer()
|> XSD.Integer.new()
else
literal(value)
end
XSD.Decimal.datatype?(datatype) ->
literal_value
|> xpath_round(precision)
|> to_string()
|> XSD.Decimal.new()
(float_datatype = XSD.Float.datatype?(datatype)) or
XSD.Double.datatype?(datatype) ->
if literal_value in ~w[nan positive_infinity negative_infinity]a do
literal(value)
else
target_datatype = if float_datatype, do: XSD.Float, else: XSD.Double
literal_value
|> new_decimal()
|> xpath_round(precision)
|> D.to_float()
|> target_datatype.new()
end
end end
end end
RDF.Literal.datatype?(value) -> # non-numeric datatypes
Literal.datatype?(value) ->
nil nil
true -> true ->
value value
|> RDF.Literal.coerce() |> Literal.coerce()
|> round(precision) |> round(precision)
end end
end end
@ -410,47 +411,48 @@ defmodule RDF.XSD.Numeric do
""" """
def ceil(literal) def ceil(literal)
def ceil(%RDF.Literal{literal: literal}), do: ceil(literal) def ceil(%Literal{literal: literal}), do: ceil(literal)
def ceil(%RDF.XSD.Decimal{} = literal) do
if RDF.XSD.Decimal.valid?(literal) do
literal.value
|> D.round(0, if(literal.value.sign == -1, do: :down, else: :up))
|> D.to_string()
|> RDF.XSD.Decimal.new()
end
end
def ceil(%datatype{value: value} = datatype_literal)
when datatype in [RDF.XSD.Double, RDF.XSD.Float] and
value in ~w[nan positive_infinity negative_infinity]a,
do: literal(datatype_literal)
def ceil(%datatype{} = literal) when datatype in [RDF.XSD.Double, RDF.XSD.Float] do
if datatype.valid?(literal) do
literal.value
|> Float.ceil()
|> trunc()
|> to_string()
|> datatype.new()
end
end
def ceil(nil), do: nil def ceil(nil), do: nil
def ceil(value) do def ceil(value) do
cond do cond do
datatype?(value) -> datatype?(value) ->
if RDF.Literal.Datatype.valid?(value) do if Literal.Datatype.valid?(value) do
literal(value) %datatype{value: literal_value} = value
cond do
XSD.Integer.datatype?(datatype) ->
literal(value)
XSD.Decimal.datatype?(datatype) ->
literal_value
|> D.round(0, if(literal_value.sign == -1, do: :down, else: :up))
|> D.to_string()
|> XSD.Decimal.new()
(float_datatype = XSD.Float.datatype?(datatype)) or
XSD.Double.datatype?(datatype) ->
if literal_value in ~w[nan positive_infinity negative_infinity]a do
literal(value)
else
target_datatype = if float_datatype, do: XSD.Float, else: XSD.Double
literal_value
|> Float.ceil()
|> trunc()
|> to_string()
|> target_datatype.new()
end
end
end end
RDF.Literal.datatype?(value) -> # non-numeric datatypes
Literal.datatype?(value) ->
nil nil
true -> true ->
value value
|> RDF.Literal.coerce() |> Literal.coerce()
|> ceil() |> ceil()
end end
end end
@ -466,51 +468,54 @@ defmodule RDF.XSD.Numeric do
""" """
def floor(literal) def floor(literal)
def floor(%RDF.Literal{literal: literal}), do: floor(literal) def floor(%Literal{literal: literal}), do: floor(literal)
def floor(%RDF.XSD.Decimal{} = literal) do
if RDF.XSD.Decimal.valid?(literal) do
literal.value
|> D.round(0, if(literal.value.sign == -1, do: :up, else: :down))
|> D.to_string()
|> RDF.XSD.Decimal.new()
end
end
def floor(%datatype{value: value} = datatype_literal)
when datatype in [RDF.XSD.Double, RDF.XSD.Float] and
value in ~w[nan positive_infinity negative_infinity]a,
do: literal(datatype_literal)
def floor(%datatype{} = literal) when datatype in [RDF.XSD.Double, RDF.XSD.Float] do
if datatype.valid?(literal) do
literal.value
|> Float.floor()
|> trunc()
|> to_string()
|> datatype.new()
end
end
def floor(nil), do: nil def floor(nil), do: nil
def floor(value) do def floor(value) do
cond do cond do
datatype?(value) -> datatype?(value) ->
if RDF.Literal.Datatype.valid?(value), do: literal(value) if Literal.Datatype.valid?(value) do
%datatype{value: literal_value} = value
RDF.Literal.datatype?(value) -> cond do
XSD.Integer.datatype?(datatype) ->
literal(value)
XSD.Decimal.datatype?(datatype) ->
literal_value
|> D.round(0, if(literal_value.sign == -1, do: :up, else: :down))
|> D.to_string()
|> XSD.Decimal.new()
(float_datatype = XSD.Float.datatype?(datatype)) or
XSD.Double.datatype?(datatype) ->
if literal_value in ~w[nan positive_infinity negative_infinity]a do
literal(value)
else
target_datatype = if float_datatype, do: XSD.Float, else: XSD.Double
literal_value
|> Float.floor()
|> trunc()
|> to_string()
|> target_datatype.new()
end
end
end
# non-numeric datatypes
Literal.datatype?(value) ->
nil nil
true -> true ->
value value
|> RDF.Literal.coerce() |> Literal.coerce()
|> floor() |> floor()
end end
end end
defp arithmetic_operation(op, %RDF.Literal{literal: literal1}, literal2, fun), do: arithmetic_operation(op, literal1, literal2, fun) defp arithmetic_operation(op, %Literal{literal: literal1}, literal2, fun), do: arithmetic_operation(op, literal1, literal2, fun)
defp arithmetic_operation(op, literal1, %RDF.Literal{literal: literal2}, fun), do: arithmetic_operation(op, literal1, literal2, fun) defp arithmetic_operation(op, literal1, %Literal{literal: literal2}, fun), do: arithmetic_operation(op, literal1, literal2, fun)
defp arithmetic_operation(op, %datatype1{} = literal1, %datatype2{} = literal2, fun) do defp arithmetic_operation(op, %datatype1{} = literal1, %datatype2{} = literal2, fun) do
if datatype?(datatype1) and datatype?(datatype2) do if datatype?(datatype1) and datatype?(datatype2) do
result_type = result_type(op, datatype1, datatype2) result_type = result_type(op, datatype1, datatype2)
@ -524,36 +529,36 @@ defmodule RDF.XSD.Numeric do
cond do cond do
is_nil(left) -> nil is_nil(left) -> nil
is_nil(right) -> nil is_nil(right) -> nil
not RDF.Literal.datatype?(left) -> arithmetic_operation(op, RDF.Literal.coerce(left), right, fun) not Literal.datatype?(left) -> arithmetic_operation(op, Literal.coerce(left), right, fun)
not RDF.Literal.datatype?(right) -> arithmetic_operation(op, left, RDF.Literal.coerce(right), fun) not Literal.datatype?(right) -> arithmetic_operation(op, left, Literal.coerce(right), fun)
true -> false true -> false
end end
end end
defp type_conversion(%RDF.XSD.Decimal{} = left_decimal, %{value: right_value}, RDF.XSD.Decimal), defp type_conversion(%XSD.Decimal{} = left_decimal, %{value: right_value}, XSD.Decimal),
do: {left_decimal, RDF.XSD.Decimal.new(right_value).literal} do: {left_decimal, XSD.Decimal.new(right_value).literal}
defp type_conversion(%{value: left_value}, %RDF.XSD.Decimal{} = right_decimal, RDF.XSD.Decimal), defp type_conversion(%{value: left_value}, %XSD.Decimal{} = right_decimal, XSD.Decimal),
do: {RDF.XSD.Decimal.new(left_value).literal, right_decimal} do: {XSD.Decimal.new(left_value).literal, right_decimal}
defp type_conversion(%RDF.XSD.Decimal{value: left_decimal}, right, datatype) defp type_conversion(%XSD.Decimal{value: left_decimal}, right, datatype)
when datatype in [RDF.XSD.Double, RDF.XSD.Float], when datatype in [XSD.Double, XSD.Float],
do: {(left_decimal |> D.to_float() |> RDF.XSD.Double.new()).literal, right} do: {(left_decimal |> D.to_float() |> XSD.Double.new()).literal, right}
defp type_conversion(left, %RDF.XSD.Decimal{value: right_decimal}, datatype) defp type_conversion(left, %XSD.Decimal{value: right_decimal}, datatype)
when datatype in [RDF.XSD.Double, RDF.XSD.Float], when datatype in [XSD.Double, XSD.Float],
do: {left, (right_decimal |> D.to_float() |> RDF.XSD.Double.new()).literal} do: {left, (right_decimal |> D.to_float() |> XSD.Double.new()).literal}
defp type_conversion(left, right, _), do: {left, right} defp type_conversion(left, right, _), do: {left, right}
defp result_type(_, RDF.XSD.Double, _), do: RDF.XSD.Double defp result_type(_, XSD.Double, _), do: XSD.Double
defp result_type(_, _, RDF.XSD.Double), do: RDF.XSD.Double defp result_type(_, _, XSD.Double), do: XSD.Double
defp result_type(_, RDF.XSD.Float, _), do: RDF.XSD.Float defp result_type(_, XSD.Float, _), do: XSD.Float
defp result_type(_, _, RDF.XSD.Float), do: RDF.XSD.Float defp result_type(_, _, XSD.Float), do: XSD.Float
defp result_type(_, RDF.XSD.Decimal, _), do: RDF.XSD.Decimal defp result_type(_, XSD.Decimal, _), do: XSD.Decimal
defp result_type(_, _, RDF.XSD.Decimal), do: RDF.XSD.Decimal defp result_type(_, _, XSD.Decimal), do: XSD.Decimal
defp result_type(:/, _, _), do: RDF.XSD.Decimal defp result_type(:/, _, _), do: XSD.Decimal
defp result_type(_, _, _), do: RDF.XSD.Integer defp result_type(_, _, _), do: XSD.Integer
defp literal(value), do: %RDF.Literal{literal: value} defp literal(value), do: %Literal{literal: value}
end end

View file

@ -7,4 +7,34 @@ defmodule RDF.TestDatatypes do
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 150 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 150
end end
defmodule DecimalUnitInterval do
use RDF.XSD.Datatype.Restriction,
name: "decimal_unit_interval",
id: "http://example.com/decimalUnitInterval",
base: RDF.XSD.Decimal
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 1
end
defmodule DoubleUnitInterval do
use RDF.XSD.Datatype.Restriction,
name: "double_unit_interval",
id: "http://example.com/doubleUnitInterval",
base: RDF.XSD.Double
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 1
end
defmodule FloatUnitInterval do
use RDF.XSD.Datatype.Restriction,
name: "float_unit_interval",
id: "http://example.com/floatUnitInterval",
base: RDF.XSD.Float
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 1
end
end end

View file

@ -3,6 +3,7 @@ defmodule RDF.XSD.NumericTest do
alias RDF.XSD alias RDF.XSD
alias XSD.Numeric alias XSD.Numeric
alias RDF.TestDatatypes.{Age, DecimalUnitInterval, DoubleUnitInterval, FloatUnitInterval}
alias Decimal, as: D alias Decimal, as: D
@ -59,16 +60,26 @@ defmodule RDF.XSD.NumericTest do
describe "add/2" do describe "add/2" do
test "xsd:integer literal + xsd:integer literal" do test "xsd:integer literal + xsd:integer literal" do
assert Numeric.add(XSD.integer(1), XSD.integer(2)) == XSD.integer(3) assert Numeric.add(XSD.integer(1), XSD.integer(2)) == XSD.integer(3)
assert Numeric.add(XSD.integer(1), XSD.byte(2)) == XSD.integer(3)
assert Numeric.add(XSD.byte(1), XSD.integer(2)) == XSD.integer(3)
assert Numeric.add(XSD.integer(1), Age.new(2)) == XSD.integer(3)
end end
test "xsd:decimal literal + xsd:integer literal" do test "xsd:decimal literal + xsd:integer literal" do
assert Numeric.add(XSD.decimal(1.1), XSD.integer(2)) == XSD.decimal(3.1) assert Numeric.add(XSD.decimal(1.1), XSD.integer(2)) == XSD.decimal(3.1)
assert Numeric.add(XSD.decimal(1.1), XSD.positiveInteger(2)) == XSD.decimal(3.1)
assert Numeric.add(XSD.decimal(1.1), Age.new(2)) == XSD.decimal(3.1)
assert Numeric.add(XSD.positiveInteger(2), XSD.decimal(1.1)) == XSD.decimal(3.1)
end end
test "xsd:double literal + xsd:integer literal" do test "xsd:double literal + xsd:integer literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.double(1.1), XSD.integer(2)) assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.double(1.1), XSD.integer(2))
assert_in_delta RDF.Literal.value(result), assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(3.1)), 0.000000000000001 RDF.Literal.value(XSD.double(3.1)), 0.000000000000001
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.double(1.1), Age.new(2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(3.1)), 0.000000000000001
end end
test "xsd:decimal literal + xsd:double literal" do test "xsd:decimal literal + xsd:double literal" do
@ -99,6 +110,7 @@ defmodule RDF.XSD.NumericTest do
assert Numeric.add(@negative_infinity, XSD.double(3.14)) == @negative_infinity assert Numeric.add(@negative_infinity, XSD.double(3.14)) == @negative_infinity
assert Numeric.add(XSD.double(0), @negative_infinity) == @negative_infinity assert Numeric.add(XSD.double(0), @negative_infinity) == @negative_infinity
assert Numeric.add(XSD.double(3.14), @negative_infinity) == @negative_infinity assert Numeric.add(XSD.double(3.14), @negative_infinity) == @negative_infinity
assert Numeric.add(@negative_infinity, Age.new(0)) == @negative_infinity
end end
test "if both operands are INF, INF is returned" do test "if both operands are INF, INF is returned" do
@ -130,10 +142,13 @@ defmodule RDF.XSD.NumericTest do
describe "subtract/2" do describe "subtract/2" do
test "xsd:integer literal - xsd:integer literal" do test "xsd:integer literal - xsd:integer literal" do
assert Numeric.subtract(XSD.integer(3), XSD.integer(2)) == XSD.integer(1) assert Numeric.subtract(XSD.integer(3), XSD.integer(2)) == XSD.integer(1)
assert Numeric.subtract(XSD.integer(3), XSD.short(2)) == XSD.integer(1)
assert Numeric.subtract(XSD.integer(3), Age.new(2)) == XSD.integer(1)
end end
test "xsd:decimal literal - xsd:integer literal" do test "xsd:decimal literal - xsd:integer literal" do
assert Numeric.subtract(XSD.decimal(3.3), XSD.integer(2)) == XSD.decimal(1.3) assert Numeric.subtract(XSD.decimal(3.3), XSD.integer(2)) == XSD.decimal(1.3)
assert Numeric.subtract(XSD.decimal(3.3), XSD.positiveInteger(2)) == XSD.decimal(1.3)
end end
test "xsd:double literal - xsd:integer literal" do test "xsd:double literal - xsd:integer literal" do
@ -181,6 +196,7 @@ defmodule RDF.XSD.NumericTest do
describe "multiply/2" do describe "multiply/2" do
test "xsd:integer literal * xsd:integer literal" do test "xsd:integer literal * xsd:integer literal" do
assert Numeric.multiply(XSD.integer(2), XSD.integer(3)) == XSD.integer(6) assert Numeric.multiply(XSD.integer(2), XSD.integer(3)) == XSD.integer(6)
assert Numeric.multiply(Age.new(2), XSD.integer(3)) == XSD.integer(6)
end end
test "xsd:decimal literal * xsd:integer literal" do test "xsd:decimal literal * xsd:integer literal" do
@ -247,6 +263,7 @@ defmodule RDF.XSD.NumericTest do
describe "divide/2" do describe "divide/2" do
test "xsd:integer literal / xsd:integer literal" do test "xsd:integer literal / xsd:integer literal" do
assert Numeric.divide(XSD.integer(4), XSD.integer(2)) == XSD.decimal(2.0) assert Numeric.divide(XSD.integer(4), XSD.integer(2)) == XSD.decimal(2.0)
assert Numeric.divide(XSD.integer(4), Age.new(2)) == XSD.decimal(2.0)
end end
test "xsd:decimal literal / xsd:integer literal" do test "xsd:decimal literal / xsd:integer literal" do
@ -374,11 +391,13 @@ defmodule RDF.XSD.NumericTest do
assert XSD.decimal(-3.14) |> Numeric.abs() == XSD.decimal(3.14) assert XSD.decimal(-3.14) |> Numeric.abs() == XSD.decimal(3.14)
end end
@tag skip: "TODO: type-promotion"
test "with derived numerics" do test "with derived numerics" do
assert XSD.byte(-42) |> Numeric.abs() == XSD.byte(42) assert XSD.byte(-42) |> Numeric.abs() == XSD.integer(42)
assert XSD.byte("-42") |> Numeric.abs() == XSD.byte(42) assert XSD.byte("-42") |> Numeric.abs() == XSD.integer(42)
assert XSD.non_positive_integer(-42) |> Numeric.abs() == XSD.integer(42) assert XSD.non_positive_integer(-42) |> Numeric.abs() == XSD.integer(42)
assert DecimalUnitInterval.new(0.14) |> Numeric.abs() == XSD.decimal(0.14)
assert DoubleUnitInterval.new(0.14) |> Numeric.abs() == XSD.double(0.14)
assert FloatUnitInterval.new(0.14) |> Numeric.abs() == XSD.float(0.14)
end end
test "with invalid numeric literals" do test "with invalid numeric literals" do
@ -419,6 +438,14 @@ defmodule RDF.XSD.NumericTest do
assert XSD.decimal(-2.5) |> Numeric.round() == XSD.decimal("-2") assert XSD.decimal(-2.5) |> Numeric.round() == XSD.decimal("-2")
end end
test "with derived numerics" do
assert XSD.byte(42) |> Numeric.round() == XSD.byte(42)
assert XSD.non_positive_integer(-42) |> Numeric.round() == XSD.non_positive_integer(-42)
assert DecimalUnitInterval.new(0.14) |> Numeric.round() == XSD.decimal("0")
assert DoubleUnitInterval.new(0.14) |> Numeric.round() == XSD.double(0)
assert FloatUnitInterval.new(0.14) |> Numeric.round() == XSD.float(0)
end
test "with invalid numeric literals" do test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.round() == nil assert XSD.integer("-3.14") |> Numeric.round() == nil
assert XSD.double("foo") |> Numeric.round() == nil assert XSD.double("foo") |> Numeric.round() == nil
@ -466,6 +493,14 @@ defmodule RDF.XSD.NumericTest do
assert XSD.decimal(-2.55) |> Numeric.round(1) == XSD.decimal("-2.5") assert XSD.decimal(-2.55) |> Numeric.round(1) == XSD.decimal("-2.5")
end end
test "with derived numerics" do
assert XSD.byte(42) |> Numeric.round(2) == XSD.byte(42)
assert XSD.non_positive_integer(-42) |> Numeric.round(2) == XSD.non_positive_integer(-42)
assert DecimalUnitInterval.new(0.14) |> Numeric.round(1) == XSD.decimal(0.1)
assert DoubleUnitInterval.new(0.14) |> Numeric.round(1) == XSD.double(0.1)
assert FloatUnitInterval.new(0.14) |> Numeric.round(1) == XSD.float(0.1)
end
test "with invalid numeric literals" do test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.round(1) == nil assert XSD.integer("-3.14") |> Numeric.round(1) == nil
assert XSD.double("foo") |> Numeric.round(2) == nil assert XSD.double("foo") |> Numeric.round(2) == nil
@ -510,6 +545,14 @@ defmodule RDF.XSD.NumericTest do
assert XSD.decimal(-10.5) |> Numeric.ceil() == XSD.decimal("-10") assert XSD.decimal(-10.5) |> Numeric.ceil() == XSD.decimal("-10")
end end
test "with derived numerics" do
assert XSD.byte(42) |> Numeric.ceil() == XSD.byte(42)
assert XSD.non_positive_integer(-42) |> Numeric.ceil() == XSD.non_positive_integer(-42)
assert DoubleUnitInterval.new(0.14) |> Numeric.ceil() == XSD.double("1")
assert DoubleUnitInterval.new(0.4) |> Numeric.ceil() == XSD.double("1")
assert FloatUnitInterval.new(0.5) |> Numeric.ceil() == XSD.float("1")
end
test "with invalid numeric literals" do test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.ceil() == nil assert XSD.integer("-3.14") |> Numeric.ceil() == nil
assert XSD.double("foo") |> Numeric.ceil() == nil assert XSD.double("foo") |> Numeric.ceil() == nil
@ -554,6 +597,14 @@ defmodule RDF.XSD.NumericTest do
assert XSD.decimal(-10.5) |> Numeric.floor() == XSD.decimal("-11") assert XSD.decimal(-10.5) |> Numeric.floor() == XSD.decimal("-11")
end end
test "with derived numerics" do
assert XSD.byte(42) |> Numeric.floor() == XSD.byte(42)
assert XSD.non_positive_integer(-42) |> Numeric.floor() == XSD.non_positive_integer(-42)
assert DoubleUnitInterval.new(0.14) |> Numeric.floor() == XSD.double("0")
assert DoubleUnitInterval.new(0.4) |> Numeric.floor() == XSD.double("0")
assert FloatUnitInterval.new(0.5) |> Numeric.floor() == XSD.float("0")
end
test "with invalid numeric literals" do test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.floor() == nil assert XSD.integer("-3.14") |> Numeric.floor() == nil
assert XSD.double("foo") |> Numeric.floor() == nil assert XSD.double("foo") |> Numeric.floor() == nil