Fix type promotion on numeric operations with derived datatypes

This commit is contained in:
Marcel Otto 2020-05-19 03:09:29 +02:00
parent 22c2aaa1af
commit fa130bf14e
2 changed files with 245 additions and 20 deletions

View file

@ -535,30 +535,56 @@ defmodule RDF.XSD.Numeric do
end end
end end
defp type_conversion(%XSD.Decimal{} = left_decimal, %{value: right_value}, XSD.Decimal), defp type_conversion(left, right, XSD.Decimal) do
do: {left_decimal, XSD.Decimal.new(right_value).literal} {
if XSD.Decimal.datatype?(left) do
left
else
XSD.Decimal.new(left.value).literal
end,
if XSD.Decimal.datatype?(right) do
right
else
XSD.Decimal.new(right.value).literal
end
}
end
defp type_conversion(%{value: left_value}, %XSD.Decimal{} = right_decimal, XSD.Decimal), defp type_conversion(left, right, datatype) when datatype in [XSD.Double, XSD.Float] do
do: {XSD.Decimal.new(left_value).literal, right_decimal} {
if XSD.Decimal.datatype?(left) do
defp type_conversion(%XSD.Decimal{value: left_decimal}, right, datatype) (left.value |> D.to_float() |> XSD.Double.new()).literal
when datatype in [XSD.Double, XSD.Float], else
do: {(left_decimal |> D.to_float() |> XSD.Double.new()).literal, right} left
end,
defp type_conversion(left, %XSD.Decimal{value: right_decimal}, datatype) if XSD.Decimal.datatype?(right) do
when datatype in [XSD.Double, XSD.Float], (right.value |> D.to_float() |> XSD.Double.new()).literal
do: {left, (right_decimal |> D.to_float() |> XSD.Double.new()).literal} else
right
end
}
end
defp type_conversion(left, right, _), do: {left, right} defp type_conversion(left, right, _), do: {left, right}
defp result_type(_, XSD.Double, _), do: XSD.Double @doc false
defp result_type(_, _, XSD.Double), do: XSD.Double def result_type(op, left, right), do: do_result_type(op, base_primitive(left), base_primitive(right))
defp result_type(_, XSD.Float, _), do: XSD.Float
defp result_type(_, _, XSD.Float), do: XSD.Float defp do_result_type(_, XSD.Double, _), do: XSD.Double
defp result_type(_, XSD.Decimal, _), do: XSD.Decimal defp do_result_type(_, _, XSD.Double), do: XSD.Double
defp result_type(_, _, XSD.Decimal), do: XSD.Decimal defp do_result_type(_, XSD.Float, _), do: XSD.Float
defp result_type(:/, _, _), do: XSD.Decimal defp do_result_type(_, _, XSD.Float), do: XSD.Float
defp result_type(_, _, _), do: XSD.Integer defp do_result_type(_, XSD.Decimal, _), do: XSD.Decimal
defp do_result_type(_, _, XSD.Decimal), do: XSD.Decimal
defp do_result_type(:/, _, _), do: XSD.Decimal
defp do_result_type(_, _, _), do: XSD.Integer
defp base_primitive(datatype) do
primitive = datatype.base_primitive()
if primitive == XSD.Double and XSD.Float.datatype?(datatype),
do: XSD.Float,
else: primitive
end
defp literal(value), do: %Literal{literal: value} defp literal(value), do: %Literal{literal: value}
end end

View file

@ -70,6 +70,8 @@ defmodule RDF.XSD.NumericTest do
assert Numeric.add(XSD.decimal(1.1), XSD.positiveInteger(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.decimal(1.1), Age.new(2)) == XSD.decimal(3.1)
assert Numeric.add(XSD.positiveInteger(2), XSD.decimal(1.1)) == XSD.decimal(3.1) assert Numeric.add(XSD.positiveInteger(2), XSD.decimal(1.1)) == XSD.decimal(3.1)
assert Numeric.add(XSD.decimal(1.5), Age.new(3)) == XSD.decimal(4.5)
assert Numeric.add(DecimalUnitInterval.new(0.5), Age.new(3)) == XSD.decimal(3.5)
end end
test "xsd:double literal + xsd:integer literal" do test "xsd:double literal + xsd:integer literal" do
@ -80,24 +82,42 @@ defmodule RDF.XSD.NumericTest do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.double(1.1), Age.new(2)) assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.double(1.1), Age.new(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(DoubleUnitInterval.new(0.5), Age.new(2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(2.5)), 0.000000000000001
end end
test "xsd:decimal literal + xsd:double literal" do test "xsd:decimal literal + xsd:double literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.decimal(1.1), XSD.double(2.2)) assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.decimal(1.1), XSD.double(2.2))
assert_in_delta RDF.Literal.value(result), assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(3.3)), 0.000000000000001 RDF.Literal.value(XSD.double(3.3)), 0.000000000000001
assert result = %RDF.Literal{literal: %XSD.Double{}} =
Numeric.add(DecimalUnitInterval.new(0.5), DoubleUnitInterval.new(0.5))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(1.0)), 0.000000000000001
end end
test "xsd:float literal + xsd:integer literal" do test "xsd:float literal + xsd:integer literal" do
assert result = %RDF.Literal{literal: %XSD.Float{}} = Numeric.add(XSD.float(1.1), XSD.integer(2)) assert result = %RDF.Literal{literal: %XSD.Float{}} = Numeric.add(XSD.float(1.1), XSD.integer(2))
assert_in_delta RDF.Literal.value(result), assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.float(3.1)), 0.000000000000001 RDF.Literal.value(XSD.float(3.1)), 0.000000000000001
assert result = %RDF.Literal{literal: %XSD.Float{}} =
Numeric.add(Age.new(42), FloatUnitInterval.new(0.5))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.float(42.5)), 0.000000000000001
end end
test "xsd:decimal literal + xsd:float literal" do test "xsd:decimal literal + xsd:float literal" do
assert result = %RDF.Literal{literal: %XSD.Float{}} = Numeric.add(XSD.decimal(1.1), XSD.float(2.2)) assert result = %RDF.Literal{literal: %XSD.Float{}} = Numeric.add(XSD.decimal(1.1), XSD.float(2.2))
assert_in_delta RDF.Literal.value(result), assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.float(3.3)), 0.000000000000001 RDF.Literal.value(XSD.float(3.3)), 0.000000000000001
assert result = %RDF.Literal{literal: %XSD.Float{}} =
Numeric.add(DecimalUnitInterval.new(0.5), FloatUnitInterval.new(0.5))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.float(1.0)), 0.000000000000001
end end
test "if one of the operands is a zero or a finite number and the other is INF or -INF, INF or -INF is returned" do test "if one of the operands is a zero or a finite number and the other is INF or -INF, INF or -INF is returned" do
@ -264,6 +284,7 @@ defmodule RDF.XSD.NumericTest 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) assert Numeric.divide(XSD.integer(4), Age.new(2)) == XSD.decimal(2.0)
assert Numeric.divide(Age.new(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
@ -619,4 +640,182 @@ defmodule RDF.XSD.NumericTest do
assert Numeric.floor(:foo) == nil assert Numeric.floor(:foo) == nil
end end
end end
test "result_type/3 (type-promotion)" do
%{
XSD.Integer => %{
XSD.Integer => XSD.Integer,
XSD.NonPositiveInteger => XSD.Integer,
XSD.NegativeInteger => XSD.Integer,
XSD.Long => XSD.Integer,
XSD.Int => XSD.Integer,
XSD.Short => XSD.Integer,
XSD.Byte => XSD.Integer,
XSD.NonNegativeInteger => XSD.Integer,
XSD.UnsignedLong => XSD.Integer,
XSD.UnsignedInt => XSD.Integer,
XSD.UnsignedShort => XSD.Integer,
XSD.UnsignedByte => XSD.Integer,
XSD.PositiveInteger => XSD.Integer,
XSD.Decimal => XSD.Decimal,
XSD.Float => XSD.Float,
XSD.Double => XSD.Double,
DecimalUnitInterval => XSD.Decimal,
FloatUnitInterval => XSD.Float,
DoubleUnitInterval => XSD.Double,
},
XSD.Byte => %{
XSD.Integer => XSD.Integer,
XSD.NonPositiveInteger => XSD.Integer,
XSD.NegativeInteger => XSD.Integer,
XSD.Long => XSD.Integer,
XSD.Int => XSD.Integer,
XSD.Short => XSD.Integer,
XSD.Byte => XSD.Integer,
XSD.NonNegativeInteger => XSD.Integer,
XSD.UnsignedLong => XSD.Integer,
XSD.UnsignedInt => XSD.Integer,
XSD.UnsignedShort => XSD.Integer,
XSD.UnsignedByte => XSD.Integer,
XSD.PositiveInteger => XSD.Integer,
XSD.Decimal => XSD.Decimal,
XSD.Float => XSD.Float,
XSD.Double => XSD.Double,
DecimalUnitInterval => XSD.Decimal,
FloatUnitInterval => XSD.Float,
DoubleUnitInterval => XSD.Double,
},
XSD.Decimal => %{
XSD.Integer => XSD.Decimal,
XSD.NonPositiveInteger => XSD.Decimal,
XSD.NegativeInteger => XSD.Decimal,
XSD.Long => XSD.Decimal,
XSD.Int => XSD.Decimal,
XSD.Short => XSD.Decimal,
XSD.Byte => XSD.Decimal,
XSD.NonNegativeInteger => XSD.Decimal,
XSD.UnsignedLong => XSD.Decimal,
XSD.UnsignedInt => XSD.Decimal,
XSD.UnsignedShort => XSD.Decimal,
XSD.UnsignedByte => XSD.Decimal,
XSD.PositiveInteger => XSD.Decimal,
XSD.Decimal => XSD.Decimal,
XSD.Float => XSD.Float,
XSD.Double => XSD.Double,
DecimalUnitInterval => XSD.Decimal,
FloatUnitInterval => XSD.Float,
DoubleUnitInterval => XSD.Double,
},
DecimalUnitInterval => %{
XSD.Integer => XSD.Decimal,
XSD.NonPositiveInteger => XSD.Decimal,
XSD.NegativeInteger => XSD.Decimal,
XSD.Long => XSD.Decimal,
XSD.Int => XSD.Decimal,
XSD.Short => XSD.Decimal,
XSD.Byte => XSD.Decimal,
XSD.NonNegativeInteger => XSD.Decimal,
XSD.UnsignedLong => XSD.Decimal,
XSD.UnsignedInt => XSD.Decimal,
XSD.UnsignedShort => XSD.Decimal,
XSD.UnsignedByte => XSD.Decimal,
XSD.PositiveInteger => XSD.Decimal,
XSD.Decimal => XSD.Decimal,
XSD.Float => XSD.Float,
XSD.Double => XSD.Double,
DecimalUnitInterval => XSD.Decimal,
FloatUnitInterval => XSD.Float,
DoubleUnitInterval => XSD.Double,
},
XSD.Float => %{
XSD.Integer => XSD.Float,
XSD.NonPositiveInteger => XSD.Float,
XSD.NegativeInteger => XSD.Float,
XSD.Long => XSD.Float,
XSD.Int => XSD.Float,
XSD.Short => XSD.Float,
XSD.Byte => XSD.Float,
XSD.NonNegativeInteger => XSD.Float,
XSD.UnsignedLong => XSD.Float,
XSD.UnsignedInt => XSD.Float,
XSD.UnsignedShort => XSD.Float,
XSD.UnsignedByte => XSD.Float,
XSD.PositiveInteger => XSD.Float,
XSD.Decimal => XSD.Float,
XSD.Float => XSD.Float,
XSD.Double => XSD.Double,
DecimalUnitInterval => XSD.Float,
FloatUnitInterval => XSD.Float,
DoubleUnitInterval => XSD.Double,
},
FloatUnitInterval => %{
XSD.Integer => XSD.Float,
XSD.NonPositiveInteger => XSD.Float,
XSD.NegativeInteger => XSD.Float,
XSD.Long => XSD.Float,
XSD.Int => XSD.Float,
XSD.Short => XSD.Float,
XSD.Byte => XSD.Float,
XSD.NonNegativeInteger => XSD.Float,
XSD.UnsignedLong => XSD.Float,
XSD.UnsignedInt => XSD.Float,
XSD.UnsignedShort => XSD.Float,
XSD.UnsignedByte => XSD.Float,
XSD.PositiveInteger => XSD.Float,
XSD.Decimal => XSD.Float,
XSD.Float => XSD.Float,
XSD.Double => XSD.Double,
DecimalUnitInterval => XSD.Float,
FloatUnitInterval => XSD.Float,
DoubleUnitInterval => XSD.Double,
},
XSD.Double => %{
XSD.Integer => XSD.Double,
XSD.NonPositiveInteger => XSD.Double,
XSD.NegativeInteger => XSD.Double,
XSD.Long => XSD.Double,
XSD.Int => XSD.Double,
XSD.Short => XSD.Double,
XSD.Byte => XSD.Double,
XSD.NonNegativeInteger => XSD.Double,
XSD.UnsignedLong => XSD.Double,
XSD.UnsignedInt => XSD.Double,
XSD.UnsignedShort => XSD.Double,
XSD.UnsignedByte => XSD.Double,
XSD.PositiveInteger => XSD.Double,
XSD.Decimal => XSD.Double,
XSD.Float => XSD.Double,
XSD.Double => XSD.Double,
DecimalUnitInterval => XSD.Double,
FloatUnitInterval => XSD.Double,
DoubleUnitInterval => XSD.Double,
},
DoubleUnitInterval => %{
XSD.Integer => XSD.Double,
XSD.NonPositiveInteger => XSD.Double,
XSD.NegativeInteger => XSD.Double,
XSD.Long => XSD.Double,
XSD.Int => XSD.Double,
XSD.Short => XSD.Double,
XSD.Byte => XSD.Double,
XSD.NonNegativeInteger => XSD.Double,
XSD.UnsignedLong => XSD.Double,
XSD.UnsignedInt => XSD.Double,
XSD.UnsignedShort => XSD.Double,
XSD.UnsignedByte => XSD.Double,
XSD.PositiveInteger => XSD.Double,
XSD.Decimal => XSD.Double,
XSD.Float => XSD.Double,
XSD.Double => XSD.Double,
DecimalUnitInterval => XSD.Double,
FloatUnitInterval => XSD.Double,
DoubleUnitInterval => XSD.Double,
},
}
|> Enum.each(fn {left, right_result} ->
Enum.each(right_result, fn {right, result} ->
assert Numeric.result_type(:+, left, right) == result
end)
end)
end
end end