diff --git a/resources/function_help/json/rand b/resources/function_help/json/rand index d1bba234bd3..3c24ee910f3 100644 --- a/resources/function_help/json/rand +++ b/resources/function_help/json/rand @@ -1,8 +1,9 @@ { "name": "rand", "type": "function", - "description": "Returns a random integer within the range specified by the minimum and maximum argument (inclusive).", + "description": "Returns a random integer within the range specified by the minimum and maximum argument (inclusive). If a seed is provided, the returned will always be the same, depending on the seed.", "arguments": [ {"arg":"min","description":"an integer representing the smallest possible random number desired"}, - {"arg":"max","description":"an integer representing the largest possible random number desired"}], + {"arg":"max","description":"an integer representing the largest possible random number desired"}, + {"arg":"seed","optional":true,"default":"null","description":"any value to use as seed"}], "examples": [ { "expression":"rand(1, 10)", "returns":"8"}] } diff --git a/resources/function_help/json/randf b/resources/function_help/json/randf index 10fa4b8d92a..b23f03b1920 100644 --- a/resources/function_help/json/randf +++ b/resources/function_help/json/randf @@ -1,8 +1,9 @@ { "name": "randf", "type": "function", - "description": "Returns a random float within the range specified by the minimum and maximum argument (inclusive).", + "description": "Returns a random float within the range specified by the minimum and maximum argument (inclusive). If a seed is provided, the returned will always be the same, depending on the seed.", "arguments": [ {"arg":"min","optional":true,"default":"0.0","description":"an float representing the smallest possible random number desired"}, - {"arg":"max","optional":true,"default":"1.0","description":"an float representing the largest possible random number desired"}], - "examples": [ { "expression":"randf(1, 10)", "returns":"4.59258286403147"}] + {"arg":"max","optional":true,"default":"1.0","description":"an float representing the largest possible random number desired"}, + {"arg":"seed","optional":true,"default":"null","description":"any value to use as seed"}], + "examples": [ { "expression":"randf(1, 10)", "returns":"4.59258286403147"} ] } diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 5972f8998a2..df9217290fd 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -368,22 +368,68 @@ static QVariant fcnRndF( const QVariantList &values, const QgsExpressionContext { double min = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent ); double max = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent ); + if ( max < min ) return QVariant(); + QRandomGenerator *generator; + if ( QgsExpressionUtils::isNull( values.at( 2 ) ) ) + { + generator = QRandomGenerator::global(); + } + else + { + quint32 seed; + if ( QgsExpressionUtils::isIntSafe( values.at( 2 ) ) ) + { + // if seed can be converted to int, we use as is + seed = QgsExpressionUtils::getIntValue( values.at( 2 ), parent ); + } + else + { + // if not, we hash string representation to int + QString seedStr = QgsExpressionUtils::getStringValue( values.at( 2 ), parent ); + std::hash hasher; + seed = hasher( seedStr.toStdString() ); + } + generator = &QRandomGenerator::QRandomGenerator( seed ); + } + // Return a random double in the range [min, max] (inclusive) - double f = static_cast< double >( qrand() ) / RAND_MAX; - return QVariant( min + f * ( max - min ) ); + return QVariant( min + generator->generateDouble() * ( max - min ) ); } static QVariant fcnRnd( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { - qlonglong min = QgsExpressionUtils::getIntValue( values.at( 0 ), parent ); - qlonglong max = QgsExpressionUtils::getIntValue( values.at( 1 ), parent ); + quint32 min = QgsExpressionUtils::getIntValue( values.at( 0 ), parent ); + quint32 max = QgsExpressionUtils::getIntValue( values.at( 1 ), parent ); if ( max < min ) return QVariant(); + QRandomGenerator *generator; + if ( QgsExpressionUtils::isNull( values.at( 2 ) ) ) + { + generator = QRandomGenerator::global(); + } + else + { + quint32 seed; + if ( QgsExpressionUtils::isIntSafe( values.at( 2 ) ) ) + { + // if seed can be converted to int, we use as is + seed = QgsExpressionUtils::getIntValue( values.at( 2 ), parent ); + } + else + { + // if not, we hash string representation to int + QString seedStr = QgsExpressionUtils::getStringValue( values.at( 2 ), parent ); + std::hash hasher; + seed = hasher( seedStr.toStdString() ); + } + generator = &QRandomGenerator::QRandomGenerator( seed ); + } + // Return a random integer in the range [min, max] (inclusive) - return QVariant( min + ( qrand() % static_cast< qlonglong >( max - min + 1 ) ) ); + return QVariant( generator->bounded( min, max + 1 ) ); } static QVariant fcnLinearScale( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) @@ -5216,11 +5262,11 @@ const QList &QgsExpression::Functions() << new QgsStaticExpressionFunction( QStringLiteral( "log" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "base" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnLog, QStringLiteral( "Math" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "round" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "places" ), true, 0 ), fcnRound, QStringLiteral( "Math" ) ); - QgsStaticExpressionFunction *randFunc = new QgsStaticExpressionFunction( QStringLiteral( "rand" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "min" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "max" ) ), fcnRnd, QStringLiteral( "Math" ) ); + QgsStaticExpressionFunction *randFunc = new QgsStaticExpressionFunction( QStringLiteral( "rand" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "min" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "max" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "seed" ), true ), fcnRnd, QStringLiteral( "Math" ) ); randFunc->setIsStatic( false ); functions << randFunc; - QgsStaticExpressionFunction *randfFunc = new QgsStaticExpressionFunction( QStringLiteral( "randf" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "min" ), true, 0.0 ) << QgsExpressionFunction::Parameter( QStringLiteral( "max" ), true, 1.0 ), fcnRndF, QStringLiteral( "Math" ) ); + QgsStaticExpressionFunction *randfFunc = new QgsStaticExpressionFunction( QStringLiteral( "randf" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "min" ), true, 0.0 ) << QgsExpressionFunction::Parameter( QStringLiteral( "max" ), true, 1.0 ) << QgsExpressionFunction::Parameter( QStringLiteral( "seed" ), true ), fcnRndF, QStringLiteral( "Math" ) ); randfFunc->setIsStatic( false ); functions << randfFunc; diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 999a0bd6bd5..3dbec218296 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -2160,6 +2160,34 @@ class TestQgsExpression: public QObject QgsExpression exp3( QStringLiteral( "rand(10,1)" ) ); QVariant v3 = exp3.evaluate(); QCOMPARE( v3.type(), QVariant::Invalid ); + + // Supports multiple type of seeds + QgsExpression exp4( QStringLiteral( "rand(1,100,123)" ) ); + QVariant v4 = exp4.evaluate(); + QCOMPARE( v4.type(), QVariant::Int ); + QgsExpression exp5( QStringLiteral( "rand(1,100,1.23)" ) ); + QVariant v5 = exp5.evaluate(); + QCOMPARE( v5.type(), QVariant::Int ); + QgsExpression exp6( QStringLiteral( "rand(1,100,'123')" ) ); + QVariant v6 = exp6.evaluate(); + QCOMPARE( v6.type(), QVariant::Int ); + QgsExpression exp7( QStringLiteral( "rand(1,100,'abc')" ) ); + QVariant v7 = exp7.evaluate(); + QCOMPARE( v7.type(), QVariant::Int ); + + // Two calls with the same seed return the same numer + QgsExpression exp8( QStringLiteral( "rand(1,100000000000,1)" ) ); + QVariant v8 = exp8.evaluate(); + QgsExpression exp9( QStringLiteral( "rand(1,100000000000,1)" ) ); + QVariant v9 = exp9.evaluate(); + QCOMPARE( v8.toInt() == v9.toInt(), true ); + + // Two calls with a different seed return a different number + QgsExpression exp10( QStringLiteral( "rand(1,100000000000,1)" ) ); + QVariant v10 = exp10.evaluate(); + QgsExpression exp11( QStringLiteral( "rand(1,100000000000,2)" ) ); + QVariant v11 = exp11.evaluate(); + QCOMPARE( v10.toInt() != v11.toInt(), true ); } void eval_randf() @@ -2177,6 +2205,34 @@ class TestQgsExpression: public QObject QgsExpression exp3( QStringLiteral( "randf(9.3333,1.784)" ) ); QVariant v3 = exp3.evaluate(); QCOMPARE( v3.type(), QVariant::Invalid ); + + // Supports multiple type of seeds + QgsExpression exp4( QStringLiteral( "randf(1,100,123)" ) ); + QVariant v4 = exp4.evaluate(); + QCOMPARE( v4.type(), QVariant::Float ); + QgsExpression exp5( QStringLiteral( "randf(1,100,1.23)" ) ); + QVariant v5 = exp5.evaluate(); + QCOMPARE( v5.type(), QVariant::Float ); + QgsExpression exp6( QStringLiteral( "randf(1,100,'123')" ) ); + QVariant v6 = exp6.evaluate(); + QCOMPARE( v6.type(), QVariant::Float ); + QgsExpression exp7( QStringLiteral( "randf(1,100,'abc')" ) ); + QVariant v7 = exp7.evaluate(); + QCOMPARE( v7.type(), QVariant::Float ); + + // Two calls with the same seed return the same numer + QgsExpression exp8( QStringLiteral( "randf(seed:=1)" ) ); + QVariant v8 = exp8.evaluate(); + QgsExpression exp9( QStringLiteral( "randf(seed:=1)" ) ); + QVariant v9 = exp9.evaluate(); + QCOMPARE( v8.toFloat() == v9.toFloat(), true ); + + // Two calls with a different seed return a different number + QgsExpression exp10( QStringLiteral( "randf(seed:=1)" ) ); + QVariant v10 = exp10.evaluate(); + QgsExpression exp11( QStringLiteral( "randf(seed:=2)" ) ); + QVariant v11 = exp11.evaluate(); + QCOMPARE( v10.toFloat() != v11.toFloat(), true ); } void referenced_columns()