Add RDF.Numeric.round/1 and RDF.Numeric.round/2

This commit is contained in:
Marcel Otto 2018-07-13 23:14:20 +02:00
parent ae54a8bfe2
commit cb9585d87a
2 changed files with 127 additions and 3 deletions

View file

@ -47,6 +47,12 @@ defmodule RDF.Numeric do
def literal?(%Literal{datatype: datatype}), do: type?(datatype) def literal?(%Literal{datatype: datatype}), do: type?(datatype)
def literal?(_), do: false def literal?(_), do: false
# @doc """
# Returns if a given datatype is a numeric datatype for integers.
# """
# def integer_type?(type), do: MapSet.member?(@integer_types, type)
@doc """ @doc """
Tests for numeric value equality of two numeric literals. Tests for numeric value equality of two numeric literals.
@ -257,6 +263,62 @@ defmodule RDF.Numeric do
end end
end end
@doc """
Rounds a value to a specified number of decimal places, rounding upwards if two such values are equally near.
The function returns the nearest (that is, numerically closest) value to the
given literal value that is a multiple of ten to the power of minus `precision`.
If two such values are equally near (for example, if the fractional part in the
literal value is exactly .5), the function returns the one that is closest to
positive infinity.
If the argument is not a valid numeric literal `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-round>
"""
def round(literal, precision \\ 0)
def round(%Literal{datatype: @xsd_decimal} = literal, precision) do
if RDF.Decimal.valid?(literal) do
literal.value
|> xpath_round(precision)
|> RDF.Decimal.new()
end
end
def round(%Literal{datatype: @xsd_double, value: value} = literal, _)
when value in ~w[nan positive_infinity negative_infinity]a, do: literal
def round(%Literal{datatype: @xsd_double} = literal, precision) do
if RDF.Double.valid?(literal) do
literal.value
|> D.new()
|> xpath_round(precision)
|> D.to_float()
|> RDF.Double.new()
end
end
def round(%Literal{datatype: datatype} = literal, precision) do
if type?(datatype) and Literal.valid?(literal) do
if precision < 0 do
literal.value
|> D.new()
|> xpath_round(precision)
|> D.to_integer()
|> RDF.Integer.new()
else
literal
end
end
end
defp xpath_round(%D{sign: -1} = decimal, precision),
do: D.round(decimal, precision, :half_down)
defp xpath_round(decimal, precision),
do: D.round(decimal, precision)
defp arithmetic_operation(op, arg1, arg2, fun) do defp arithmetic_operation(op, arg1, arg2, fun) do
if literal?(arg1) && literal?(arg2) do if literal?(arg1) && literal?(arg2) do

View file

@ -335,15 +335,77 @@ defmodule RDF.NumericTest do
assert RDF.literal(-42, datatype: XSD.byte) |> Numeric.abs() == assert RDF.literal(-42, datatype: XSD.byte) |> Numeric.abs() ==
RDF.literal(42, datatype: XSD.byte) RDF.literal(42, datatype: XSD.byte)
assert RDF.literal("-42", datatype: XSD.byte) |> Numeric.abs() == assert RDF.literal("-42", datatype: XSD.byte) |> Numeric.abs() ==
RDF.literal(42, datatype: XSD.byte) RDF.literal(42, datatype: XSD.byte)
assert RDF.literal(-42, datatype: XSD.nonPositiveInteger) assert RDF.literal(-42, datatype: XSD.nonPositiveInteger)
|> Numeric.abs() == RDF.integer(42) |> Numeric.abs() == RDF.integer(42)
end end
test "with invalid numeric literals" do test "with invalid numeric literals" do
assert RDF.integer("-3.14") |> Numeric.abs() == nil assert RDF.integer("-3.14") |> Numeric.abs() == nil
assert RDF.double("foo") |> Numeric.abs() == nil assert RDF.double("foo") |> Numeric.abs() == nil
assert RDF.decimal("foo") |> Numeric.abs() == nil assert RDF.decimal("foo") |> Numeric.abs() == nil
end
end
describe "round/1" do
test "with xsd:integer" do
assert RDF.integer(42) |> Numeric.round() == RDF.integer(42)
assert RDF.integer(-42) |> Numeric.round() == RDF.integer(-42)
end
test "with xsd:double" do
assert RDF.double(3.14) |> Numeric.round() == RDF.double(3.0)
assert RDF.double(-3.14) |> Numeric.round() == RDF.double(-3.0)
assert RDF.double(-2.5) |> Numeric.round() == RDF.double(-2.0)
assert RDF.double("INF") |> Numeric.round() == RDF.double("INF")
assert RDF.double("-INF") |> Numeric.round() == RDF.double("-INF")
assert RDF.double("NAN") |> Numeric.round() == RDF.double("NAN")
end
test "with xsd:decimal" do
assert RDF.decimal(2.5) |> Numeric.round() == RDF.decimal(3.0)
assert RDF.decimal(2.4999) |> Numeric.round() == RDF.decimal(2.0)
assert RDF.decimal(-2.5) |> Numeric.round() == RDF.decimal(-2.0)
end
test "with invalid numeric literals" do
assert RDF.integer("-3.14") |> Numeric.round() == nil
assert RDF.double("foo") |> Numeric.round() == nil
assert RDF.decimal("foo") |> Numeric.round() == nil
end
end
describe "round/2" do
test "with xsd:integer" do
assert RDF.integer(42) |> Numeric.round(3) == RDF.integer(42)
assert RDF.integer(8452) |> Numeric.round(-2) == RDF.integer(8500)
assert RDF.integer(85) |> Numeric.round(-1) == RDF.integer(90)
assert RDF.integer(-85) |> Numeric.round(-1) == RDF.integer(-80)
end
@tag skip: "TODO: xsd:float"
test "with xsd:float"
test "with xsd:double" do
assert RDF.double(3.14) |> Numeric.round(1) == RDF.double(3.1)
assert RDF.double(3.1415e0) |> Numeric.round(2) == RDF.double(3.14e0)
assert RDF.double("INF") |> Numeric.round(1) == RDF.double("INF")
assert RDF.double("-INF") |> Numeric.round(2) == RDF.double("-INF")
assert RDF.double("NAN") |> Numeric.round(3) == RDF.double("NAN")
end
test "with xsd:decimal" do
assert RDF.decimal(1.125) |> Numeric.round(2) == RDF.decimal(1.13)
assert RDF.decimal(2.4999) |> Numeric.round(2) == RDF.decimal(2.5)
assert RDF.decimal(-2.55) |> Numeric.round(1) == RDF.decimal(-2.5)
end
test "with invalid numeric literals" do
assert RDF.integer("-3.14") |> Numeric.round(1) == nil
assert RDF.double("foo") |> Numeric.round(2) == nil
assert RDF.decimal("foo") |> Numeric.round(3) == nil
end end
end end