2017-04-16 21:13:39 +00:00
|
|
|
defmodule RDF.Double do
|
2017-06-09 22:18:39 +00:00
|
|
|
@moduledoc """
|
|
|
|
`RDF.Datatype` for XSD double.
|
|
|
|
"""
|
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
use RDF.Datatype, id: RDF.Datatype.NS.XSD.double
|
|
|
|
|
2018-09-09 13:28:35 +00:00
|
|
|
import RDF.Literal.Guards
|
|
|
|
|
2017-04-26 00:48:49 +00:00
|
|
|
|
|
|
|
def build_literal_by_value(value, opts) do
|
|
|
|
case convert(value, opts) do
|
|
|
|
float when is_float(float) ->
|
|
|
|
build_literal(float, decimal_form(float), opts)
|
|
|
|
nil ->
|
|
|
|
build_literal(nil, invalid_lexical(value), opts)
|
|
|
|
special_value when is_atom(special_value) ->
|
|
|
|
build_literal(special_value, nil, opts)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-17 00:08:16 +00:00
|
|
|
@impl RDF.Datatype
|
|
|
|
def convert(value, opts)
|
2017-04-26 00:48:49 +00:00
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
def convert(value, _) when is_float(value), do: value
|
2017-04-26 00:48:49 +00:00
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
def convert(value, _) when is_integer(value), do: value / 1
|
2017-04-26 00:48:49 +00:00
|
|
|
|
|
|
|
def convert(value, opts) when is_binary(value) do
|
|
|
|
case Float.parse(value) do
|
|
|
|
{float, ""} ->
|
|
|
|
float
|
|
|
|
{float, remainder} ->
|
|
|
|
# 1.E-8 is not a valid Elixir float literal and consequently not fully parsed with Float.parse
|
|
|
|
if Regex.match?(~r/^\.e?[\+\-]?\d+$/i, remainder) do
|
|
|
|
convert(to_string(float) <> String.trim_leading(remainder, "."), opts)
|
|
|
|
else
|
|
|
|
super(value, opts)
|
|
|
|
end
|
|
|
|
:error ->
|
|
|
|
case String.upcase(value) do
|
|
|
|
"INF" -> :positive_infinity
|
|
|
|
"+INF" -> :positive_infinity
|
|
|
|
"-INF" -> :negative_infinity
|
|
|
|
"NAN" -> :nan
|
|
|
|
_ -> super(value, opts)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def convert(value, _)
|
|
|
|
when value in ~W[positive_infinity negative_infinity nan]a,
|
|
|
|
do: value
|
|
|
|
|
|
|
|
def convert(value, opts), do: super(value, opts)
|
|
|
|
|
|
|
|
|
2018-09-17 00:08:16 +00:00
|
|
|
@impl RDF.Datatype
|
|
|
|
def canonical_lexical(value)
|
|
|
|
|
2018-06-15 18:56:40 +00:00
|
|
|
def canonical_lexical(:nan), do: "NaN"
|
|
|
|
def canonical_lexical(:positive_infinity), do: "INF"
|
|
|
|
def canonical_lexical(:negative_infinity), do: "-INF"
|
|
|
|
def canonical_lexical(float) when is_float(float), do: exponential_form(float)
|
|
|
|
def canonical_lexical(value), do: to_string(value)
|
|
|
|
|
2017-04-26 00:48:49 +00:00
|
|
|
|
2018-09-17 00:08:42 +00:00
|
|
|
defp decimal_form(float) when is_float(float) do
|
2017-04-26 00:48:49 +00:00
|
|
|
to_string(float)
|
|
|
|
end
|
|
|
|
|
2018-09-17 00:08:42 +00:00
|
|
|
defp exponential_form(float) when is_float(float) do
|
2017-04-26 00:48:49 +00:00
|
|
|
# Can't use simple %f transformation due to special requirements from
|
|
|
|
# N3 tests in representation
|
|
|
|
[i, f, e] =
|
2018-06-30 10:14:30 +00:00
|
|
|
float
|
|
|
|
|> float_to_string()
|
2017-04-26 00:48:49 +00:00
|
|
|
|> String.split(~r/[\.e]/)
|
|
|
|
f =
|
|
|
|
case String.replace(f, ~r/0*$/, "", global: false) do # remove any trailing zeroes
|
|
|
|
"" -> "0" # ...but there must be a digit to the right of the decimal point
|
|
|
|
f -> f
|
|
|
|
end
|
|
|
|
e = String.trim_leading(e, "+")
|
|
|
|
"#{i}.#{f}E#{e}"
|
|
|
|
end
|
2017-04-16 21:13:39 +00:00
|
|
|
|
2018-06-30 10:14:30 +00:00
|
|
|
if List.to_integer(:erlang.system_info(:otp_release)) >= 21 do
|
|
|
|
defp float_to_string(float) do
|
|
|
|
:io_lib.format("~.15e", [float])
|
|
|
|
|> to_string()
|
|
|
|
end
|
|
|
|
else
|
|
|
|
defp float_to_string(float) do
|
|
|
|
:io_lib.format("~.15e", [float])
|
|
|
|
|> List.first
|
|
|
|
|> to_string()
|
|
|
|
end
|
|
|
|
end
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2018-09-09 13:28:35 +00:00
|
|
|
|
2018-09-17 00:08:16 +00:00
|
|
|
@impl RDF.Datatype
|
|
|
|
def cast(literal)
|
2018-09-09 13:28:35 +00:00
|
|
|
|
|
|
|
def cast(%RDF.Literal{datatype: datatype} = literal) do
|
|
|
|
cond do
|
|
|
|
not RDF.Literal.valid?(literal) ->
|
|
|
|
nil
|
|
|
|
|
|
|
|
is_xsd_double(datatype) ->
|
|
|
|
literal
|
|
|
|
|
|
|
|
literal == RDF.false ->
|
|
|
|
new(0.0)
|
|
|
|
|
|
|
|
literal == RDF.true ->
|
|
|
|
new(1.0)
|
|
|
|
|
|
|
|
is_xsd_string(datatype) ->
|
|
|
|
literal.value
|
|
|
|
|> new()
|
|
|
|
|> canonical()
|
2018-09-14 15:02:04 +00:00
|
|
|
|> validate_cast()
|
2018-09-09 13:28:35 +00:00
|
|
|
|
|
|
|
is_xsd_decimal(datatype) ->
|
|
|
|
literal.value
|
|
|
|
|> Decimal.to_float()
|
|
|
|
|> new()
|
|
|
|
|
|
|
|
is_xsd_integer(datatype) or is_xsd_float(datatype) ->
|
|
|
|
new(literal.value)
|
|
|
|
|
|
|
|
true ->
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-14 16:00:43 +00:00
|
|
|
def cast(_), do: nil
|
|
|
|
|
2018-09-09 13:28:35 +00:00
|
|
|
|
2018-09-17 00:08:16 +00:00
|
|
|
@impl RDF.Datatype
|
2018-06-08 10:26:52 +00:00
|
|
|
def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right)
|
|
|
|
|
2018-11-02 21:00:48 +00:00
|
|
|
@impl RDF.Datatype
|
|
|
|
def compare(left, right), do: RDF.Numeric.compare(left, right)
|
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
end
|