diff --git a/ruby/red-arrow-format/lib/arrow-format/array.rb b/ruby/red-arrow-format/lib/arrow-format/array.rb index 6474869c30c..9d82aae16fc 100644 --- a/ruby/red-arrow-format/lib/arrow-format/array.rb +++ b/ruby/red-arrow-format/lib/arrow-format/array.rb @@ -309,12 +309,9 @@ def to_a end class DecimalArray < FixedSizeBinaryArray - end - - class Decimal128Array < DecimalArray def to_a byte_width = @type.byte_width - buffer_types = [:u64, :s64] # TODO: big endian support + buffer_types = [:u64] * (byte_width / 8 - 1) + [:s64] values = 0.step(@size * byte_width - 1, byte_width).collect do |offset| @values_buffer.get_values(buffer_types, offset) end @@ -322,27 +319,47 @@ def to_a if value.nil? nil else - low, high = value - BigDecimal(format_value(low, high)) + BigDecimal(format_value(value)) end end end private - def format_value(low, high) + def format_value(components) + highest = components.last width = @type.precision - width += 1 if high < 0 - value = (high << 64) + low - string = value.to_s.ljust(width, "0") + width += 1 if highest < 0 + value = 0 + components.reverse_each do |component| + value = (value << 64) + component + end + string = value.to_s if @type.scale < 0 string << ("0" * -@type.scale) elsif @type.scale > 0 - string[-@type.scale, 0] = "." + n_digits = string.bytesize + n_digits -= 1 if value < 0 + if n_digits < @type.scale + prefix = "0." + ("0" * (@type.scale - n_digits - 1)) + if value < 0 + string[1, 0] = prefix + else + string[0, 0] = prefix + end + else + string[-@type.scale, 0] = "." + end end string end end + class Decimal128Array < DecimalArray + end + + class Decimal256Array < DecimalArray + end + class VariableSizeListArray < Array def initialize(type, size, validity_buffer, offsets_buffer, child) super(type, size, validity_buffer) diff --git a/ruby/red-arrow-format/lib/arrow-format/readable.rb b/ruby/red-arrow-format/lib/arrow-format/readable.rb index 5a247c822a4..7aa2effde25 100644 --- a/ruby/red-arrow-format/lib/arrow-format/readable.rb +++ b/ruby/red-arrow-format/lib/arrow-format/readable.rb @@ -171,6 +171,8 @@ def read_field(fb_field) case fb_type.bit_width when 128 type = Decimal128Type.new(fb_type.precision, fb_type.scale) + when 256 + type = Decimal256Type.new(fb_type.precision, fb_type.scale) end end Field.new(fb_field.name, type, fb_field.nullable?) diff --git a/ruby/red-arrow-format/lib/arrow-format/type.rb b/ruby/red-arrow-format/lib/arrow-format/type.rb index d6d8b7bb81a..92a699509ba 100644 --- a/ruby/red-arrow-format/lib/arrow-format/type.rb +++ b/ruby/red-arrow-format/lib/arrow-format/type.rb @@ -522,6 +522,20 @@ def build_array(size, validity_buffer, values_buffer) end end + class Decimal256Type < DecimalType + def initialize(precision, scale) + super(32, precision, scale) + end + + def name + "Decimal256" + end + + def build_array(size, validity_buffer, values_buffer) + Decimal256Array.new(self, size, validity_buffer, values_buffer) + end + end + class VariableSizeListType < Type attr_reader :child def initialize(child) diff --git a/ruby/red-arrow-format/test/test-reader.rb b/ruby/red-arrow-format/test/test-reader.rb index cddcea484fb..8164d20623f 100644 --- a/ruby/red-arrow-format/test/test-reader.rb +++ b/ruby/red-arrow-format/test/test-reader.rb @@ -676,19 +676,61 @@ def test_read sub_test_case("Decimal128") do def build_array - @positive = "12345678901234567890123456789012345.678" - @negative = "-12345678901234567890123456789012345.67" + @positive_small = "1.200" + @positive_large = ("1234567890" * 3) + "12345.678" + @negative_small = "-1.200" + @negative_large = "-" + ("1234567890" * 3) + "12345.678" Arrow::Decimal128Array.new({precision: 38, scale: 3}, - [@positive, nil, @negative]) + [ + @positive_large, + @positive_small, + nil, + @negative_small, + @negative_large, + ]) end def test_read assert_equal([ { "value" => [ - BigDecimal(@positive), + BigDecimal(@positive_large), + BigDecimal(@positive_small), nil, - BigDecimal(@negative), + BigDecimal(@negative_small), + BigDecimal(@negative_large), + ], + }, + ], + read) + end + end + + sub_test_case("Decimal256") do + def build_array + @positive_small = "1.200" + @positive_large = ("1234567890" * 7) + "123.456" + @negative_small = "-1.200" + @negative_large = "-" + ("1234567890" * 7) + "123.456" + Arrow::Decimal256Array.new({precision: 76, scale: 3}, + [ + @positive_large, + @positive_small, + nil, + @negative_small, + @negative_large, + ]) + end + + def test_read + assert_equal([ + { + "value" => [ + BigDecimal(@positive_large), + BigDecimal(@positive_small), + nil, + BigDecimal(@negative_small), + BigDecimal(@negative_large), ], }, ],