-
Notifications
You must be signed in to change notification settings - Fork 98
/
decimal.rb
138 lines (129 loc) · 4.43 KB
/
decimal.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
module RDF; class Literal
##
# A decimal literal.
#
# @example Arithmetic with decimal literals
# RDF::Literal(BigDecimal('1.0')) + 0.5 #=> RDF::Literal(BigDecimal('1.5'))
# RDF::Literal(BigDecimal('1.0')) - 0.5 #=> RDF::Literal(BigDecimal('0.5'))
# RDF::Literal(BigDecimal('1.0')) * 0.5 #=> RDF::Literal(BigDecimal('0.5'))
# RDF::Literal(BigDecimal('1.0')) / 0.5 #=> RDF::Literal(BigDecimal('2.0'))
#
# @see http://www.w3.org/TR/xmlschema11-2/#decimal
# @since 0.2.1
class Decimal < Numeric
DATATYPE = RDF::URI("http://www.w3.org/2001/XMLSchema#decimal")
GRAMMAR = /^[\+\-]?\d+(\.\d*)?$/.freeze
##
# @param [String, BidDecimal, Numeric] value
# @param (see Literal#initialize)
def initialize(value, datatype: nil, lexical: nil, **options)
@datatype = RDF::URI(datatype || self.class.const_get(:DATATYPE))
@string = lexical || (value if value.is_a?(String))
@object = case
when value.is_a?(::BigDecimal) then value
when value.is_a?(::Float) then BigDecimal(value.to_s)
when value.is_a?(::Numeric) then BigDecimal(value)
else
value = value.to_s
value += "0" if value.end_with?(".") # Normalization required in Ruby 2.4
BigDecimal(value) rescue BigDecimal(0)
end
end
##
# Converts this literal into its canonical lexical representation.
#
# @return [RDF::Literal] `self`
# @see http://www.w3.org/TR/xmlschema11-2/#decimal
def canonicalize!
# Can't use simple %f transformation due to special requirements from
# N3 tests in representation
@string = begin
i, f = @object.to_s('F').split('.')
i.sub!(/^\+?0+(\d)$/, '\1') # remove the optional leading '+' sign and any extra leading zeroes
f = f[0, 16] # truncate the fractional part after 15 decimal places
f.sub!(/0*$/, '') # remove any trailing zeroes
f = '0' if f.empty? # ...but there must be a digit to the right of the decimal point
"#{i}.#{f}"
end
@object = BigDecimal(@string) unless @object.nil?
self
end
##
# Returns the absolute value of `self`.
#
# From the XQuery function [fn:abs](https://www.w3.org/TR/xpath-functions/#func-abs).
#
# @return [RDF::Literal]
# @see https://www.w3.org/TR/xpath-functions/#func-abs
# @since 0.2.3
def abs
(d = to_d) && d > 0 ? self : RDF::Literal(d.abs)
end
##
# Returns the number with no fractional part that is closest to the argument. If there are two such numbers, then the one that is closest to positive infinity is returned. An error is raised if arg is not a numeric value.
#
# From the XQuery function [fn:round](https://www.w3.org/TR/xpath-functions/#func-round).
#
# @return [RDF::Literal::Decimal]
# @see https://www.w3.org/TR/xpath-functions/#func-round
def round
rounded = to_d.round(half: (to_d < 0 ? :down : :up))
if rounded == -0.0
# to avoid -0.0
self.class.new(0.0)
else
self.class.new(rounded)
end
end
##
# Returns the smallest integer greater than or equal to `self`.
#
# From the XQuery function [fn:ceil](https://www.w3.org/TR/xpath-functions/#func-ceil).
#
# @example
# RDF::Literal(1).ceil #=> RDF::Literal(1)
#
# @return [RDF::Literal::Integer]
# @see https://www.w3.org/TR/xpath-functions/#func-ceil
def ceil
RDF::Literal::Integer.new(to_d.ceil)
end
##
# Returns the largest integer less than or equal to `self`.
#
# From the XQuery function [fn:floor](https://www.w3.org/TR/xpath-functions/#func-floor).
#
# @example
# RDF::Literal(1).floor #=> RDF::Literal(1)
#
# @return [RDF::Literal::Integer]
# @see https://www.w3.org/TR/xpath-functions/#func-floor
def floor
RDF::Literal::Integer.new(to_d.floor)
end
##
# Returns `true` if the value is zero.
#
# @return [Boolean]
# @since 0.2.3
def zero?
to_d.zero?
end
##
# Returns `self` if the value is not zero, `nil` otherwise.
#
# @return [Boolean]
# @since 0.2.3
def nonzero?
to_d.nonzero? ? self : nil
end
##
# Returns the value as a string.
#
# @return [String]
# @see BigDecimal#to_s
def to_s
@string || @object.to_s('F')
end
end # Decimal
end; end # RDF::Literal