Class: Monotonic::Measurement

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/Monotonic/Measurement.rb

Overview

An elapsed interval together with what it is worth. The count alone says nothing of how much of itself is real, so #to_s shows only as many digits as the uncertainty supports. Nothing is capped: a day resolved to a hundred nanoseconds prints every figure it has, which looks absurd and is — the absurdity being the mismatch between what the instrument resolves and what was asked of it, and better seen than rounded away.

The doubt is a Measurand's business and the unit is a Duration's; what is left here is the ladder between them and the string at the end of it.

Constant Summary collapse

UNITS =

Powers of a thousand, and no further. The scheme is a decimal-places one, so minutes and hours have no place in it: 90s is plainer than 1.5min, and a run of a day reads as 86400s rather than reaching for a unit which does not divide by ten.

[[1, 'ns'], [1_000, 'us'], [1_000_000, 'ms'], [1_000_000_000, 's']]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#driftObject (readonly)

Returns the value of attribute drift.



37
38
39
# File 'lib/Monotonic/Measurement.rb', line 37

def drift
  @drift
end

#nanosecondsObject (readonly)

class << self



36
37
38
# File 'lib/Monotonic/Measurement.rb', line 36

def nanoseconds
  @nanoseconds
end

Class Method Details

.from(measurand) ⇒ Object

Built from a measurand rather than from a floor, for what comes back from arithmetic: its uncertainty has been propagated rather than measured, so it is neither an instrument's floor nor has a drift left to apply a second time.



31
32
33
# File 'lib/Monotonic/Measurement.rb', line 31

def from(measurand)
  allocate.tap{|measurement| measurement.send(:build, measurand)}
end

Instance Method Details

#*(multiplier) ⇒ Object

Scaled by a number it stays an interval, the uncertainty scaling with it. Multiplied by another interval it would be time squared, which has no unit here, so it is refused as Duration::Common refuses it.



78
79
80
81
82
83
# File 'lib/Monotonic/Measurement.rb', line 78

def *(multiplier)
  if interval?(multiplier)
    raise TypeError, "can't multiply #{self.class} by #{multiplier.class}: there is no unit of time squared"
  end
  self.class.from(measurand * multiplier)
end

#+(addend) ⇒ Object

The arithmetic is Measurand's throughout — this only decides what may be combined with what, and hands back the right kind of thing. Two intervals add and subtract to an interval, their uncertainties going in quadrature.



67
68
69
# File 'lib/Monotonic/Measurement.rb', line 67

def +(addend)
  self.class.from(measurand + as_measurand(addend, :add))
end

#-(subtrahend) ⇒ Object



71
72
73
# File 'lib/Monotonic/Measurement.rb', line 71

def -(subtrahend)
  self.class.from(measurand - as_measurand(subtrahend, :subtract))
end

#/(divisor) ⇒ Object

Divided by a number it stays an interval. Divided by another interval the units cancel and a Measurand is left — dimensionless, but still knowing how well it is known, which is what a speedup or a rate is.



88
89
90
91
# File 'lib/Monotonic/Measurement.rb', line 88

def /(divisor)
  return measurand / divisor.measurand if divisor.respond_to?(:measurand)
  self.class.from(measurand / divisor)
end

#<=>(other) ⇒ Object

Against another measurement only. A bare number has no unit, and comparing against one would have to assume which was meant, so nil is returned and == is false, as Duration::Common does.



140
141
142
143
# File 'lib/Monotonic/Measurement.rb', line 140

def <=>(other)
  return nil unless other.is_a?(Monotonic::Measurement)
  @nanoseconds <=> other.nanoseconds
end

#decimalsObject

The least significant digit worth showing is the one the uncertainty reaches, which Measurand settles by the Particle Data Group convention and reports as #place, a power of ten. Rendering in a coarser unit moves it along by however many tens that unit is worth. A measurand with no uncertainty has no last real digit, and answers nil.



110
111
112
113
114
115
# File 'lib/Monotonic/Measurement.rb', line 110

def decimals
  @decimals ||= (
    place = measurand.place
    place ? [Math.log10(scale) - place, 0].max.to_i : Math.log10(scale).to_i
  )
end

#floorObject

The floor may be given as a number or as anything which answers to #call, so that measuring it can wait until somebody asks about doubt. Monotonic::Timer hands over its .floor method rather than its value, and a caller which only wants a number never sets the thousand null measurements going.



44
45
46
# File 'lib/Monotonic/Measurement.rb', line 44

def floor
  @floor = @floor.respond_to?(:call) ? @floor.call : @floor
end

#inspectObject



145
146
147
# File 'lib/Monotonic/Measurement.rb', line 145

def inspect
  "#<#{self.class} #{self} (#{measurand} ns)>"
end

#measurandObject

The floor is absolute and tells upon short intervals; drift is relative and tells upon long ones, being the rate at which the clock's own oscillator wanders. Combining them is not addition — they are independent, so they go in quadrature — which is why the arithmetic is Measurand's and not done here.



53
54
55
56
57
58
# File 'lib/Monotonic/Measurement.rb', line 53

def measurand
  @measurand ||= (
    absolute = Measurand.new(@nanoseconds, floor)
    @drift ? absolute * Measurand.relative(1, @drift) : absolute
  )
end

#scaleObject



97
98
99
# File 'lib/Monotonic/Measurement.rb', line 97

def scale
  unit.first
end

#to_durationObject



117
118
119
# File 'lib/Monotonic/Measurement.rb', line 117

def to_duration
  Duration::Nanoseconds.new(@nanoseconds)
end

#to_fObject



129
130
131
# File 'lib/Monotonic/Measurement.rb', line 129

def to_f
  @nanoseconds.to_f
end

#to_iObject

Both answer in nanoseconds, this measurement's own unit, as Duration::Minutes answers 5 rather than 300. Every other unit comes from #to_duration, which names the one it is asked for. A number handed out without its unit stated must at least always mean the same thing.



125
126
127
# File 'lib/Monotonic/Measurement.rb', line 125

def to_i
  @nanoseconds
end

#to_sObject



133
134
135
# File 'lib/Monotonic/Measurement.rb', line 133

def to_s
  format("%.#{decimals}f #{unit_name}", @nanoseconds.to_f / scale)
end

#uncertaintyObject



60
61
62
# File 'lib/Monotonic/Measurement.rb', line 60

def uncertainty
  measurand.uncertainty
end

#unitObject



93
94
95
# File 'lib/Monotonic/Measurement.rb', line 93

def unit
  @unit ||= UNITS.reverse.find{|scale, _| @nanoseconds.abs >= scale} || UNITS.first
end

#unit_nameObject



101
102
103
# File 'lib/Monotonic/Measurement.rb', line 101

def unit_name
  unit.last
end