Add RDF.Numeric.round/1 and RDF.Numeric.round/2
This commit is contained in:
parent
ae54a8bfe2
commit
cb9585d87a
2 changed files with 127 additions and 3 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue