Repository: ad-freiburg/qlever (language: python)

Issue:
`RAND` operator should produce a `xsd:double`
According to the SPARQL 1.1 spec, the `RAND` operator should produce a `xsd:double` between 0 and 1.

But it's not what I'm getting from the API. For example, with a query like:

```sparql
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
select ?disorder ?random ?label where {
	?disorder dc:type "disorder" .
	?disorder rdfs:label ?label
	FILTER langMatches(lang(?label), "fr")
    BIND(RAND() as ?random)
} order by ?random limit 1000
```

I'm getting this kind of payload:

```json
{
  "disorder": {
    "type": "uri",
    "value": "http://snomed.info/id/4448006"
  },
  "label": {
    "type": "literal",
    "value": "céphalée allergique",
    "xml:lang": "fr"
  },
  "random": {
    "datatype": "http://www.w3.org/2001/XMLSchema#int",
    "type": "literal",
    "value": "-576459726194747256"
  }
}
```

The type of the random variable being `xsd:int` instead of `xsd:double` and the value is way out of the spec bounds.


Discussion / hints:

Fix patch:
diff --git a/src/engine/sparqlExpressions/RandomExpression.h b/src/engine/sparqlExpressions/RandomExpression.h
index 66561e109..2aec490fb 100644
--- a/src/engine/sparqlExpressions/RandomExpression.h
+++ b/src/engine/sparqlExpressions/RandomExpression.h
@@ -22,19 +22,19 @@ class RandomExpression : public SparqlExpression {
     VectorWithMemoryLimit<Id> result{context->_allocator};
     const size_t numElements = context->_endIndex - context->_beginIndex;
     result.reserve(numElements);
-    ad_utility::FastRandomIntGenerator<int64_t> randInt;
+    ad_utility::RandomDoubleGenerator randDouble(0.0, 1.0);
 
     // As part of a GROUP BY we only return one value per group.
     if (context->_isPartOfGroupBy) {
-      return Id::makeFromInt(randInt() >> Id::numDatatypeBits);
+      return Id::makeFromDouble(randDouble());
     }
 
     // 1000 is an arbitrarily chosen interval at which to check for
     // cancellation.
     ad_utility::chunkedForLoop<1000>(
         0, numElements,
-        [&result, &randInt](size_t) {
-          result.push_back(Id::makeFromInt(randInt() >> Id::numDatatypeBits));
+        [&result, &randDouble](size_t) {
+          result.push_back(Id::makeFromDouble(randDouble()));
         },
         [context]() { context->cancellationHandle_->throwIfCancelled(); });
     return result;


Test patch:
diff --git a/test/RandomExpressionTest.cpp b/test/RandomExpressionTest.cpp
index 7430aba77..d7d408636 100644
--- a/test/RandomExpressionTest.cpp
+++ b/test/RandomExpressionTest.cpp
@@ -25,7 +25,9 @@ TEST(RandomExpression, evaluate) {
 
   std::vector<int64_t> histogram(10);
   for (auto rand : resultVector) {
-    ASSERT_EQ(rand.getDatatype(), Datatype::Int);
+    ASSERT_EQ(rand.getDatatype(), Datatype::Double);
+    ASSERT_GE(rand.getDouble(), 0.0);
+    ASSERT_LT(rand.getDouble(), 1.0);
     histogram[std::abs(rand.getInt()) % 10]++;
   }
 
diff --git a/test/RandomTest.cpp b/test/RandomTest.cpp
index d66389d28..fdcae374e 100644
--- a/test/RandomTest.cpp
+++ b/test/RandomTest.cpp
@@ -195,6 +195,38 @@ TEST(RandomNumberGeneratorTest, RandomDoubleGenerator) {
   testRange<RandomDoubleGenerator>(ranges);
 }
 
+// Test for the performance of `FastRandomIntGenerator` and
+// `RandomDoubleGenerator`.
+// NOTE: This does not actually test anything. It's just here to measure the
+// performance of the random number generators.
+TEST(RandomNumberGeneratorTest, PerformanceTes) {
+  // Create random number generators.
+  FastRandomIntGenerator<size_t> fastIntGenerator;
+  RandomDoubleGenerator doubleGenerator(0.0, 1.0);
+  size_t n = 1'000'000;
+  // Lambda for measuring the speed of a given random number generator.
+  auto measureAndShowSpeed = [&n](auto& generator, std::string name) {
+    auto start = std::chrono::high_resolution_clock::now();
+    decltype(generator()) sum = 0;
+    for (size_t i = 0; i < n; i++) {
+      sum += generator();
+    }
+    // Show in ns per number with one digit after the comma.
+    std::cout << "Speed of " << name << ": " << std::fixed
+              << std::setprecision(1)
+              << (static_cast<double>(
+                      std::chrono::duration_cast<std::chrono::nanoseconds>(
+                          std::chrono::high_resolution_clock::now() - start)
+                          .count()) /
+                  n)
+              << " ns per number" << std::setprecision(4)
+              << " [average value: " << (sum / n) << "]" << std::endl;
+  };
+  // Measure the time of our two random number generators.
+  measureAndShowSpeed(fastIntGenerator, "FastRandomIntGenerator");
+  measureAndShowSpeed(doubleGenerator, "RandomDoubleGenerator");
+}
+
 // Small test, if `randomShuffle` shuffles things the same way, if given the
 // same seed.
 TEST(RandomShuffleTest, Seed) {