diff --git a/lib/rdf/datatypes/numeric.ex b/lib/rdf/datatypes/numeric.ex index bc1ae9e..bdfaeb1 100644 --- a/lib/rdf/datatypes/numeric.ex +++ b/lib/rdf/datatypes/numeric.ex @@ -319,6 +319,80 @@ defmodule RDF.Numeric do defp xpath_round(decimal, precision), do: D.round(decimal, precision) + @doc """ + Rounds a numeric literal upwards to a whole number literal. + + If the argument is not a valid numeric literal `nil` is returned. + + see + + """ + def ceil(literal) + + def ceil(%Literal{datatype: @xsd_decimal} = literal) do + if RDF.Decimal.valid?(literal) do + literal.value + |> D.round(0, (if literal.value.sign == -1, do: :down, else: :up)) + |> D.to_integer() + |> RDF.Integer.new() + end + end + + def ceil(%Literal{datatype: @xsd_double, value: value} = literal) + when value in ~w[nan positive_infinity negative_infinity]a, do: literal + + def ceil(%Literal{datatype: @xsd_double} = literal) do + if RDF.Double.valid?(literal) do + literal.value + |> Float.ceil() + |> trunc() + |> RDF.Integer.new() + end + end + + def ceil(%Literal{datatype: datatype} = literal) do + if type?(datatype) and Literal.valid?(literal) do + literal + end + end + + @doc """ + Rounds a numeric literal downwards to a whole number literal. + + If the argument is not a valid numeric literal `nil` is returned. + + see + + """ + def floor(literal) + + def floor(%Literal{datatype: @xsd_decimal} = literal) do + if RDF.Decimal.valid?(literal) do + literal.value + |> D.round(0, (if literal.value.sign == -1, do: :up, else: :down)) + |> D.to_integer() + |> RDF.Integer.new() + end + end + + def floor(%Literal{datatype: @xsd_double, value: value} = literal) + when value in ~w[nan positive_infinity negative_infinity]a, do: literal + + def floor(%Literal{datatype: @xsd_double} = literal) do + if RDF.Double.valid?(literal) do + literal.value + |> Float.floor() + |> trunc() + |> RDF.Integer.new() + end + end + + def floor(%Literal{datatype: datatype} = literal) do + if type?(datatype) and Literal.valid?(literal) do + literal + end + end + defp arithmetic_operation(op, arg1, arg2, fun) do if literal?(arg1) && literal?(arg2) do diff --git a/test/unit/datatypes/numeric_test.exs b/test/unit/datatypes/numeric_test.exs index 7493226..b517017 100644 --- a/test/unit/datatypes/numeric_test.exs +++ b/test/unit/datatypes/numeric_test.exs @@ -409,4 +409,58 @@ defmodule RDF.NumericTest do end end + describe "ceil/1" do + test "with xsd:integer" do + assert RDF.integer(42) |> Numeric.ceil() == RDF.integer(42) + assert RDF.integer(-42) |> Numeric.ceil() == RDF.integer(-42) + end + + test "with xsd:double" do + assert RDF.double(10.5) |> Numeric.ceil() == RDF.integer(11) + assert RDF.double(-10.5) |> Numeric.ceil() == RDF.integer(-10) + + assert RDF.double("INF") |> Numeric.ceil() == RDF.double("INF") + assert RDF.double("-INF") |> Numeric.ceil() == RDF.double("-INF") + assert RDF.double("NAN") |> Numeric.ceil() == RDF.double("NAN") + end + + test "with xsd:decimal" do + assert RDF.decimal(10.5) |> Numeric.ceil() == RDF.integer(11) + assert RDF.decimal(-10.5) |> Numeric.ceil() == RDF.integer(-10) + end + + test "with invalid numeric literals" do + assert RDF.integer("-3.14") |> Numeric.ceil() == nil + assert RDF.double("foo") |> Numeric.ceil() == nil + assert RDF.decimal("foo") |> Numeric.ceil() == nil + end + end + + describe "floor/1" do + test "with xsd:integer" do + assert RDF.integer(42) |> Numeric.floor() == RDF.integer(42) + assert RDF.integer(-42) |> Numeric.floor() == RDF.integer(-42) + end + + test "with xsd:double" do + assert RDF.double(10.5) |> Numeric.floor() == RDF.integer(10) + assert RDF.double(-10.5) |> Numeric.floor() == RDF.integer(-11) + + assert RDF.double("INF") |> Numeric.floor() == RDF.double("INF") + assert RDF.double("-INF") |> Numeric.floor() == RDF.double("-INF") + assert RDF.double("NAN") |> Numeric.floor() == RDF.double("NAN") + end + + test "with xsd:decimal" do + assert RDF.decimal(10.5) |> Numeric.floor() == RDF.integer(10) + assert RDF.decimal(-10.5) |> Numeric.floor() == RDF.integer(-11) + end + + test "with invalid numeric literals" do + assert RDF.integer("-3.14") |> Numeric.floor() == nil + assert RDF.double("foo") |> Numeric.floor() == nil + assert RDF.decimal("foo") |> Numeric.floor() == nil + end + end + end