fraction
Overview
This module provides two things:
- A Fraction PHP class for representing and working with fractions.
- A Fraction field with 2 widgets and 3 formatters.
Fraction class
Usage:
$fraction = new Fraction(1, 2);
Get the numerator and denominator (as strings):
$numerator = $fraction->getNumerator();
$denominator = $fraction->getDenominator();
Convert the fraction to a decimal with precision 2:
$precision = 2;
$decimal = $fraction->toDecimal($precision);
Multiply fractions:
$fraction1 = new Fraction(2, 3);
$fraction2 = new Fraction(1, 2);
$fraction3 = $fraction1->multiply($fraction2);
Operations are performed using the BCMath PHP extension, when available. Otherwise, normal PHP float operations are used.
Fraction field
Widgets
(for editing the field)
Fraction - Two separate text fields for specifying the numerator and denominator separately. This is the best way to store very specific fractions, ie: 1/3.
Decimal - A single textfield that accepts a decimal representation of a fraction (ie: 0.33333). This is useful when working with prices. Note that the decimal is converted into a fraction with a base-10 denominator before saving (ie: 0.33 becomes 33/100).
Formatters
(for viewing the field)
Fraction - Simply displays the fraction's numerator and denominator separated with a slash, ie: 1/3.
Decimal - Displays the fraction as a decimal with a fixed precision. For example, the fraction 1/3 can be represented with a precision of 5 as 0.33333.
Percentage - Displays the fraction as a percentage with a fixed precision. This works the same as the Decimal formatter, but the value is multiplied by 100. For example, the fraction 1/3 can be represented with a precision of 5 as 33.33333%.
Database storage
Fractions are stored in the database using two columns: a numerator and a denominator.
The numerator is represented as a signed BIGINT, which allows for a range of values between -9223372036854775808 and 9223372036854775807.
The denominator is represented as a signed INT, which allows for a range of values between 1 and 2147483647 (zero and negative values are disallowed by the module).
Views integration
Views integration is provided by Views itself on behalf of the core Field module. Fraction extends some of the field handlers to allow sorting and filtering by the decimal equivalent of the fraction value (calculated via formula in the database query).
Using this module for price storage
This module aims to solve an outstanding issue with price storage in Drupal. Storing prices in the database can be tricky if you don't know what the decimal precision will need to be from the beginning. This is often the case when multiple currencies need to be supported, or when you need extra flexibility with decimal precision.
Drupal Commerce, for instance, ties precision directly to currency. So if all of your products are sold with a two-decimal price, but you want to add one with three decimal places, you need to add a second (duplicate) currency for just those products. There is an outstanding issue in the Commerce queue that is discussing solutions (one of which is to use a Fraction-based approach): #1125706: Price Implementation for Commerce 2.x
The alternative to using a decimal-based storage is to use floats. This brings more issues with precision, due to the way that floating-point numbers are represented in lower-level system storage. The limitations of float precision are outlined on PHP's website: http://www.php.net/manual/en/language.types.float.php (If you're REALLY interested, here's a great video that explains floating-point rounding errors: https://www.youtube.com/watch?v=PZRI1IfStY0)
Fraction overcomes these issues by storing the value as a fraction, using two integer fields in the database. Decimal representation of this fraction is provided in higher-level functions.
The best way to use this module for price storage is to utilize the "Decimal field" widget to collect the price, and the "Decimal" formatter to display it. This approach hides the fact that there are actually two numbers being stored in the database: numerator and denominator. This is achieved through a simple decimal-to-fraction conversion when a decimal value is saved.
When a decimal is converted to a fraction, the numerator and denominator are calculated such that the denominator is a power of 10. For example, the decimal 13.95 becomes 1395/100 in the database.
This means that for price storage, this module supports up to 9 decimal places of precision (because the maximum denominator is 2,147,483,647, meaning the highest possible base-10 number that fits into that is 1,000,000,000). Ultimately, the maximum numerator depends on the size of the denominator. But, using the maximum precision (of 9 decimal places), the biggest number this can store is 9,223,372,036.854775807.