KAHIBARO
Discord Login Register

2.2 Variables and Data Types

Integers

In ROOT, as in standard C++, integers represent whole numbers without any fractional part. You use them to count events, index loops, label bins, and store any quantity that is naturally a whole number.

The most common integer type you will use is int:

cpp
int nEvents = 1000;
int bin = 5;
int runNumber = 12345;

An int can store both positive and negative values. The exact range depends on your system, but it is typically from about $-2 \times 10^9$ to $+2 \times 10^9$. ROOT follows the C++ rules here.

You can also use the long and long long types for larger ranges, or shorter types like short when you know the value will stay small:

cpp
short smallCounter = 10;
long long bigCounter = 10000000000LL;  // ten billion

The suffix LL tells the compiler that this is a long long literal. This is useful when you work with very large statistics or event counts.

There is also a distinction between signed and unsigned integers. A signed integer can be negative or positive. An unsigned integer is always non-negative, which doubles the maximum positive value that can be stored, at the cost of losing negative numbers:

cpp
unsigned int nTracks = 42;      // cannot be negative
int signedDifference = -3;      // can be negative

When you mix signed and unsigned integers in expressions, C++ applies conversion rules that can give surprising results if you are not careful. For example, subtracting a larger unsigned value from a smaller one can produce a large positive number instead of a negative number. For basic ROOT analysis it is usually simpler to stick to int unless you explicitly need an unsigned type.

Important rule: Use int for general counting and indexing, and reserve larger or unsigned integer types for situations where you clearly need them, such as very large event counts.

You can initialize integers in several ways:

cpp
int a = 5;        // copy initialization
int b(10);        // direct initialization
int c{};          // value initialization, c becomes 0

All of these are valid in ROOT, since ROOT uses standard C++ syntax.

You will often use integers in loops, especially when filling histograms or looping over entries in a tree. For example:

cpp
for (int i = 0; i < 100; ++i) {
   // do something with i
}

Integers behave as expected under arithmetic operations. Division between integers discards any fractional part:

cpp
int x = 7;
int y = 2;
int z = x / y;    // z becomes 3, the fractional part is lost

If you need fractional results, you must use floating-point types, which are introduced in the next section.

Floating-point numbers

Floating-point types store real numbers that may have a fractional part. In ROOT, they are essential for representing measurements, energies, momenta, positions, times, and any quantity that is not just a whole number.

The two main floating-point types you will use are float and double:

cpp
float x = 3.14f;
double energy = 13.6;
double mass = 0.938272;    // proton mass in GeV (approx)

A float usually has about 7 decimal digits of precision. A double usually has about 15 decimal digits. In most physics analyses you should prefer double to avoid unnecessary rounding errors.

Numeric literals that contain a decimal point are of type double by default. If you want a float literal, add the f suffix:

cpp
float prob = 0.001f;     // float
double prob2 = 0.001;    // double

Floating-point division preserves fractional parts:

cpp
double a = 7;
double b = 2;
double c = a / b;        // c becomes 3.5

If you mix integers and floating-point types in an expression, the integers are automatically converted to floating-point before the operation:

cpp
int n = 7;
int m = 2;
double r1 = n / m;         // integer division, result is 3, then converted to 3.0
double r2 = n / 2.0;       // mixed types, result is 3.5
double r3 = double(n) / m; // explicit cast, result is 3.5

Important rule: If you need a fractional result, make sure at least one operand of the division is a floating-point type, for example 2.0 instead of 2.

Floating-point numbers are only approximations to real numbers. Many simple decimal values cannot be represented exactly, which leads to small rounding errors. For this reason you should not compare floating-point values for exact equality:

cpp
double x = 0.1 + 0.2;
bool equal = (x == 0.3);     // may be false due to rounding

Instead, compare with a tolerance:

cpp
double x = 0.1 + 0.2;
double y = 0.3;
double eps = 1e-12;
bool approximatelyEqual = std::fabs(x - y) < eps;

ROOT provides many mathematical functions that operate on double values, such as TMath::Sin, TMath::Exp, and TMath::Sqrt. These are heavily used in analysis code.

A simple comparison of common numeric types is:

TypeKindTypical use
intIntegerCounts, indices, event numbers
floatFloating-pointLarge arrays where memory matters
doubleFloating-pointPrecise physics quantities

In ROOT histograms, the axis bin centers and contents are stored using floating-point types. Understanding floating-point behavior will help you interpret histogram results correctly.

Boolean values

Boolean types represent logical truth values. In C++ and ROOT, the bool type can hold either true or false. Booleans are fundamental for control flow, conditional statements, and selection cuts.

You can declare and assign booleans like this:

cpp
bool passedCut = true;
bool isMuon = false;

Booleans often come from comparisons:

cpp
double energy = 10.5;
bool highEnergy = (energy > 5.0);     // true
bool mediumEnergy = (energy > 5.0 && energy < 20.0);

The standard logical operators are:

OperatorMeaningExample
==equalitya == b
!=inequalitya != b
<less thanx < 0.0
>greater thanchi2 > 1.0
<=less or equaln <= 10
>=greater or equaln >= 100
&&logical ANDpassedCut1 && passedCut2
``logical OR`isMuonisElectron`
!logical NOT!passedCut

Booleans are used directly in if, else if, and while statements. For example:

cpp
bool passed = (energy > 1.0 && energy < 5.0);
if (passed) {
   // fill signal histogram
} else {
   // fill background histogram
}

In C++, integers can be converted to boolean automatically. Zero becomes false, and any non-zero value becomes true:

cpp
int n = 0;
bool b1 = n;      // false
n = 5;
bool b2 = n;      // true

Although this is allowed, it is clearer in analysis code to write explicit comparisons, such as n != 0.

Similarly, when you output a bool using std::cout, it appears as 0 or 1 by default. You can ask for true and false instead:

cpp
bool passed = true;
std::cout << passed << std::endl;          // prints 1
std::cout << std::boolalpha << passed;     // prints true

In ROOT analyses you will often use booleans to represent selection decisions, trigger conditions, detector status flags, or classification results.

Important rule: Use bool to represent logical conditions, not as a substitute for integers. Avoid using integer values like 0 and 1 directly when you really mean false and true.

Characters

Characters represent single text symbols, such as letters, digits, or punctuation. In C++ and ROOT, the basic character type is char. This type typically holds a single byte that represents a character code.

You write character literals in single quotes:

cpp
char grade = 'A';
char sign = '+';
char newline = '\n';    // special newline character

Characters are internally stored as integer codes, for example ASCII codes. Because of this, you can sometimes see them behave like small integers in expressions. For example:

cpp
char c = 'A';
int code = c;            // converts 'A' to its integer code

For ROOT analysis, you will usually not do arithmetic with characters. Instead, you might use them as simple flags, labels, or to read single-character fields from text files.

It is important not to confuse 'a' and "a". The first is a char literal, representing a single character. The second is a string literal, which has type const char* and represents a sequence of characters terminated by a null character. The difference is:

cpp
char letter = 'a';      // one character
const char *text = "a"; // pointer to a string of length 1 plus terminator

You can also use escape sequences to represent special characters that cannot be written directly. Some common examples are:

EscapeMeaning
'\n'newline
'\t'horizontal tab
'\\'backslash
'\''single quote
'\"'double quote

When you print characters using std::cout, they appear as the text symbol they represent:

cpp
char c = 'Z';
std::cout << c << std::endl;   // prints Z

Character types are not central to most ROOT analyses, but they appear occasionally in file formats, options strings, and small identifiers, so it is useful to recognize them.

Strings

Strings represent sequences of characters, for example file names, axis titles, and object names. Strings are essential in ROOT, since many classes use them for identification and labels.

In modern C++ you will usually work with the std::string class. ROOT is fully compatible with std::string, and you can use it freely in your macros:

cpp
#include <string>
std::string filename = "data.root";
std::string histName = "h_energy";
std::string title = "Energy spectrum;E (GeV);Events";

You can construct strings from string literals, which are enclosed in double quotes, and you can concatenate strings using the + operator:

cpp
std::string run = "Run";
std::string runNumber = "00123";
std::string runLabel = run + " " + runNumber;  // "Run 00123"

Many ROOT constructors and methods take const char instead of std::string. A const char is a pointer to a C-style string. You can pass a string literal directly, or you can convert an std::string using the c_str() method:

cpp
TH1F *h1 = new TH1F("h1", "Energy;E (GeV);Events", 100, 0, 10);
std::string hname = "h2";
std::string htitle = "Momentum;p (GeV/c);Events";
TH1F *h2 = new TH1F(hname.c_str(), htitle.c_str(), 100, 0, 5);

The c_str() method returns a const char* pointer to the internal character array of the std::string, which is what many ROOT functions expect.

You can also read input into strings, for example from text files or from the user, using standard C++ streams:

cpp
std::string line;
std::getline(std::cin, line);

Strings support many useful operations, such as checking their length, accessing individual characters, and searching for substrings:

cpp
std::string path = "/data/run00123.root";
std::size_t len = path.size();
char firstChar = path[0];            // '/'
bool containsData = (path.find("data") != std::string::npos);

C-style strings use char arrays terminated by a special null character '\0'. You will see them often in older code and in many ROOT interfaces. For example:

cpp
const char *treeName = "Events";
TTree *t = new TTree(treeName, "Event data");

You can convert between C-style strings and std::string easily:

cpp
const char *nameC = "histogram";
std::string nameS = nameC;           // construct std::string
const char *nameC2 = nameS.c_str();  // back to const char*

Important rule: Prefer std::string in your own C++ code for safety and convenience, and use .c_str() when you need to pass a string to ROOT functions that expect const char*.

In ROOT, strings are everywhere: object names, titles, axis labels, file paths, and selection expressions. Understanding how to handle both std::string and C-style strings will make writing ROOT macros much smoother.

Views: 12

Comments

Please login to add a comment.

Don't have an account? Register now!