@@ -91,13 +91,19 @@ fn decimalToText(value: i64, scale: i32, buf: *[64]u8) []const u8 {
9191 // ponytail: manual int-to-text avoids bufPrint FixedWriter issues
9292 return intToBuf (value , buf );
9393 }
94- var pow10 : i64 = 1 ;
94+ var pow10 : u64 = 1 ;
9595 for (0.. @as (usize , @intCast (scale ))) | _ | pow10 *= 10 ;
96- const int_part = @divTrunc (value , pow10 );
97- const prefix = intToBuf (int_part , buf );
96+ // ponytail: sign tracked independently (Issue #234) — @divTrunc(-1, 100)
97+ // is 0, so intToBuf(int_part) alone drops the sign of |value| < pow10
98+ const neg = value < 0 ;
99+ const mag : u64 = @intCast (@abs (value ));
100+ if (neg ) buf [0 ] = '-' ;
101+ const off : usize = @intFromBool (neg );
102+ const digits = intToBuf (@as (i64 , @intCast (mag / pow10 )), buf [off .. ]);
103+ const prefix = buf [0 .. off + digits .len ];
98104 buf [prefix .len ] = '.' ;
99105 const dot_pos = prefix .len + 1 ;
100- var f = @as ( u64 , @intCast ( @abs ( @rem ( value , pow10 )))) ;
106+ var f = mag % pow10 ;
101107 var pos : usize = @intCast (scale );
102108 while (pos > 0 ) {
103109 pos -= 1 ;
@@ -137,6 +143,15 @@ fn intToBuf(value: i64, buf: []u8) []const u8 {
137143 return buf [0.. end ];
138144}
139145
146+ test "decimalToText: negative sub-unit value keeps its sign (Issue #234)" {
147+ var buf : [64 ]u8 = undefined ;
148+ try std .testing .expectEqualStrings ("-0.01" , decimalToText (-1 , 2 , & buf ));
149+ try std .testing .expectEqualStrings ("0.01" , decimalToText (1 , 2 , & buf ));
150+ try std .testing .expectEqualStrings ("-123.45" , decimalToText (-12345 , 2 , & buf ));
151+ try std .testing .expectEqualStrings ("-5" , decimalToText (-5 , 0 , & buf ));
152+ try std .testing .expectEqualStrings ("0.00" , decimalToText (0 , 2 , & buf ));
153+ }
154+
140155/// Map a Parquet physical type to a SQLite ColumnType (no logical type mapping).
141156fn physicalToAffinity (phys : parquet.format.PhysicalType ) sqlite_mod.ColumnType {
142157 return switch (phys ) {
0 commit comments