2.2 Variables and Data Types
Table of Contents
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:
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:
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:
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:
int a = 5; // copy initialization
int b(10); // direct initialization
int c{}; // value initialization, c becomes 0All 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:
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:
int x = 7;
int y = 2;
int z = x / y; // z becomes 3, the fractional part is lostIf 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:
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:
float prob = 0.001f; // float
double prob2 = 0.001; // doubleFloating-point division preserves fractional parts:
double a = 7;
double b = 2;
double c = a / b; // c becomes 3.5If you mix integers and floating-point types in an expression, the integers are automatically converted to floating-point before the operation:
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:
double x = 0.1 + 0.2;
bool equal = (x == 0.3); // may be false due to roundingInstead, compare with a tolerance:
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:
| Type | Kind | Typical use |
|---|---|---|
int | Integer | Counts, indices, event numbers |
float | Floating-point | Large arrays where memory matters |
double | Floating-point | Precise 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:
bool passedCut = true;
bool isMuon = false;Booleans often come from comparisons:
double energy = 10.5;
bool highEnergy = (energy > 5.0); // true
bool mediumEnergy = (energy > 5.0 && energy < 20.0);The standard logical operators are:
| Operator | Meaning | Example | ||||
|---|---|---|---|---|---|---|
== | equality | a == b | ||||
!= | inequality | a != b | ||||
< | less than | x < 0.0 | ||||
> | greater than | chi2 > 1.0 | ||||
<= | less or equal | n <= 10 | ||||
>= | greater or equal | n >= 100 | ||||
&& | logical AND | passedCut1 && passedCut2 | ||||
| ` | ` | logical OR | `isMuon | isElectron` | ||
! | logical NOT | !passedCut |
Booleans are used directly in if, else if, and while statements. For example:
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:
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:
bool passed = true;
std::cout << passed << std::endl; // prints 1
std::cout << std::boolalpha << passed; // prints trueIn 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:
char grade = 'A';
char sign = '+';
char newline = '\n'; // special newline characterCharacters 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:
char c = 'A';
int code = c; // converts 'A' to its integer codeFor 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:
char letter = 'a'; // one character
const char *text = "a"; // pointer to a string of length 1 plus terminatorYou can also use escape sequences to represent special characters that cannot be written directly. Some common examples are:
| Escape | Meaning |
|---|---|
'\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:
char c = 'Z';
std::cout << c << std::endl; // prints ZCharacter 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:
#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:
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:
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:
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:
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:
const char *treeName = "Events";
TTree *t = new TTree(treeName, "Event data");
You can convert between C-style strings and std::string easily:
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
KAHIBARO