From fa130bf14ec74d33edbec73dc82ca68a0b69e749 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Tue, 19 May 2020 03:09:29 +0200 Subject: [PATCH] Fix type promotion on numeric operations with derived datatypes --- lib/rdf/xsd/datatypes/numeric.ex | 66 +++++--- test/unit/xsd/datatypes/numeric_test.exs | 199 +++++++++++++++++++++++ 2 files changed, 245 insertions(+), 20 deletions(-) diff --git a/lib/rdf/xsd/datatypes/numeric.ex b/lib/rdf/xsd/datatypes/numeric.ex index 7078bde..c1b27aa 100644 --- a/lib/rdf/xsd/datatypes/numeric.ex +++ b/lib/rdf/xsd/datatypes/numeric.ex @@ -535,30 +535,56 @@ defmodule RDF.XSD.Numeric do end end - defp type_conversion(%XSD.Decimal{} = left_decimal, %{value: right_value}, XSD.Decimal), - do: {left_decimal, XSD.Decimal.new(right_value).literal} + defp type_conversion(left, right, XSD.Decimal) do + { + 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), - do: {XSD.Decimal.new(left_value).literal, right_decimal} - - defp type_conversion(%XSD.Decimal{value: left_decimal}, right, datatype) - when datatype in [XSD.Double, XSD.Float], - do: {(left_decimal |> D.to_float() |> XSD.Double.new()).literal, right} - - defp type_conversion(left, %XSD.Decimal{value: right_decimal}, datatype) - when datatype in [XSD.Double, XSD.Float], - do: {left, (right_decimal |> D.to_float() |> XSD.Double.new()).literal} + defp type_conversion(left, right, datatype) when datatype in [XSD.Double, XSD.Float] do + { + if XSD.Decimal.datatype?(left) do + (left.value |> D.to_float() |> XSD.Double.new()).literal + else + left + end, + if XSD.Decimal.datatype?(right) do + (right.value |> D.to_float() |> XSD.Double.new()).literal + else + right + end + } + end defp type_conversion(left, right, _), do: {left, right} - defp result_type(_, XSD.Double, _), do: XSD.Double - defp result_type(_, _, XSD.Double), do: XSD.Double - defp result_type(_, XSD.Float, _), do: XSD.Float - defp result_type(_, _, XSD.Float), do: XSD.Float - defp result_type(_, XSD.Decimal, _), do: XSD.Decimal - defp result_type(_, _, XSD.Decimal), do: XSD.Decimal - defp result_type(:/, _, _), do: XSD.Decimal - defp result_type(_, _, _), do: XSD.Integer + @doc false + def result_type(op, left, right), do: do_result_type(op, base_primitive(left), base_primitive(right)) + + defp do_result_type(_, XSD.Double, _), do: XSD.Double + defp do_result_type(_, _, XSD.Double), do: XSD.Double + defp do_result_type(_, XSD.Float, _), do: XSD.Float + defp do_result_type(_, _, XSD.Float), do: XSD.Float + 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} end diff --git a/test/unit/xsd/datatypes/numeric_test.exs b/test/unit/xsd/datatypes/numeric_test.exs index d9666af..2d0918b 100644 --- a/test/unit/xsd/datatypes/numeric_test.exs +++ b/test/unit/xsd/datatypes/numeric_test.exs @@ -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), 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.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 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_in_delta RDF.Literal.value(result), 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 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_in_delta RDF.Literal.value(result), 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 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_in_delta RDF.Literal.value(result), 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 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_in_delta RDF.Literal.value(result), 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 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 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(Age.new(4), Age.new(2)) == XSD.decimal(2.0) end test "xsd:decimal literal / xsd:integer literal" do @@ -619,4 +640,182 @@ defmodule RDF.XSD.NumericTest do assert Numeric.floor(:foo) == nil 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