class Avro::LogicalTypes::BytesDecimal

Logical type to handle arbitrary-precision decimals using byte array.

The byte array contains the two's-complement representation of the unscaled integer value in big-endian byte order.

Constants

ERROR_INSUFFICIENT_PRECISION

Messages for exceptions

ERROR_ROUNDING_NECESSARY
ERROR_VALUE_MUST_BE_NUMERIC
PACK_UNSIGNED_CHARS

The pattern used to pack up the byte array (8 bit unsigned integer/char)

TEN

The number 10 as BigDecimal

Attributes

precision[R]

@return [Integer] The number of total digits supported by the decimal

scale[R]

@return [Integer] The number of fractional digits

Public Class Methods

new(schema) click to toggle source

Build a new decimal logical type

@param schema [Avro::Schema]

The schema defining precision and scale for the conversion
    # File lib/avro/logical_types.rb
 98 def initialize(schema)
 99   super
100 
101   @scale     = schema.scale.to_i
102   @precision = schema.precision.to_i
103   @factor    = TEN ** @scale
104 end

Public Instance Methods

decode(stream) click to toggle source

Decode a byte array (in form of a string) into a BigDecimal of the given precision and scale

@param stream [String]

The byte array to decode

@return [BigDecimal]

    # File lib/avro/logical_types.rb
132 def decode(stream)
133   from_byte_array(stream) / @factor
134 end
encode(value) click to toggle source

Encode the provided value into a byte array

@param value [BigDecimal, Float, Integer]

The numeric value to encode

@raise [ArgumentError]

If the provided value is not a numeric type

@raise [RangeError]

If the provided value has a scale higher than the schema permits,
or does not fit into the schema's precision
    # File lib/avro/logical_types.rb
118 def encode(value)
119   raise ArgumentError, ERROR_VALUE_MUST_BE_NUMERIC unless value.is_a?(Numeric)
120 
121   to_byte_array(unscaled_value(value.to_d)).pack(PACK_UNSIGNED_CHARS).freeze
122 end

Private Instance Methods

from_byte_array(stream) click to toggle source

Convert the provided stream of bytes into the unscaled value

@param stream [String]

The stream of bytes to convert

@return [Integer]

    # File lib/avro/logical_types.rb
145 def from_byte_array(stream)
146   bytes    = stream.bytes
147   positive = bytes.first[7].zero?
148   total    = 0
149 
150   bytes.each_with_index do |value, ix|
151     total += (positive ? value : (value ^ 0xff)) << (bytes.length - ix - 1) * 8
152   end
153 
154   return total if positive
155 
156   -(total + 1)
157 end
to_byte_array(number) click to toggle source

Convert the provided number into its two's complement representation in network order (big endian).

@param number [Integer]

The number to convert

@return [Array<Integer>]

The byte array in network order
    # File lib/avro/logical_types.rb
168 def to_byte_array(number)
169   [].tap do |result|
170     loop do
171       result.unshift(number & 0xff)
172       number >>= 8
173 
174       break if (number == 0 || number == -1) && (result.first[7] == number[7])
175     end
176   end
177 end
unscaled_value(decimal) click to toggle source

Get the unscaled value from a BigDecimal considering the schema's scale

@param decimal [BigDecimal]

The decimal to get the unscaled value from

@return [Integer]

    # File lib/avro/logical_types.rb
186 def unscaled_value(decimal)
187   details = decimal.split
188   length  = details[1].length
189 
190   fractional_part = length - details[3]
191   raise RangeError, ERROR_ROUNDING_NECESSARY if fractional_part > scale
192 
193   if length > precision || (length - fractional_part) > (precision - scale)
194     raise RangeError, ERROR_INSUFFICIENT_PRECISION
195   end
196 
197   (decimal * @factor).to_i
198 end