all repos

clerk @ 2d8dc1f

missing tooling for ledger/hledger
2 files changed, 103 insertions(+), 0 deletions(-)
decimal: add .Abs() and .Div()
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-06-09 15:33:31 +0300
Authored at: 2026-06-09 15:25:32 +0300
Change ID: nvnywlzvuqltyrwsmpwwmxpuqzlknyxo
Parent: 4c27ef1
M internal/decimal/decimal.go
···
        189
        189
         	}

      
        190
        190
         }

      
        191
        191
         

      
        
        192
        +func (d Decimal) Abs() Decimal {

      
        
        193
        +	if d.coeff == nil || d.coeff.Sign() == 0 {

      
        
        194
        +		return Decimal{}

      
        
        195
        +	}

      
        
        196
        +	if d.coeff.Sign() > 0 {

      
        
        197
        +		return Decimal{coeff: new(big.Int).Set(d.coeff), scale: d.scale}

      
        
        198
        +	}

      
        
        199
        +	return Decimal{coeff: new(big.Int).Neg(d.coeff), scale: d.scale}

      
        
        200
        +}

      
        
        201
        +

      
        192
        202
         func (d Decimal) Neg() Decimal {

      
        193
        203
         	if d.coeff == nil || d.coeff.Sign() == 0 {

      
        194
        204
         		return Decimal{}

      ···
        209
        219
         	}

      
        210
        220
         	product := new(big.Int).Mul(d.coeffOrZero(), other.coeffOrZero())

      
        211
        221
         	return Decimal{coeff: product, scale: d.scale + other.scale}.normalized()

      
        
        222
        +}

      
        
        223
        +

      
        
        224
        +func (d Decimal) Div(other Decimal) Decimal {

      
        
        225
        +	if other.IsZero() {

      
        
        226
        +		panic("decimal: division by zero")

      
        
        227
        +	}

      
        
        228
        +	if d.IsZero() {

      
        
        229
        +		return Decimal{}

      
        
        230
        +	}

      
        
        231
        +

      
        
        232
        +	scale := max(d.scale, other.scale) + 10

      
        
        233
        +

      
        
        234
        +	dCoeff := d.coeffOrZero()

      
        
        235
        +	oCoeff := other.coeffOrZero()

      
        
        236
        +

      
        
        237
        +	shift := scale + other.scale - d.scale

      
        
        238
        +	if shift > 0 {

      
        
        239
        +		dCoeff = new(big.Int).Mul(dCoeff, pow10(shift))

      
        
        240
        +	} else if shift < 0 {

      
        
        241
        +		oCoeff = new(big.Int).Mul(oCoeff, pow10(-shift))

      
        
        242
        +	}

      
        
        243
        +

      
        
        244
        +	quo := new(big.Int).Quo(dCoeff, oCoeff)

      
        
        245
        +	return Decimal{coeff: quo, scale: scale}.normalized()

      
        212
        246
         }

      
        213
        247
         

      
        214
        248
         func (d Decimal) Cmp(other Decimal) int {

      
M internal/decimal/decimal_test.go
···
        104
        104
         	}

      
        105
        105
         }

      
        106
        106
         

      
        
        107
        +func TestAbs(t *testing.T) {

      
        
        108
        +	tests := []struct {

      
        
        109
        +		in, want string

      
        
        110
        +	}{

      
        
        111
        +		{"0", "0"},

      
        
        112
        +		{"-0", "0"},

      
        
        113
        +		{"0.00", "0"},

      
        
        114
        +		{"1.5", "1.5"},

      
        
        115
        +		{"-1.5", "1.5"},

      
        
        116
        +		{"100", "100"},

      
        
        117
        +		{"-100", "100"},

      
        
        118
        +		{"0.001", "0.001"},

      
        
        119
        +		{"-0.001", "0.001"},

      
        
        120
        +		{"123.456", "123.456"},

      
        
        121
        +		{"-123.456", "123.456"},

      
        
        122
        +	}

      
        
        123
        +	for _, tt := range tests {

      
        
        124
        +		d, err := FromString(tt.in)

      
        
        125
        +		if err != nil {

      
        
        126
        +			t.Fatalf("FromString(%q) unexpected error: %v", tt.in, err)

      
        
        127
        +		}

      
        
        128
        +		if got := d.Abs().String(); got != tt.want {

      
        
        129
        +			t.Fatalf("Abs(%q) = %q, want %q", tt.in, got, tt.want)

      
        
        130
        +		}

      
        
        131
        +	}

      
        
        132
        +}

      
        
        133
        +

      
        
        134
        +func TestDiv(t *testing.T) {

      
        
        135
        +	tests := []struct {

      
        
        136
        +		a, b, want string

      
        
        137
        +	}{

      
        
        138
        +		{"1", "2", "0.5"},

      
        
        139
        +		{"10", "3", "3.3333333333"},

      
        
        140
        +		{"1.50", "3.00", "0.5"},

      
        
        141
        +		{"0.1", "0.3", "0.33333333333"},

      
        
        142
        +		{"100", "10", "10"},

      
        
        143
        +		{"-1", "2", "-0.5"},

      
        
        144
        +		{"1", "-2", "-0.5"},

      
        
        145
        +		{"-1", "-2", "0.5"},

      
        
        146
        +		{"0", "5", "0"},

      
        
        147
        +		{"5.50", "5", "1.1"},

      
        
        148
        +		{"1000", "3", "333.3333333333"},

      
        
        149
        +	}

      
        
        150
        +	for _, tt := range tests {

      
        
        151
        +		a, err := FromString(tt.a)

      
        
        152
        +		if err != nil {

      
        
        153
        +			t.Fatalf("FromString(%q) unexpected error: %v", tt.a, err)

      
        
        154
        +		}

      
        
        155
        +		b, err := FromString(tt.b)

      
        
        156
        +		if err != nil {

      
        
        157
        +			t.Fatalf("FromString(%q) unexpected error: %v", tt.b, err)

      
        
        158
        +		}

      
        
        159
        +		got := a.Div(b).String()

      
        
        160
        +		if got != tt.want {

      
        
        161
        +			t.Fatalf("Div(%q, %q) = %q, want %q", tt.a, tt.b, got, tt.want)

      
        
        162
        +		}

      
        
        163
        +	}

      
        
        164
        +}

      
        
        165
        +

      
        
        166
        +func TestDivByZero(t *testing.T) {

      
        
        167
        +	defer func() {

      
        
        168
        +		if r := recover(); r == nil {

      
        
        169
        +			t.Fatal("expected panic on division by zero")

      
        
        170
        +		}

      
        
        171
        +	}()

      
        
        172
        +	d, _ := FromString("1")

      
        
        173
        +	d.Div(Decimal{})

      
        
        174
        +}

      
        
        175
        +

      
        107
        176
         func TestCmpAndNeg(t *testing.T) {

      
        108
        177
         	a, _ := FromString("1.0")

      
        109
        178
         	b, _ := FromString("1")