Coverage Report

Created: 2026-04-09 11:41

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
0
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
216k
{
42
    // https://github.com/boostorg/boost/issues/335 when these are std::numeric_limits<sal_Int32>::min
43
216k
    if (n == d)
44
138k
        return 1;
45
    // tdf#144319 avoid boost::bad_rational e.g. if numerator=-476741369, denominator=-2147483648
46
78.0k
    if (d < -std::numeric_limits<sal_Int32>::max())
47
0
        return 0;
48
78.0k
    return boost::rational<sal_Int32>(n, d);
49
78.0k
}
50
51
static constexpr bool isOutOfRange(sal_Int64 nNum)
52
475k
{
53
475k
    return nNum < std::numeric_limits<sal_Int32>::min()
54
475k
            || nNum > std::numeric_limits<sal_Int32>::max();
55
475k
}
56
57
237k
Fraction::Fraction( sal_Int64 nNum, sal_Int64 nDen ) : mnNumerator(nNum), mnDenominator(nDen)
58
237k
{
59
237k
    if ( isOutOfRange(nNum) || isOutOfRange(nDen) )
60
0
    {
61
        // tdf#143200
62
0
        if (const auto gcd = std::gcd(nNum, nDen); gcd > 1)
63
0
        {
64
0
            nNum /= gcd;
65
0
            nDen /= gcd;
66
0
        }
67
0
        SAL_WARN_IF(isOutOfRange(nNum) || isOutOfRange(nDen),
68
0
                    "tools.fraction", "values outside of range we can represent, doing reduction, which will reduce precision");
69
0
        while (isOutOfRange(nNum) || isOutOfRange(nDen))
70
0
        {
71
0
            nNum /= 2;
72
0
            nDen /= 2;
73
0
        }
74
0
        mnNumerator = nNum;
75
0
        mnDenominator = nDen;
76
0
    }
77
237k
    if ( mnDenominator == 0 )
78
12.0k
    {
79
12.0k
        mbValid = false;
80
12.0k
        SAL_WARN( "tools.fraction", "'Fraction(" << nNum << ",0)' invalid fraction created" );
81
12.0k
        return;
82
12.0k
    }
83
225k
    else if ((nDen == -1 && nNum == std::numeric_limits<sal_Int32>::min()) ||
84
225k
            (nNum == -1 && nDen == std::numeric_limits<sal_Int32>::min()))
85
26
    {
86
26
        mbValid = false;
87
26
        SAL_WARN("tools.fraction", "'Fraction(" << nNum << "," << nDen << ")' invalid fraction created");
88
26
        return;
89
26
    }
90
237k
}
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
2.62k
{
101
2.62k
    try
102
2.62k
    {
103
2.62k
        boost::rational<sal_Int32> v = rational_FromDouble( dVal );
104
2.62k
        mnNumerator = v.numerator();
105
2.62k
        mnDenominator = v.denominator();
106
2.62k
    }
107
2.62k
    catch (const boost::bad_rational&)
108
2.62k
    {
109
0
        mbValid = false;
110
0
        SAL_WARN( "tools.fraction", "'Fraction(" << dVal << ")' invalid fraction created" );
111
0
    }
112
2.62k
}
113
114
Fraction::operator double() const
115
216k
{
116
216k
    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
216k
    return boost::rational_cast<double>(toRational(mnNumerator, mnDenominator));
123
216k
}
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
0
{
150
0
    if ( !rVal.mbValid )
151
0
        mbValid = false;
152
153
0
    if ( !mbValid )
154
0
    {
155
0
        SAL_WARN( "tools.fraction", "'operator -=' with invalid fraction" );
156
0
        return *this;
157
0
    }
158
159
0
    boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
160
0
    a -= toRational(rVal.mnNumerator, rVal.mnDenominator);
161
0
    mnNumerator = a.numerator();
162
0
    mnDenominator = a.denominator();
163
164
0
    return *this;
165
0
}
166
167
namespace
168
{
169
    bool checked_multiply_by(boost::rational<sal_Int32>& i, const boost::rational<sal_Int32>& r)
170
0
    {
171
        // Protect against self-modification
172
0
        sal_Int32 num = r.numerator();
173
0
        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
0
        if ((impl_NumberOfBits(std::abs(i.numerator())) + impl_NumberOfBits(std::abs(r.numerator()))) < 32 &&
178
0
            (impl_NumberOfBits(std::abs(i.denominator())) + impl_NumberOfBits(std::abs(r.denominator()))) < 32)
179
0
        {
180
0
            i *= r;
181
0
            return false;
182
0
        }
183
184
        // Avoid overflow and preserve normalization
185
0
        sal_Int32 gcd1 = std::gcd(i.numerator(), den);
186
0
        sal_Int32 gcd2 = std::gcd(num, i.denominator());
187
188
0
        if (!gcd1 || !gcd2)
189
0
            return true;
190
191
0
        bool fail = false;
192
0
        fail |= o3tl::checked_multiply(i.numerator() / gcd1, num / gcd2, num);
193
0
        fail |= o3tl::checked_multiply(i.denominator() / gcd2, den / gcd1, den);
194
195
0
        if (!fail)
196
0
            i.assign(num, den);
197
198
0
        return fail;
199
0
    }
200
}
201
202
Fraction& Fraction::operator *= ( const Fraction& rVal )
203
0
{
204
0
    if ( !rVal.mbValid )
205
0
        mbValid = false;
206
207
0
    if ( !mbValid )
208
0
    {
209
0
        SAL_WARN( "tools.fraction", "'operator *=' with invalid fraction" );
210
0
        return *this;
211
0
    }
212
213
0
    boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
214
0
    boost::rational<sal_Int32> b = toRational(rVal.mnNumerator, rVal.mnDenominator);
215
0
    bool bFail = checked_multiply_by(a, b);
216
0
    mnNumerator = a.numerator();
217
0
    mnDenominator = a.denominator();
218
219
0
    if (bFail)
220
0
    {
221
0
        mbValid = false;
222
0
    }
223
224
0
    return *this;
225
0
}
226
227
Fraction& Fraction::operator /= ( const Fraction& rVal )
228
0
{
229
0
    if ( !rVal.mbValid )
230
0
        mbValid = false;
231
232
0
    if ( !mbValid )
233
0
    {
234
0
        SAL_WARN( "tools.fraction", "'operator /=' with invalid fraction" );
235
0
        return *this;
236
0
    }
237
238
0
    boost::rational<sal_Int32> a = toRational(mnNumerator, mnDenominator);
239
0
    a /= toRational(rVal.mnNumerator, rVal.mnDenominator);
240
0
    mnNumerator = a.numerator();
241
0
    mnDenominator = a.denominator();
242
243
0
    return *this;
244
0
}
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
0
{
266
0
    if ( !mbValid )
267
0
    {
268
0
        SAL_WARN( "tools.fraction", "'ReduceInaccurate' on invalid fraction" );
269
0
        return;
270
0
    }
271
272
0
    if ( !mnNumerator )
273
0
        return;
274
275
0
    auto a = toRational(mnNumerator, mnDenominator);
276
0
    rational_ReduceInaccurate(a, nSignificantBits);
277
0
    mnNumerator = a.numerator();
278
0
    mnDenominator = a.denominator();
279
0
}
280
281
sal_Int32 Fraction::GetNumerator() const
282
25.6k
{
283
25.6k
    if ( !mbValid )
284
12.9k
    {
285
12.9k
        SAL_WARN( "tools.fraction", "'GetNumerator()' on invalid fraction" );
286
12.9k
        return 0;
287
12.9k
    }
288
12.6k
    return mnNumerator;
289
25.6k
}
290
291
sal_Int32 Fraction::GetDenominator() const
292
81.4k
{
293
81.4k
    if ( !mbValid )
294
19.4k
    {
295
19.4k
        SAL_WARN( "tools.fraction", "'GetDenominator()' on invalid fraction" );
296
19.4k
        return -1;
297
19.4k
    }
298
62.0k
    return mnDenominator;
299
81.4k
}
300
301
Fraction::operator sal_Int32() const
302
0
{
303
0
    if ( !mbValid )
304
0
    {
305
0
        SAL_WARN( "tools.fraction", "'operator sal_Int32()' on invalid fraction" );
306
0
        return 0;
307
0
    }
308
0
    return boost::rational_cast<sal_Int32>(toRational(mnNumerator, mnDenominator));
309
0
}
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
0
{
320
0
    Fraction aErg( rVal1 );
321
0
    aErg -= rVal2;
322
0
    return aErg;
323
0
}
324
325
Fraction operator*( const Fraction& rVal1, const Fraction& rVal2 )
326
0
{
327
0
    Fraction aErg( rVal1 );
328
0
    aErg *= rVal2;
329
0
    return aErg;
330
0
}
331
332
Fraction operator/( const Fraction& rVal1, const Fraction& rVal2 )
333
0
{
334
0
    Fraction aErg( rVal1 );
335
0
    aErg /= rVal2;
336
0
    return aErg;
337
0
}
338
339
bool operator !=( const Fraction& rVal1, const Fraction& rVal2 )
340
0
{
341
0
    return !(rVal1 == rVal2);
342
0
}
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
0
{
356
0
    if ( !rVal1.mbValid || !rVal2.mbValid )
357
0
    {
358
0
        SAL_WARN( "tools.fraction", "'operator ==' with an invalid fraction" );
359
0
        return false;
360
0
    }
361
362
0
    return toRational(rVal1.mnNumerator, rVal1.mnDenominator) == toRational(rVal2.mnNumerator, rVal2.mnDenominator);
363
0
}
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
2.62k
{
394
2.62k
    if ( dVal > std::numeric_limits<sal_Int32>::max() ||
395
2.62k
         dVal < std::numeric_limits<sal_Int32>::min() ||
396
2.62k
         std::isnan(dVal) )
397
0
        throw boost::bad_rational();
398
399
2.62k
    const sal_Int32 nMAX = std::numeric_limits<sal_Int32>::max() / 10;
400
2.62k
    sal_Int32 nDen = 1;
401
22.0k
    while ( std::abs( dVal ) < nMAX && nDen < nMAX ) {
402
19.4k
        dVal *= 10;
403
19.4k
        nDen *= 10;
404
19.4k
    }
405
2.62k
    return boost::rational<sal_Int32>( sal_Int32(dVal), nDen );
406
2.62k
}
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
0
{
428
0
    if ( !rRational )
429
0
        return;
430
431
    // http://www.boost.org/doc/libs/release/libs/rational/rational.html#Internal%20representation
432
0
    sal_Int32 nMul = rRational.numerator();
433
0
    if (nMul == std::numeric_limits<sal_Int32>::min())
434
0
    {
435
        // ofz#32973 Integer-overflow
436
0
        return;
437
0
    }
438
0
    const bool bNeg = nMul < 0;
439
0
    if (bNeg)
440
0
        nMul = -nMul;
441
0
    sal_Int32 nDiv = rRational.denominator();
442
443
0
    DBG_ASSERT(nSignificantBits<65, "More than 64 bit of significance is overkill!");
444
445
    // How much bits can we lose?
446
0
    const int nMulBitsToLose = std::max( ( impl_NumberOfBits( nMul ) - int( nSignificantBits ) ), 0 );
447
0
    const int nDivBitsToLose = std::max( ( impl_NumberOfBits( nDiv ) - int( nSignificantBits ) ), 0 );
448
449
0
    const int nToLose = std::min( nMulBitsToLose, nDivBitsToLose );
450
451
    // Remove the bits
452
0
    nMul >>= nToLose;
453
0
    nDiv >>= nToLose;
454
455
0
    if ( !nMul || !nDiv ) {
456
        // Return without reduction
457
0
        OSL_FAIL( "Oops, we reduced too much..." );
458
0
        return;
459
0
    }
460
461
0
    rRational.assign( bNeg ? -nMul : nMul, nDiv );
462
0
}
463
464
size_t Fraction::GetHashValue() const
465
0
{
466
0
    size_t hash = 0;
467
0
    o3tl::hash_combine( hash, mnNumerator );
468
0
    o3tl::hash_combine( hash, mnDenominator );
469
0
    o3tl::hash_combine( hash, mbValid );
470
0
    return hash;
471
0
}
472
473
Fraction Fraction::MakeFraction( tools::Long nN1, tools::Long nN2, tools::Long nD1, tools::Long nD2 )
474
0
{
475
0
    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
0
    tools::Long i = 1;
482
483
0
    if ( nN1 < 0 ) { i = -i; nN1 = -nN1; }
484
0
    if ( nN2 < 0 ) { i = -i; nN2 = -nN2; }
485
0
    if ( nD1 < 0 ) { i = -i; nD1 = -nD1; }
486
0
    if ( nD2 < 0 ) { i = -i; nD2 = -nD2; }
487
    // all positive; i sign
488
489
0
    assert( nN1 >= std::numeric_limits<sal_Int32>::min() );
490
0
    assert( nN1 <= std::numeric_limits<sal_Int32>::max( ));
491
0
    assert( nD1 >= std::numeric_limits<sal_Int32>::min() );
492
0
    assert( nD1 <= std::numeric_limits<sal_Int32>::max( ));
493
0
    assert( nN2 >= std::numeric_limits<sal_Int32>::min() );
494
0
    assert( nN2 <= std::numeric_limits<sal_Int32>::max( ));
495
0
    assert( nD2 >= std::numeric_limits<sal_Int32>::min() );
496
0
    assert( nD2 <= std::numeric_limits<sal_Int32>::max( ));
497
498
0
    boost::rational<sal_Int32> a = toRational(i*nN1, nD1);
499
0
    boost::rational<sal_Int32> b = toRational(nN2, nD2);
500
0
    bool bFail = checked_multiply_by(a, b);
501
502
0
    while ( bFail ) {
503
0
        if ( nN1 > nN2 )
504
0
            nN1 = (nN1 + 1) / 2;
505
0
        else
506
0
            nN2 = (nN2 + 1) / 2;
507
0
        if ( nD1 > nD2 )
508
0
            nD1 = (nD1 + 1) / 2;
509
0
        else
510
0
            nD2 = (nD2 + 1) / 2;
511
512
0
        a = toRational(i*nN1, nD1);
513
0
        b = toRational(nN2, nD2);
514
0
        bFail = checked_multiply_by(a, b);
515
0
    }
516
517
0
    return Fraction(a.numerator(), a.denominator());
518
0
}
519
520
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */