Module: Rjq::MathFunctions

Defined in:
lib/rjq/math_functions.rb

Constant Summary collapse

CANDIDATE_LIBRARIES =
[
  '/usr/lib/libSystem.B.dylib',
  'libm.so.6',
  'libm.so',
  'libm.dylib'
].freeze
NATIVE_SIGNATURES =
{
  fma: 'double fma(double, double, double)',
  remainder: 'double remainder(double, double)',
  scalb: 'double scalb(double, double)',
  scalbln: 'double scalbln(double, long)'
}.freeze

Class Method Summary collapse

Class Method Details

.bessel(name, *values) ⇒ Object



22
23
24
25
26
27
28
# File 'lib/rjq/math_functions.rb', line 22

def bessel(name, *values)
  if %w[jn yn].include?(name)
    bessel_library.public_send(name, values.fetch(0).to_i, values.fetch(1).to_f)
  else
    bessel_library.public_send(name, values.fetch(0).to_f)
  end
end

.bessel_available?Boolean

Returns:

  • (Boolean)


136
137
138
139
140
141
# File 'lib/rjq/math_functions.rb', line 136

def bessel_available?
  bessel_library
  true
rescue Rjq::RuntimeError
  false
end

.bessel_libraryObject



132
133
134
# File 'lib/rjq/math_functions.rb', line 132

def bessel_library
  @bessel_library ||= load_bessel_library
end

.build_bessel_library(library) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/rjq/math_functions.rb', line 154

def build_bessel_library(library)
  Module.new do
    extend Fiddle::Importer

    dlload library
    extern 'double j0(double)'
    extern 'double j1(double)'
    extern 'double y0(double)'
    extern 'double y1(double)'
    extern 'double jn(int, double)'
    extern 'double yn(int, double)'
  end
end

.build_native_library(library, signature) ⇒ Object



96
97
98
99
100
101
102
103
# File 'lib/rjq/math_functions.rb', line 96

def build_native_library(library, signature)
  Module.new do
    extend Fiddle::Importer

    dlload library
    extern signature
  end
end

.c_long_exponent(value) ⇒ Object



105
106
107
108
109
110
111
112
113
# File 'lib/rjq/math_functions.rb', line 105

def c_long_exponent(value)
  bits = Fiddle::SIZEOF_LONG * 8
  minimum = -(1 << (bits - 1))
  maximum = (1 << (bits - 1)) - 1
  return 0 if value.nan?
  return value.positive? ? maximum : minimum if value.infinite?

  [[value.to_i, minimum].max, maximum].min
end

.fma(left, right, addend) ⇒ Object

Raises:



30
31
32
33
34
35
# File 'lib/rjq/math_functions.rb', line 30

def fma(left, right, addend)
  library = native_library(:fma)
  return library.fma(left.to_f, right.to_f, addend.to_f) if library

  raise Rjq::RuntimeError, 'native fused multiply-add is not available on this platform'
end

.load_bessel_libraryObject

Raises:



143
144
145
146
147
148
149
150
151
152
# File 'lib/rjq/math_functions.rb', line 143

def load_bessel_library
  errors = []
  CANDIDATE_LIBRARIES.each do |library|
    return build_bessel_library(library)
  rescue Fiddle::DLError => e
    errors << "#{library}: #{e.message}"
  end

  raise Rjq::RuntimeError, "C math library with Bessel functions is not available (#{errors.join('; ')})"
end

.native_available?(name) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/rjq/math_functions.rb', line 80

def native_available?(name)
  !native_library(name).nil?
end

.native_library(name) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/rjq/math_functions.rb', line 84

def native_library(name)
  @native_libraries ||= {}
  return @native_libraries[name] if @native_libraries.key?(name)

  signature = NATIVE_SIGNATURES.fetch(name)
  @native_libraries[name] = CANDIDATE_LIBRARIES.lazy.filter_map do |library|
    build_native_library(library, signature)
  rescue Fiddle::DLError
    nil
  end.first
end

.portable_scale(value, exponent) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/rjq/math_functions.rb', line 115

def portable_scale(value, exponent)
  return value if value.zero?
  return signed_max(value) if value.infinite?

  _, value_exponent = Math.frexp(value.abs)
  target_exponent = value_exponent + exponent
  return signed_max(value) if target_exponent > 1024
  return value.negative? ? -0.0 : 0.0 if target_exponent < -1074

  result = Math.ldexp(value, exponent)
  result.infinite? ? signed_max(value) : result
end

.remainder(left, right) ⇒ Object

Raises:



37
38
39
40
41
42
# File 'lib/rjq/math_functions.rb', line 37

def remainder(left, right)
  library = native_library(:remainder)
  return library.remainder(left.to_f, right.to_f) if library

  raise Rjq::RuntimeError, 'native IEEE remainder is not available on this platform'
end

.scalb(value, exponent) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/rjq/math_functions.rb', line 44

def scalb(value, exponent)
  library = native_library(:scalb)
  if library
    result = library.scalb(value.to_f, exponent.to_f)
    return signed_max(value.to_f) if result.infinite?

    return result
  end

  exponent = exponent.to_f
  return Float::NAN if exponent.nan?
  return portable_scale(value.to_f, exponent.positive? ? 10_000 : -10_000) if exponent.infinite?

  portable_scale(value.to_f, exponent.to_i)
end

.scalbln(value, exponent) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rjq/math_functions.rb', line 60

def scalbln(value, exponent)
  exponent = exponent.to_f
  if RUBY_PLATFORM.include?('linux') && !exponent.finite?
    # jq's Linux build passes non-finite values through a C long
    # conversion, which yields zero on the supported libc targets.
    return 0.0
  end

  integral_exponent = c_long_exponent(exponent)
  library = native_library(:scalbln)
  if library
    result = library.scalbln(value.to_f, integral_exponent)
    return signed_max(value.to_f) if result.infinite?

    return result
  end

  portable_scale(value.to_f, integral_exponent)
end

.signed_max(value) ⇒ Object



128
129
130
# File 'lib/rjq/math_functions.rb', line 128

def signed_max(value)
  value.negative? ? -Float::MAX : Float::MAX
end