1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <tools/fract.hxx>
21 #include <tools/debug.hxx>
22 #include <o3tl/hash_combine.hxx>
23 #include <o3tl/safeint.hxx>
24 #include <sal/log.hxx>
25 #include <osl/diagnose.h>
31 #include <boost/rational.hpp>
37 static boost::rational
<sal_Int32
> rational_FromDouble(double dVal
);
38 static void rational_ReduceInaccurate(boost::rational
<sal_Int32
>& rRational
, unsigned nSignificantBits
);
39 static int impl_NumberOfBits( sal_uInt32 nNum
);
41 static boost::rational
<sal_Int32
> toRational(sal_Int32 n
, sal_Int32 d
)
43 // https://github.com/boostorg/boost/issues/335 when these are std::numeric_limits<sal_Int32>::min
46 // tdf#144319 avoid boost::bad_rational e.g. if numerator=-476741369, denominator=-2147483648
47 if (d
< -std::numeric_limits
<sal_Int32
>::max())
49 return boost::rational
<sal_Int32
>(n
, d
);
52 static constexpr bool isOutOfRange(sal_Int64 nNum
)
54 return nNum
< std::numeric_limits
<sal_Int32
>::min()
55 || nNum
> std::numeric_limits
<sal_Int32
>::max();
58 Fraction::Fraction( sal_Int64 nNum
, sal_Int64 nDen
) : mnNumerator(nNum
), mnDenominator(nDen
)
60 if ( isOutOfRange(nNum
) || isOutOfRange(nDen
) )
63 if (const auto gcd
= std::gcd(nNum
, nDen
); gcd
> 1)
68 SAL_WARN_IF(isOutOfRange(nNum
) || isOutOfRange(nDen
),
69 "tools.fraction", "values outside of range we can represent, doing reduction, which will reduce precision");
70 while (isOutOfRange(nNum
) || isOutOfRange(nDen
))
78 if ( mnDenominator
== 0 )
81 SAL_WARN( "tools.fraction", "'Fraction(" << nNum
<< ",0)' invalid fraction created" );
84 else if ((nDen
== -1 && nNum
== std::numeric_limits
<sal_Int32
>::min()) ||
85 (nNum
== -1 && nDen
== std::numeric_limits
<sal_Int32
>::min()))
88 SAL_WARN("tools.fraction", "'Fraction(" << nNum
<< "," << nDen
<< ")' invalid fraction created");
94 * only here to prevent passing of NaN
96 Fraction::Fraction( double nNum
, double nDen
)
97 : Fraction(sal_Int64(nNum
), sal_Int64(nDen
))
100 Fraction::Fraction( double dVal
)
104 boost::rational
<sal_Int32
> v
= rational_FromDouble( dVal
);
105 mnNumerator
= v
.numerator();
106 mnDenominator
= v
.denominator();
108 catch (const boost::bad_rational
&)
111 SAL_WARN( "tools.fraction", "'Fraction(" << dVal
<< ")' invalid fraction created" );
115 Fraction::operator double() const
119 SAL_WARN( "tools.fraction", "'double()' on invalid fraction" );
123 return boost::rational_cast
<double>(toRational(mnNumerator
, mnDenominator
));
126 // This methods first validates both values.
127 // If one of the arguments is invalid, the whole operation is invalid.
128 // After computation detect if result overflows a sal_Int32 value
129 // which cause the operation to be marked as invalid
130 Fraction
& Fraction::operator += ( const Fraction
& rVal
)
137 SAL_WARN( "tools.fraction", "'operator +=' with invalid fraction" );
141 boost::rational
<sal_Int32
> a
= toRational(mnNumerator
, mnDenominator
);
142 a
+= toRational(rVal
.mnNumerator
, rVal
.mnDenominator
);
143 mnNumerator
= a
.numerator();
144 mnDenominator
= a
.denominator();
149 Fraction
& Fraction::operator -= ( const Fraction
& rVal
)
156 SAL_WARN( "tools.fraction", "'operator -=' with invalid fraction" );
160 boost::rational
<sal_Int32
> a
= toRational(mnNumerator
, mnDenominator
);
161 a
-= toRational(rVal
.mnNumerator
, rVal
.mnDenominator
);
162 mnNumerator
= a
.numerator();
163 mnDenominator
= a
.denominator();
170 bool checked_multiply_by(boost::rational
<sal_Int32
>& i
, const boost::rational
<sal_Int32
>& r
)
172 // Protect against self-modification
173 sal_Int32 num
= r
.numerator();
174 sal_Int32 den
= r
.denominator();
176 // Fast-path if the number of bits in input is < the number of bits in the output, overflow cannot happen
177 // This is considerably faster than repeated std::gcd() operations
178 if ((impl_NumberOfBits(std::abs(i
.numerator())) + impl_NumberOfBits(std::abs(r
.numerator()))) < 32 &&
179 (impl_NumberOfBits(std::abs(i
.denominator())) + impl_NumberOfBits(std::abs(r
.denominator()))) < 32)
185 // Avoid overflow and preserve normalization
186 sal_Int32 gcd1
= std::gcd(i
.numerator(), den
);
187 sal_Int32 gcd2
= std::gcd(num
, i
.denominator());
193 fail
|= o3tl::checked_multiply(i
.numerator() / gcd1
, num
/ gcd2
, num
);
194 fail
|= o3tl::checked_multiply(i
.denominator() / gcd2
, den
/ gcd1
, den
);
203 Fraction
& Fraction::operator *= ( const Fraction
& rVal
)
210 SAL_WARN( "tools.fraction", "'operator *=' with invalid fraction" );
214 boost::rational
<sal_Int32
> a
= toRational(mnNumerator
, mnDenominator
);
215 boost::rational
<sal_Int32
> b
= toRational(rVal
.mnNumerator
, rVal
.mnDenominator
);
216 bool bFail
= checked_multiply_by(a
, b
);
217 mnNumerator
= a
.numerator();
218 mnDenominator
= a
.denominator();
228 Fraction
& Fraction::operator /= ( const Fraction
& rVal
)
235 SAL_WARN( "tools.fraction", "'operator /=' with invalid fraction" );
239 boost::rational
<sal_Int32
> a
= toRational(mnNumerator
, mnDenominator
);
240 a
/= toRational(rVal
.mnNumerator
, rVal
.mnDenominator
);
241 mnNumerator
= a
.numerator();
242 mnDenominator
= a
.denominator();
247 /** Inaccurate cancellation for a fraction.
249 Clip both nominator and denominator to said number of bits. If
250 either of those already have equal or less number of bits used,
251 this method does nothing.
253 @param nSignificantBits denotes, how many significant binary
254 digits to maintain, in both nominator and denominator.
256 @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the
257 largest error occurs with the following pair of values:
259 binary 1000000011111111111111111111111b/1000000000000000000000000000000b
260 = 1082130431/1073741824
261 = approx. 1.007812499
263 A ReduceInaccurate(8) yields 1/1.
265 void Fraction::ReduceInaccurate( unsigned nSignificantBits
)
269 SAL_WARN( "tools.fraction", "'ReduceInaccurate' on invalid fraction" );
276 auto a
= toRational(mnNumerator
, mnDenominator
);
277 rational_ReduceInaccurate(a
, nSignificantBits
);
278 mnNumerator
= a
.numerator();
279 mnDenominator
= a
.denominator();
282 sal_Int32
Fraction::GetNumerator() const
286 SAL_WARN( "tools.fraction", "'GetNumerator()' on invalid fraction" );
292 sal_Int32
Fraction::GetDenominator() const
296 SAL_WARN( "tools.fraction", "'GetDenominator()' on invalid fraction" );
299 return mnDenominator
;
302 Fraction::operator sal_Int32() const
306 SAL_WARN( "tools.fraction", "'operator sal_Int32()' on invalid fraction" );
309 return boost::rational_cast
<sal_Int32
>(toRational(mnNumerator
, mnDenominator
));
312 Fraction
operator+( const Fraction
& rVal1
, const Fraction
& rVal2
)
314 Fraction
aErg( rVal1
);
319 Fraction
operator-( const Fraction
& rVal1
, const Fraction
& rVal2
)
321 Fraction
aErg( rVal1
);
326 Fraction
operator*( const Fraction
& rVal1
, const Fraction
& rVal2
)
328 Fraction
aErg( rVal1
);
333 Fraction
operator/( const Fraction
& rVal1
, const Fraction
& rVal2
)
335 Fraction
aErg( rVal1
);
340 bool operator !=( const Fraction
& rVal1
, const Fraction
& rVal2
)
342 return !(rVal1
== rVal2
);
345 bool operator <=( const Fraction
& rVal1
, const Fraction
& rVal2
)
347 return !(rVal1
> rVal2
);
350 bool operator >=( const Fraction
& rVal1
, const Fraction
& rVal2
)
352 return !(rVal1
< rVal2
);
355 bool operator == ( const Fraction
& rVal1
, const Fraction
& rVal2
)
357 if ( !rVal1
.mbValid
|| !rVal2
.mbValid
)
359 SAL_WARN( "tools.fraction", "'operator ==' with an invalid fraction" );
363 return toRational(rVal1
.mnNumerator
, rVal1
.mnDenominator
) == toRational(rVal2
.mnNumerator
, rVal2
.mnDenominator
);
366 bool operator < ( const Fraction
& rVal1
, const Fraction
& rVal2
)
368 if ( !rVal1
.mbValid
|| !rVal2
.mbValid
)
370 SAL_WARN( "tools.fraction", "'operator <' with an invalid fraction" );
374 return toRational(rVal1
.mnNumerator
, rVal1
.mnDenominator
) < toRational(rVal2
.mnNumerator
, rVal2
.mnDenominator
);
377 bool operator > ( const Fraction
& rVal1
, const Fraction
& rVal2
)
379 if ( !rVal1
.mbValid
|| !rVal2
.mbValid
)
381 SAL_WARN( "tools.fraction", "'operator >' with an invalid fraction" );
385 return toRational(rVal1
.mnNumerator
, rVal1
.mnDenominator
) > toRational(rVal2
.mnNumerator
, rVal2
.mnDenominator
);
388 // If dVal > LONG_MAX or dVal < LONG_MIN, the rational throws a boost::bad_rational.
389 // Otherwise, dVal and denominator are multiplied by 10, until one of them
390 // is larger than (LONG_MAX / 10).
392 // NOTE: here we use 'sal_Int32' due that only values in sal_Int32 range are valid.
393 static boost::rational
<sal_Int32
> rational_FromDouble(double dVal
)
395 if ( dVal
> std::numeric_limits
<sal_Int32
>::max() ||
396 dVal
< std::numeric_limits
<sal_Int32
>::min() ||
398 throw boost::bad_rational();
400 const sal_Int32 nMAX
= std::numeric_limits
<sal_Int32
>::max() / 10;
402 while ( std::abs( dVal
) < nMAX
&& nDen
< nMAX
) {
406 return boost::rational
<sal_Int32
>( sal_Int32(dVal
), nDen
);
410 * Find the number of bits required to represent this number, using the CLZ intrinsic
412 static int impl_NumberOfBits( sal_uInt32 nNum
)
418 _BitScanReverse(&r
, nNum
);
421 return 32 - __builtin_clz(nNum
);
425 /** Inaccurate cancellation for a fraction.
427 Clip both nominator and denominator to said number of bits. If
428 either of those already have equal or less number of bits used,
429 this method does nothing.
431 @param nSignificantBits denotes, how many significant binary
432 digits to maintain, in both nominator and denominator.
434 @example ReduceInaccurate(8) has an error <1% [1/2^(8-1)] - the
435 largest error occurs with the following pair of values:
437 binary 1000000011111111111111111111111b/1000000000000000000000000000000b
438 = 1082130431/1073741824
439 = approx. 1.007812499
441 A ReduceInaccurate(8) yields 1/1.
443 static void rational_ReduceInaccurate(boost::rational
<sal_Int32
>& rRational
, unsigned nSignificantBits
)
448 // http://www.boost.org/doc/libs/release/libs/rational/rational.html#Internal%20representation
449 sal_Int32 nMul
= rRational
.numerator();
450 if (nMul
== std::numeric_limits
<sal_Int32
>::min())
452 // ofz#32973 Integer-overflow
455 const bool bNeg
= nMul
< 0;
458 sal_Int32 nDiv
= rRational
.denominator();
460 DBG_ASSERT(nSignificantBits
<65, "More than 64 bit of significance is overkill!");
462 // How much bits can we lose?
463 const int nMulBitsToLose
= std::max( ( impl_NumberOfBits( nMul
) - int( nSignificantBits
) ), 0 );
464 const int nDivBitsToLose
= std::max( ( impl_NumberOfBits( nDiv
) - int( nSignificantBits
) ), 0 );
466 const int nToLose
= std::min( nMulBitsToLose
, nDivBitsToLose
);
472 if ( !nMul
|| !nDiv
) {
473 // Return without reduction
474 OSL_FAIL( "Oops, we reduced too much..." );
478 rRational
.assign( bNeg
? -nMul
: nMul
, nDiv
);
481 size_t Fraction::GetHashValue() const
484 o3tl::hash_combine( hash
, mnNumerator
);
485 o3tl::hash_combine( hash
, mnDenominator
);
486 o3tl::hash_combine( hash
, mbValid
);
490 Fraction
Fraction::MakeFraction( tools::Long nN1
, tools::Long nN2
, tools::Long nD1
, tools::Long nD2
)
492 if( nD1
== 0 || nD2
== 0 ) //under these bad circumstances the following while loop will be endless
494 SAL_WARN("tools.fraction", "Invalid parameter for ImplMakeFraction");
495 return Fraction( 1, 1 );
500 if ( nN1
< 0 ) { i
= -i
; nN1
= -nN1
; }
501 if ( nN2
< 0 ) { i
= -i
; nN2
= -nN2
; }
502 if ( nD1
< 0 ) { i
= -i
; nD1
= -nD1
; }
503 if ( nD2
< 0 ) { i
= -i
; nD2
= -nD2
; }
504 // all positive; i sign
506 assert( nN1
>= std::numeric_limits
<sal_Int32
>::min() );
507 assert( nN1
<= std::numeric_limits
<sal_Int32
>::max( ));
508 assert( nD1
>= std::numeric_limits
<sal_Int32
>::min() );
509 assert( nD1
<= std::numeric_limits
<sal_Int32
>::max( ));
510 assert( nN2
>= std::numeric_limits
<sal_Int32
>::min() );
511 assert( nN2
<= std::numeric_limits
<sal_Int32
>::max( ));
512 assert( nD2
>= std::numeric_limits
<sal_Int32
>::min() );
513 assert( nD2
<= std::numeric_limits
<sal_Int32
>::max( ));
515 boost::rational
<sal_Int32
> a
= toRational(i
*nN1
, nD1
);
516 boost::rational
<sal_Int32
> b
= toRational(nN2
, nD2
);
517 bool bFail
= checked_multiply_by(a
, b
);
529 a
= toRational(i
*nN1
, nD1
);
530 b
= toRational(nN2
, nD2
);
531 bFail
= checked_multiply_by(a
, b
);
534 return Fraction(a
.numerator(), a
.denominator());
537 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */