Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/tools/source/generic/fract.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <tools/fract.hxx>
21
#include <tools/debug.hxx>
22
#include <o3tl/hash_combine.hxx>
23
#include <o3tl/numeric.hxx>
24
#include <o3tl/safeint.hxx>
25
#include <sal/log.hxx>
26
#include <osl/diagnose.h>
27
28
#include <algorithm>
29
#include <bit>
30
#include <cmath>
31
#include <numeric>
32
33
#include <boost/rational.hpp>
34
35
static boost::rational<sal_Int32> rational_FromDouble(double dVal);
36
static void rational_ReduceInaccurate(boost::rational<sal_Int32>& rRational, unsigned nSignificantBits);
37
// Find the number of bits required to represent this number
38
966M
static int impl_NumberOfBits(sal_uInt32 nNum) { return o3tl::number_of_bits(nNum); }
39
40
static boost::rational<sal_Int32> toRational(sal_Int32 n, sal_Int32 d)
41
711M
{
42
    // https://github.com/boostorg/boost/issues/335 when these are std::numeric_limits<sal_Int32>::min
43
711M
    if (n == d)
44
167M
        return 1;
45
    // tdf#144319 avoid boost::bad_rational e.g. if numerator=-476741369, denominator=-2147483648
46
543M
    if (d < -std::numeric_limits<sal_Int32>::max())
47
691
        return 0;
48
543M
    return boost::rational<sal_Int32>(n, d);
49
543M
}
50
51
static constexpr bool isOutOfRange(sal_Int64 nNum)
52
89.5M
{
53
89.5M
    return nNum < std::numeric_limits<sal_Int32>::min()
54
89.5M
            || nNum > std::numeric_limits<sal_Int32>::max();
55
89.5M
}
56
57
44.7M
Fraction::Fraction( sal_Int64 nNum, sal_Int64 nDen ) : mnNumerator(nNum), mnDenominator(nDen)
58
44.7M
{
59
44.7M
    if ( isOutOfRange(nNum) || isOutOfRange(nDen) )
60
3.66k
    {
61
        // tdf#143200
62
3.66k
        if (const auto gcd = std::gcd(nNum, nDen); gcd > 1)
63
637
        {
64
637
            nNum /= gcd;
65
637
            nDen /= gcd;
66
637
        }
67
3.66k
        SAL_WARN_IF(isOutOfRange(nNum) || isOutOfRange(nDen),
68
3.66k
                    "tools.fraction", "values outside of range we can represent, doing reduction, which will reduce precision");
69
52.2k
        while (isOutOfRange(nNum) || isOutOfRange(nDen))
70
48.5k
        {
71
48.5k
            nNum /= 2;
72
48.5k
            nDen /= 2;
73
48.5k
        }
74
3.66k
        mnNumerator = nNum;
75
3.66k
        mnDenominator = nDen;
76
3.66k
    }
77
44.7M
    if ( mnDenominator == 0 )
78
10.8k
    {
79
10.8k
        mbValid = false;
80
10.8k
        SAL_WARN( "tools.fraction", "'Fraction(" << nNum << ",0)' invalid fraction created" );
81
10.8k
        return;
82
10.8k
    }
83
44.7M
    else if ((nDen == -1 && nNum == std::numeric_limits<sal_Int32>::min()) ||
84
44.7M
            (nNum == -1 && nDen == std::numeric_limits<sal_Int32>::min()))
85
24
    {
86
24
        mbValid = false;
87
24
        SAL_WARN("tools.fraction", "'Fraction(" << nNum << "," << nDen << ")' invalid fraction created");
88
24
        return;
89
24
    }
90
44.7M
}
91
92
/**
93
 * only here to prevent passing of NaN
94
 */
95
Fraction::Fraction( double nNum, double nDen )
96
0
    : Fraction(sal_Int64(nNum), sal_Int64(nDen))
97
0
{}
98
99
Fraction::Fraction( double dVal )
100
203M
{
101
203M
    try
102
203M
    {
103
203M
        boost::rational<sal_Int32> v = rational_FromDouble( dVal );
104
203M
        mnNumerator = v.numerator();
105
203M
        mnDenominator = v.denominator();
106
203M
    }
107
203M
    catch (const boost::bad_rational&)
108
203M
    {
109
1.50k
        mbValid = false;
110
1.50k
        SAL_WARN( "tools.fraction", "'Fraction(" << dVal << ")' invalid fraction created" );
111
1.50k
    }
112
203M
}
113
114
Fraction::operator double() const
115
3.19M
{
116
3.19M
    if (!mbValid)
117
0
    {
118
0
        SAL_WARN( "tools.fraction", "'double()' on invalid fraction" );
119
0
        return std::numeric_limits<double>::quiet_NaN();
120
0
    }
121
122
3.19M
    return boost::rational_cast<double>(toRational(mnNumerator, mnDenominator));
123
3.19M
}
124
125
// This methods first validates both values.
126
// If one of the arguments is invalid, the whole operation is invalid.
127
// After computation detect if result overflows a sal_Int32 value
128
// which cause the operation to be marked as invalid
129
Fraction& Fraction::operator += ( const Fraction& rVal )
130
0
{
131
0
    if ( !rVal.mbValid )
132
0
        mbValid = false;
133
134
0
    if ( !mbValid )
135
0
    {
136
0
        SAL_WARN( "tools.fraction", "'operator +=' with invalid fraction" );
137
0
        return *this;
138
0
    }
139
140
0
    boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
141
0
    a += toRational(rVal.mnNumerator, rVal.mnDenominator);
142
0
    mnNumerator = a.numerator();
143
0
    mnDenominator = a.denominator();
144
145
0
    return *this;
146
0
}
147
148
Fraction& Fraction::operator -= ( const Fraction& rVal )
149
1.73k
{
150
1.73k
    if ( !rVal.mbValid )
151
0
        mbValid = false;
152
153
1.73k
    if ( !mbValid )
154
0
    {
155
0
        SAL_WARN( "tools.fraction", "'operator -=' with invalid fraction" );
156
0
        return *this;
157
0
    }
158
159
1.73k
    boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
160
1.73k
    a -= toRational(rVal.mnNumerator, rVal.mnDenominator);
161
1.73k
    mnNumerator = a.numerator();
162
1.73k
    mnDenominator = a.denominator();
163
164
1.73k
    return *this;
165
1.73k
}
166
167
namespace
168
{
169
    bool checked_multiply_by(boost::rational<sal_Int32>& i, const boost::rational<sal_Int32>& r)
170
242M
    {
171
        // Protect against self-modification
172
242M
        sal_Int32 num = r.numerator();
173
242M
        sal_Int32 den = r.denominator();
174
175
        // Fast-path if the number of bits in input is < the number of bits in the output, overflow cannot happen
176
        // This is considerably faster than repeated std::gcd() operations
177
242M
        if ((impl_NumberOfBits(std::abs(i.numerator())) + impl_NumberOfBits(std::abs(r.numerator()))) < 32 &&
178
240M
            (impl_NumberOfBits(std::abs(i.denominator())) + impl_NumberOfBits(std::abs(r.denominator()))) < 32)
179
240M
        {
180
240M
            i *= r;
181
240M
            return false;
182
240M
        }
183
184
        // Avoid overflow and preserve normalization
185
1.54M
        sal_Int32 gcd1 = std::gcd(i.numerator(), den);
186
1.54M
        sal_Int32 gcd2 = std::gcd(num, i.denominator());
187
188
1.54M
        if (!gcd1 || !gcd2)
189
0
            return true;
190
191
1.54M
        bool fail = false;
192
1.54M
        fail |= o3tl::checked_multiply(i.numerator() / gcd1, num / gcd2, num);
193
1.54M
        fail |= o3tl::checked_multiply(i.denominator() / gcd2, den / gcd1, den);
194
195
1.54M
        if (!fail)
196
49.6k
            i.assign(num, den);
197
198
1.54M
        return fail;
199
1.54M
    }
200
}
201
202
Fraction& Fraction::operator *= ( const Fraction& rVal )
203
214M
{
204
214M
    if ( !rVal.mbValid )
205
19.4k
        mbValid = false;
206
207
214M
    if ( !mbValid )
208
19.4k
    {
209
19.4k
        SAL_WARN( "tools.fraction", "'operator *=' with invalid fraction" );
210
19.4k
        return *this;
211
19.4k
    }
212
213
214M
    boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
214
214M
    boost::rational<sal_Int32> b = toRational(rVal.mnNumerator, rVal.mnDenominator);
215
214M
    bool bFail = checked_multiply_by(a, b);
216
214M
    mnNumerator = a.numerator();
217
214M
    mnDenominator = a.denominator();
218
219
214M
    if (bFail)
220
739
    {
221
739
        mbValid = false;
222
739
    }
223
224
214M
    return *this;
225
214M
}
226
227
Fraction& Fraction::operator /= ( const Fraction& rVal )
228
8.96k
{
229
8.96k
    if ( !rVal.mbValid )
230
4.92k
        mbValid = false;
231
232
8.96k
    if ( !mbValid )
233
4.92k
    {
234
4.92k
        SAL_WARN( "tools.fraction", "'operator /=' with invalid fraction" );
235
4.92k
        return *this;
236
4.92k
    }
237
238
4.03k
    boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
239
4.03k
    a /= toRational(rVal.mnNumerator, rVal.mnDenominator);
240
4.03k
    mnNumerator = a.numerator();
241
4.03k
    mnDenominator = a.denominator();
242
243
4.03k
    return *this;
244
8.96k
}
245
246
/** Inaccurate cancellation for a fraction.
247
248
    Clip both nominator and denominator to said number of bits. If
249
    either of those already have equal or less number of bits used,
250
    this method does nothing.
251
252
    @param nSignificantBits denotes, how many significant binary
253
    digits to maintain, in both nominator and denominator.
254
255
    @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the
256
    largest error occurs with the following pair of values:
257
258
    binary    1000000011111111111111111111111b/1000000000000000000000000000000b
259
    =         1082130431/1073741824
260
    = approx. 1.007812499
261
262
    A ReduceInaccurate(8) yields 1/1.
263
*/
264
void Fraction::ReduceInaccurate( unsigned nSignificantBits )
265
635
{
266
635
    if ( !mbValid )
267
0
    {
268
0
        SAL_WARN( "tools.fraction", "'ReduceInaccurate' on invalid fraction" );
269
0
        return;
270
0
    }
271
272
635
    if ( !mnNumerator )
273
0
        return;
274
275
635
    auto a = toRational(mnNumerator, mnDenominator);
276
635
    rational_ReduceInaccurate(a, nSignificantBits);
277
635
    mnNumerator = a.numerator();
278
635
    mnDenominator = a.denominator();
279
635
}
280
281
sal_Int32 Fraction::GetNumerator() const
282
53.0M
{
283
53.0M
    if ( !mbValid )
284
2.37k
    {
285
2.37k
        SAL_WARN( "tools.fraction", "'GetNumerator()' on invalid fraction" );
286
2.37k
        return 0;
287
2.37k
    }
288
53.0M
    return mnNumerator;
289
53.0M
}
290
291
sal_Int32 Fraction::GetDenominator() const
292
52.9M
{
293
52.9M
    if ( !mbValid )
294
1.81k
    {
295
1.81k
        SAL_WARN( "tools.fraction", "'GetDenominator()' on invalid fraction" );
296
1.81k
        return -1;
297
1.81k
    }
298
52.9M
    return mnDenominator;
299
52.9M
}
300
301
Fraction::operator sal_Int32() const
302
214M
{
303
214M
    if ( !mbValid )
304
24.7k
    {
305
24.7k
        SAL_WARN( "tools.fraction", "'operator sal_Int32()' on invalid fraction" );
306
24.7k
        return 0;
307
24.7k
    }
308
214M
    return boost::rational_cast<sal_Int32>(toRational(mnNumerator, mnDenominator));
309
214M
}
310
311
Fraction operator+( const Fraction& rVal1, const Fraction& rVal2 )
312
0
{
313
0
    Fraction aErg( rVal1 );
314
0
    aErg += rVal2;
315
0
    return aErg;
316
0
}
317
318
Fraction operator-( const Fraction& rVal1, const Fraction& rVal2 )
319
1.73k
{
320
1.73k
    Fraction aErg( rVal1 );
321
1.73k
    aErg -= rVal2;
322
1.73k
    return aErg;
323
1.73k
}
324
325
Fraction operator*( const Fraction& rVal1, const Fraction& rVal2 )
326
214M
{
327
214M
    Fraction aErg( rVal1 );
328
214M
    aErg *= rVal2;
329
214M
    return aErg;
330
214M
}
331
332
Fraction operator/( const Fraction& rVal1, const Fraction& rVal2 )
333
8.96k
{
334
8.96k
    Fraction aErg( rVal1 );
335
8.96k
    aErg /= rVal2;
336
8.96k
    return aErg;
337
8.96k
}
338
339
bool operator !=( const Fraction& rVal1, const Fraction& rVal2 )
340
515k
{
341
515k
    return !(rVal1 == rVal2);
342
515k
}
343
344
bool operator <=( const Fraction& rVal1, const Fraction& rVal2 )
345
0
{
346
0
    return !(rVal1 > rVal2);
347
0
}
348
349
bool operator >=( const Fraction& rVal1, const Fraction& rVal2 )
350
0
{
351
0
    return !(rVal1 < rVal2);
352
0
}
353
354
bool operator == ( const Fraction& rVal1, const Fraction& rVal2 )
355
4.39M
{
356
4.39M
    if ( !rVal1.mbValid || !rVal2.mbValid )
357
4.93k
    {
358
4.93k
        SAL_WARN( "tools.fraction", "'operator ==' with an invalid fraction" );
359
4.93k
        return false;
360
4.93k
    }
361
362
4.39M
    return toRational(rVal1.mnNumerator, rVal1.mnDenominator) == toRational(rVal2.mnNumerator, rVal2.mnDenominator);
363
4.39M
}
364
365
bool operator < ( const Fraction& rVal1, const Fraction& rVal2 )
366
0
{
367
0
    if ( !rVal1.mbValid || !rVal2.mbValid )
368
0
    {
369
0
        SAL_WARN( "tools.fraction", "'operator <' with an invalid fraction" );
370
0
        return false;
371
0
    }
372
373
0
    return toRational(rVal1.mnNumerator, rVal1.mnDenominator) < toRational(rVal2.mnNumerator, rVal2.mnDenominator);
374
0
}
375
376
bool operator > ( const Fraction& rVal1, const Fraction& rVal2 )
377
0
{
378
0
    if ( !rVal1.mbValid || !rVal2.mbValid )
379
0
    {
380
0
        SAL_WARN( "tools.fraction", "'operator >' with an invalid fraction" );
381
0
        return false;
382
0
    }
383
384
0
    return toRational(rVal1.mnNumerator, rVal1.mnDenominator) > toRational(rVal2.mnNumerator, rVal2.mnDenominator);
385
0
}
386
387
// If dVal > LONG_MAX or dVal < LONG_MIN, the rational throws a boost::bad_rational.
388
// Otherwise, dVal and denominator are multiplied by 10, until one of them
389
// is larger than (LONG_MAX / 10).
390
//
391
// NOTE: here we use 'sal_Int32' due that only values in sal_Int32 range are valid.
392
static boost::rational<sal_Int32> rational_FromDouble(double dVal)
393
203M
{
394
203M
    if ( dVal > std::numeric_limits<sal_Int32>::max() ||
395
203M
         dVal < std::numeric_limits<sal_Int32>::min() ||
396
203M
         std::isnan(dVal) )
397
1.50k
        throw boost::bad_rational();
398
399
203M
    const sal_Int32 nMAX = std::numeric_limits<sal_Int32>::max() / 10;
400
203M
    sal_Int32 nDen = 1;
401
1.78G
    while ( std::abs( dVal ) < nMAX && nDen < nMAX ) {
402
1.58G
        dVal *= 10;
403
1.58G
        nDen *= 10;
404
1.58G
    }
405
203M
    return boost::rational<sal_Int32>( sal_Int32(dVal), nDen );
406
203M
}
407
408
/** Inaccurate cancellation for a fraction.
409
410
    Clip both nominator and denominator to said number of bits. If
411
    either of those already have equal or less number of bits used,
412
    this method does nothing.
413
414
    @param nSignificantBits denotes, how many significant binary
415
    digits to maintain, in both nominator and denominator.
416
417
    @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the
418
    largest error occurs with the following pair of values:
419
420
    binary    1000000011111111111111111111111b/1000000000000000000000000000000b
421
    =         1082130431/1073741824
422
    = approx. 1.007812499
423
424
    A ReduceInaccurate(8) yields 1/1.
425
*/
426
static void rational_ReduceInaccurate(boost::rational<sal_Int32>& rRational, unsigned nSignificantBits)
427
635
{
428
635
    if ( !rRational )
429
0
        return;
430
431
    // http://www.boost.org/doc/libs/release/libs/rational/rational.html#Internal%20representation
432
635
    sal_Int32 nMul = rRational.numerator();
433
635
    if (nMul == std::numeric_limits<sal_Int32>::min())
434
0
    {
435
        // ofz#32973 Integer-overflow
436
0
        return;
437
0
    }
438
635
    const bool bNeg = nMul < 0;
439
635
    if (bNeg)
440
77
        nMul = -nMul;
441
635
    sal_Int32 nDiv = rRational.denominator();
442
443
635
    DBG_ASSERT(nSignificantBits<65, "More than 64 bit of significance is overkill!");
444
445
    // How much bits can we lose?
446
635
    const int nMulBitsToLose = std::max( ( impl_NumberOfBits( nMul ) - int( nSignificantBits ) ), 0 );
447
635
    const int nDivBitsToLose = std::max( ( impl_NumberOfBits( nDiv ) - int( nSignificantBits ) ), 0 );
448
449
635
    const int nToLose = std::min( nMulBitsToLose, nDivBitsToLose );
450
451
    // Remove the bits
452
635
    nMul >>= nToLose;
453
635
    nDiv >>= nToLose;
454
455
635
    if ( !nMul || !nDiv ) {
456
        // Return without reduction
457
0
        OSL_FAIL( "Oops, we reduced too much..." );
458
0
        return;
459
0
    }
460
461
635
    rRational.assign( bNeg ? -nMul : nMul, nDiv );
462
635
}
463
464
size_t Fraction::GetHashValue() const
465
26.0M
{
466
26.0M
    size_t hash = 0;
467
26.0M
    o3tl::hash_combine( hash, mnNumerator );
468
26.0M
    o3tl::hash_combine( hash, mnDenominator );
469
26.0M
    o3tl::hash_combine( hash, mbValid );
470
26.0M
    return hash;
471
26.0M
}
472
473
Fraction Fraction::MakeFraction( tools::Long nN1, tools::Long nN2, tools::Long nD1, tools::Long nD2 )
474
26.0M
{
475
26.0M
    if( nD1 == 0 || nD2 == 0 ) //under these bad circumstances the following while loop will be endless
476
0
    {
477
0
        SAL_WARN("tools.fraction", "Invalid parameter for ImplMakeFraction");
478
0
        return Fraction( 1, 1 );
479
0
    }
480
481
26.0M
    tools::Long i = 1;
482
483
26.0M
    if ( nN1 < 0 ) { i = -i; nN1 = -nN1; }
484
26.0M
    if ( nN2 < 0 ) { i = -i; nN2 = -nN2; }
485
26.0M
    if ( nD1 < 0 ) { i = -i; nD1 = -nD1; }
486
26.0M
    if ( nD2 < 0 ) { i = -i; nD2 = -nD2; }
487
    // all positive; i sign
488
489
26.0M
    assert( nN1 >= std::numeric_limits<sal_Int32>::min() );
490
26.0M
    assert( nN1 <= std::numeric_limits<sal_Int32>::max( ));
491
26.0M
    assert( nD1 >= std::numeric_limits<sal_Int32>::min() );
492
26.0M
    assert( nD1 <= std::numeric_limits<sal_Int32>::max( ));
493
26.0M
    assert( nN2 >= std::numeric_limits<sal_Int32>::min() );
494
26.0M
    assert( nN2 <= std::numeric_limits<sal_Int32>::max( ));
495
26.0M
    assert( nD2 >= std::numeric_limits<sal_Int32>::min() );
496
26.0M
    assert( nD2 <= std::numeric_limits<sal_Int32>::max( ));
497
498
26.0M
    boost::rational<sal_Int32> a = toRational(i*nN1, nD1);
499
26.0M
    boost::rational<sal_Int32> b = toRational(nN2, nD2);
500
26.0M
    bool bFail = checked_multiply_by(a, b);
501
502
27.5M
    while ( bFail ) {
503
1.49M
        if ( nN1 > nN2 )
504
735k
            nN1 = (nN1 + 1) / 2;
505
757k
        else
506
757k
            nN2 = (nN2 + 1) / 2;
507
1.49M
        if ( nD1 > nD2 )
508
211k
            nD1 = (nD1 + 1) / 2;
509
1.28M
        else
510
1.28M
            nD2 = (nD2 + 1) / 2;
511
512
1.49M
        a = toRational(i*nN1, nD1);
513
1.49M
        b = toRational(nN2, nD2);
514
1.49M
        bFail = checked_multiply_by(a, b);
515
1.49M
    }
516
517
26.0M
    return Fraction(a.numerator(), a.denominator());
518
26.0M
}
519
520
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */