I need to calculate the Ticklabels and the Tickrange for charts manually.
I know the "standard" algorithm for nice ticks (see http://books.google.de/books?id=fvA7zLEFWZgC&pg=PA61&lpg=PA61&redir_esc=y#v=onepage&q&f=false) and I also know this Java implementation.
The problem is, that with this algorithm, the ticks are "too smart". That means, The algorithm decides how much ticks should be displayed. My requirement is, that there are always 5 Ticks, but these should of course be "pretty". The naive approach would be to get the maximum value, divide with 5 and multiply with the ticknumber. The values here are - of course - not optimal and the ticks are pretty ugly.
Does anyone know a solution for the problem or have a hint for a formal algorithm description?
I am the author of "Algorithm for Optimal Scaling on a Chart Axis". It used to be hosted on trollop.org, but I have recently moved domains/blogging engines. Anyhow, I'll post the contents here for easier access.
I've been working on an Android charting application for an assignment and ran into a bit of an issue when it came to presenting the chart in a nicely scaled format. I spent a some time trying to create this algorithm on my own and came awfully close, but in the end I found a pseudo-code example in a book called "Graphics Gems, Volume 1" by Andrew S. Glassner. An excellent description of the problem is given in the chapter on "Nice Numbers for Graph Labels":
When creating a graph by computer, it is desirable to label the x and
y axes with "nice" numbers: simple decimal numbers. For example, if
the data range is 105 to 543, we'd probably want to plot the range
from 100 to 600 and put tick marks every 100 units. Or if the data
range is 2.04 to 2.16, we'd probably plot a range from 2.00 to 2.20
with a tick spacing of 0.05. Humans are good at choosing such "nice"
numbers, but simplistic algorithms are not. The naïve label-selection
algorithm takes the data range and divides it into n equal intervals,
but this usually results in ugly tick labels. We here describe a
simple method for generating nice graph labels.
The primary observation is that the "nicest" numbers in decimal are 1,
2, and 5, and all power-of-ten multiples of these numbers. We will use
only such numbers for the tick spacing, and place tick marks at
multiples of the tick spacing...
I used the pseudo-code example in this book to create the following class in Java:
public class NiceScale {
private double minPoint;
private double maxPoint;
private double maxTicks = 10;
private double tickSpacing;
private double range;
private double niceMin;
private double niceMax;
/**
* Instantiates a new instance of the NiceScale class.
*
* #param min the minimum data point on the axis
* #param max the maximum data point on the axis
*/
public NiceScale(double min, double max) {
this.minPoint = min;
this.maxPoint = max;
calculate();
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
private void calculate() {
this.range = niceNum(maxPoint - minPoint, false);
this.tickSpacing = niceNum(range / (maxTicks - 1), true);
this.niceMin =
Math.floor(minPoint / tickSpacing) * tickSpacing;
this.niceMax =
Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
private double niceNum(double range, boolean round) {
double exponent; /** exponent of range */
double fraction; /** fractional part of range */
double niceFraction; /** nice, rounded fraction */
exponent = Math.floor(Math.log10(range));
fraction = range / Math.pow(10, exponent);
if (round) {
if (fraction < 1.5)
niceFraction = 1;
else if (fraction < 3)
niceFraction = 2;
else if (fraction < 7)
niceFraction = 5;
else
niceFraction = 10;
} else {
if (fraction <= 1)
niceFraction = 1;
else if (fraction <= 2)
niceFraction = 2;
else if (fraction <= 5)
niceFraction = 5;
else
niceFraction = 10;
}
return niceFraction * Math.pow(10, exponent);
}
/**
* Sets the minimum and maximum data points for the axis.
*
* #param minPoint the minimum data point on the axis
* #param maxPoint the maximum data point on the axis
*/
public void setMinMaxPoints(double minPoint, double maxPoint) {
this.minPoint = minPoint;
this.maxPoint = maxPoint;
calculate();
}
/**
* Sets maximum number of tick marks we're comfortable with
*
* #param maxTicks the maximum number of tick marks for the axis
*/
public void setMaxTicks(double maxTicks) {
this.maxTicks = maxTicks;
calculate();
}
}
We can then make use of the above code like this:
NiceScale numScale = new NiceScale(-0.085, 0.173);
System.out.println("Tick Spacing:\t" + numScale.getTickSpacing());
System.out.println("Nice Minimum:\t" + numScale.getNiceMin());
System.out.println("Nice Maximum:\t" + numScale.getNiceMax());
Which will then output nicely formatted numbers for use in whatever application for which you need to create pretty scales. =D
Tick Spacing: 0.05
Nice Minimum: -0.1
Nice Maximum: 0.2
Here is a javascript version:
var minPoint;
var maxPoint;
var maxTicks = 10;
var tickSpacing;
var range;
var niceMin;
var niceMax;
/**
* Instantiates a new instance of the NiceScale class.
*
* min the minimum data point on the axis
* max the maximum data point on the axis
*/
function niceScale( min, max) {
minPoint = min;
maxPoint = max;
calculate();
return {
tickSpacing: tickSpacing,
niceMinimum: niceMin,
niceMaximum: niceMax
};
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
function calculate() {
range = niceNum(maxPoint - minPoint, false);
tickSpacing = niceNum(range / (maxTicks - 1), true);
niceMin =
Math.floor(minPoint / tickSpacing) * tickSpacing;
niceMax =
Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* localRange the data range
* round whether to round the result
* a "nice" number to be used for the data range
*/
function niceNum( localRange, round) {
var exponent; /** exponent of localRange */
var fraction; /** fractional part of localRange */
var niceFraction; /** nice, rounded fraction */
exponent = Math.floor(Math.log10(localRange));
fraction = localRange / Math.pow(10, exponent);
if (round) {
if (fraction < 1.5)
niceFraction = 1;
else if (fraction < 3)
niceFraction = 2;
else if (fraction < 7)
niceFraction = 5;
else
niceFraction = 10;
} else {
if (fraction <= 1)
niceFraction = 1;
else if (fraction <= 2)
niceFraction = 2;
else if (fraction <= 5)
niceFraction = 5;
else
niceFraction = 10;
}
return niceFraction * Math.pow(10, exponent);
}
/**
* Sets the minimum and maximum data points for the axis.
*
* minPoint the minimum data point on the axis
* maxPoint the maximum data point on the axis
*/
function setMinMaxPoints( localMinPoint, localMaxPoint) {
minPoint = localMinPoint;
maxPoint = localMaxoint;
calculate();
}
/**
* Sets maximum number of tick marks we're comfortable with
*
* maxTicks the maximum number of tick marks for the axis
*/
function setMaxTicks(localMaxTicks) {
maxTicks = localMaxTicks;
calculate();
}
Enjoy!
I have converted above java code to Python as per my requirement.
import math
class NiceScale:
def __init__(self, minv,maxv):
self.maxTicks = 6
self.tickSpacing = 0
self.lst = 10
self.niceMin = 0
self.niceMax = 0
self.minPoint = minv
self.maxPoint = maxv
self.calculate()
def calculate(self):
self.lst = self.niceNum(self.maxPoint - self.minPoint, False)
self.tickSpacing = self.niceNum(self.lst / (self.maxTicks - 1), True)
self.niceMin = math.floor(self.minPoint / self.tickSpacing) * self.tickSpacing
self.niceMax = math.ceil(self.maxPoint / self.tickSpacing) * self.tickSpacing
def niceNum(self, lst, rround):
self.lst = lst
exponent = 0 # exponent of range */
fraction = 0 # fractional part of range */
niceFraction = 0 # nice, rounded fraction */
exponent = math.floor(math.log10(self.lst));
fraction = self.lst / math.pow(10, exponent);
if (self.lst):
if (fraction < 1.5):
niceFraction = 1
elif (fraction < 3):
niceFraction = 2
elif (fraction < 7):
niceFraction = 5;
else:
niceFraction = 10;
else :
if (fraction <= 1):
niceFraction = 1
elif (fraction <= 2):
niceFraction = 2
elif (fraction <= 5):
niceFraction = 5
else:
niceFraction = 10
return niceFraction * math.pow(10, exponent)
def setMinMaxPoints(self, minPoint, maxPoint):
self.minPoint = minPoint
self.maxPoint = maxPoint
self.calculate()
def setMaxTicks(self, maxTicks):
self.maxTicks = maxTicks;
self.calculate()
a=NiceScale(14024, 17756)
print "a.lst ", a.lst
print "a.maxPoint ", a.maxPoint
print "a.maxTicks ", a.maxTicks
print "a.minPoint ", a.minPoint
print "a.niceMax ", a.niceMax
print "a.niceMin ", a.niceMin
print "a.tickSpacing ", a.tickSpacing
This is the Swift version:
class NiceScale {
private var minPoint: Double
private var maxPoint: Double
private var maxTicks = 10
private(set) var tickSpacing: Double = 0
private(set) var range: Double = 0
private(set) var niceMin: Double = 0
private(set) var niceMax: Double = 0
init(min: Double, max: Double) {
minPoint = min
maxPoint = max
calculate()
}
func setMinMaxPoints(min: Double, max: Double) {
minPoint = min
maxPoint = max
calculate()
}
private func calculate() {
range = niceNum(maxPoint - minPoint, round: false)
tickSpacing = niceNum(range / Double((maxTicks - 1)), round: true)
niceMin = floor(minPoint / tickSpacing) * tickSpacing
niceMax = floor(maxPoint / tickSpacing) * tickSpacing
}
private func niceNum(range: Double, round: Bool) -> Double {
let exponent = floor(log10(range))
let fraction = range / pow(10, exponent)
let niceFraction: Double
if round {
if fraction <= 1.5 {
niceFraction = 1
} else if fraction <= 3 {
niceFraction = 2
} else if fraction <= 7 {
niceFraction = 5
} else {
niceFraction = 10
}
} else {
if fraction <= 1 {
niceFraction = 1
} else if fraction <= 2 {
niceFraction = 2
} else if fraction <= 5 {
niceFraction = 5
} else {
niceFraction = 10
}
}
return niceFraction * pow(10, exponent)
}
}
Here's the C++ version. As a bonus you get a function that returns the minimum number of decimal points to display the tick labels on the axis.
The header file:
class NiceScale
{ public:
float minPoint;
float maxPoint;
float maxTicks;
float tickSpacing;
float range;
float niceMin;
float niceMax;
public:
NiceScale()
{ maxTicks = 10;
}
/**
* Instantiates a new instance of the NiceScale class.
*
* #param min the minimum data point on the axis
* #param max the maximum data point on the axis
*/
NiceScale(float min, float max)
{ minPoint = min;
maxPoint = max;
calculate();
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
void calculate() ;
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
float niceNum(float range, boolean round) ;
/**
* Sets the minimum and maximum data points for the axis.
*
* #param minPoint the minimum data point on the axis
* #param maxPoint the maximum data point on the axis
*/
void setMinMaxPoints(float minPoint, float maxPoint) ;
/**
* Sets maximum number of tick marks we're comfortable with
*
* #param maxTicks the maximum number of tick marks for the axis
*/
void setMaxTicks(float maxTicks) ;
int decimals(void);
};
And the CPP file:
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
void NiceScale::calculate()
{
range = niceNum(maxPoint - minPoint, false);
tickSpacing = niceNum(range / (maxTicks - 1), true);
niceMin = floor(minPoint / tickSpacing) * tickSpacing;
niceMax = ceil(maxPoint / tickSpacing) * tickSpacing;
}
/**
* Returns a "nice" number approximately equal to range
Rounds the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
float NiceScale::niceNum(float range, boolean round)
{ float exponent; /** exponent of range */
float fraction; /** fractional part of range */
float niceFraction; /** nice, rounded fraction */
exponent = floor(log10(range));
fraction = range / pow(10.f, exponent);
if (round)
{ if (fraction < 1.5)
niceFraction = 1;
else if (fraction < 3)
niceFraction = 2;
else if (fraction < 7)
niceFraction = 5;
else
niceFraction = 10;
}
else
{ if (fraction <= 1)
niceFraction = 1;
else if (fraction <= 2)
niceFraction = 2;
else if (fraction <= 5)
niceFraction = 5;
else
niceFraction = 10;
}
return niceFraction * pow(10, exponent);
}
/**
* Sets the minimum and maximum data points for the axis.
*
* #param minPoint the minimum data point on the axis
* #param maxPoint the maximum data point on the axis
*/
void NiceScale::setMinMaxPoints(float minPoint, float maxPoint)
{
this->minPoint = minPoint;
this->maxPoint = maxPoint;
calculate();
}
/**
* Sets maximum number of tick marks we're comfortable with
*
* #param maxTicks the maximum number of tick marks for the axis
*/
void NiceScale::setMaxTicks(float maxTicks)
{
this->maxTicks = maxTicks;
calculate();
}
// minimum number of decimals in tick labels
// use in sprintf statement:
// sprintf(buf, "%.*f", decimals(), tickValue);
int NiceScale::decimals(void)
{
float logTickX = log10(tickSpacing);
if(logTickX >= 0)
return 0;
return (int)(abs(floor(logTickX)));
}
You should be able to use the Java implementation with minor corrections.
Change maxticks to 5.
Change the calculate mehod to this:
private void calculate() {
this.range = niceNum(maxPoint - minPoint, false);
this.tickSpacing = niceNum(range / (maxTicks - 1), true);
this.niceMin =
Math.floor(minPoint / tickSpacing) * tickSpacing;
this.niceMax = this.niceMin + tickSpacing * (maxticks - 1); // Always display maxticks
}
Disclaimer: Note that I haven't tested this, so you may have to tweak it to make it look good. My suggested solution adds extra space at the top of the chart to always make room for 5 ticks. This may look ugly in some cases.
Here is the same thing in Objective C
YFRNiceScale.h
#import <Foundation/Foundation.h>
#interface YFRNiceScale : NSObject
#property (nonatomic, readonly) CGFloat minPoint;
#property (nonatomic, readonly) CGFloat maxPoint;
#property (nonatomic, readonly) CGFloat maxTicks;
#property (nonatomic, readonly) CGFloat tickSpacing;
#property (nonatomic, readonly) CGFloat range;
#property (nonatomic, readonly) CGFloat niceRange;
#property (nonatomic, readonly) CGFloat niceMin;
#property (nonatomic, readonly) CGFloat niceMax;
- (id) initWithMin: (CGFloat) min andMax: (CGFloat) max;
- (id) initWithNSMin: (NSDecimalNumber*) min andNSMax: (NSDecimalNumber*) max;
#end
YFRNiceScale.m
#import "YFRNiceScale.h"
#implementation YFRNiceScale
#synthesize minPoint = _minPoint;
#synthesize maxPoint = _maxPoint;
#synthesize maxTicks = _maxTicks;
#synthesize tickSpacing = _tickSpacing;
#synthesize range = _range;
#synthesize niceRange = _niceRange;
#synthesize niceMin = _niceMin;
#synthesize niceMax = _niceMax;
- (id)init {
self = [super init];
if (self) {
}
return self;
}
- (id) initWithMin: (CGFloat) min andMax: (CGFloat) max {
if (self) {
_maxTicks = 10;
_minPoint = min;
_maxPoint = max;
[self calculate];
}
return [self init];
}
- (id) initWithNSMin: (NSDecimalNumber*) min andNSMax: (NSDecimalNumber*) max {
if (self) {
_maxTicks = 10;
_minPoint = [min doubleValue];
_maxPoint = [max doubleValue];
[self calculate];
}
return [self init];
}
/**
* Calculate and update values for tick spacing and nice minimum and maximum
* data points on the axis.
*/
- (void) calculate {
_range = [self niceNumRange: (_maxPoint-_minPoint) roundResult:NO];
_tickSpacing = [self niceNumRange: (_range / (_maxTicks - 1)) roundResult:YES];
_niceMin = floor(_minPoint / _tickSpacing) * _tickSpacing;
_niceMax = ceil(_maxPoint / _tickSpacing) * _tickSpacing;
_niceRange = _niceMax - _niceMin;
}
/**
* Returns a "nice" number approximately equal to range Rounds the number if
* round = true Takes the ceiling if round = false.
*
* #param range
* the data range
* #param round
* whether to round the result
* #return a "nice" number to be used for the data range
*/
- (CGFloat) niceNumRange:(CGFloat) aRange roundResult:(BOOL) round {
CGFloat exponent;
CGFloat fraction;
CGFloat niceFraction;
exponent = floor(log10(aRange));
fraction = aRange / pow(10, exponent);
if (round) {
if (fraction < 1.5) {
niceFraction = 1;
} else if (fraction < 3) {
niceFraction = 2;
} else if (fraction < 7) {
niceFraction = 5;
} else {
niceFraction = 10;
}
} else {
if (fraction <= 1) {
niceFraction = 1;
} else if (fraction <= 2) {
niceFraction = 2;
} else if (fraction <= 5) {
niceFraction = 2;
} else {
niceFraction = 10;
}
}
return niceFraction * pow(10, exponent);
}
- (NSString*) description {
return [NSString stringWithFormat:#"NiceScale [minPoint=%.2f, maxPoint=%.2f, maxTicks=%.2f, tickSpacing=%.2f, range=%.2f, niceMin=%.2f, niceMax=%.2f]", _minPoint, _maxPoint, _maxTicks, _tickSpacing, _range, _niceMin, _niceMax ];
}
#end
Usage:
YFRNiceScale* niceScale = [[YFRNiceScale alloc] initWithMin:0 andMax:500];
NSLog(#"Nice: %#", niceScale);
Here it is in TypeScript!
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
function calculateTicks(maxTicks: number, minPoint: number, maxPoint: number): [number, number, number] {
let range = niceNum(maxPoint - minPoint, false);
let tickSpacing = niceNum(range / (maxTicks - 1), true);
let niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
let niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
let tickCount = range / tickSpacing;
return [tickCount, niceMin, niceMax];
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
function niceNum(range: number, round: boolean): number {
let exponent: number;
/** exponent of range */
let fraction: number;
/** fractional part of range */
let niceFraction: number;
/** nice, rounded fraction */
exponent = Math.floor(Math.log10(range));
fraction = range / Math.pow(10, exponent);
if (round) {
if (fraction < 1.5)
niceFraction = 1;
else if (fraction < 3)
niceFraction = 2;
else if (fraction < 7)
niceFraction = 5;
else
niceFraction = 10;
} else {
if (fraction <= 1)
niceFraction = 1;
else if (fraction <= 2)
niceFraction = 2;
else if (fraction <= 5)
niceFraction = 5;
else
niceFraction = 10;
}
return niceFraction * Math.pow(10, exponent);
}
I found this thread while writing some php, so now the same code is available in php too!
class CNiceScale {
private $minPoint;
private $maxPoint;
private $maxTicks = 10;
private $tickSpacing;
private $range;
private $niceMin;
private $niceMax;
public function setScale($min, $max) {
$this->minPoint = $min;
$this->maxPoint = $max;
$this->calculate();
}
private function calculate() {
$this->range = $this->niceNum($this->maxPoint - $this->minPoint, false);
$this->tickSpacing = $this->niceNum($this->range / ($this->maxTicks - 1), true);
$this->niceMin = floor($this->minPoint / $this->tickSpacing) * $this->tickSpacing;
$this->niceMax = ceil($this->maxPoint / $this->tickSpacing) * $this->tickSpacing;
}
private function niceNum($range, $round) {
$exponent; /** exponent of range */
$fraction; /** fractional part of range */
$niceFraction; /** nice, rounded fraction */
$exponent = floor(log10($range));
$fraction = $range / pow(10, $exponent);
if ($round) {
if ($fraction < 1.5)
$niceFraction = 1;
else if ($fraction < 3)
$niceFraction = 2;
else if ($fraction < 7)
$niceFraction = 5;
else
$niceFraction = 10;
} else {
if ($fraction <= 1)
$niceFraction = 1;
else if ($fraction <= 2)
$niceFraction = 2;
else if ($fraction <= 5)
$niceFraction = 5;
else
$niceFraction = 10;
}
return $niceFraction * pow(10, $exponent);
}
public function setMinMaxPoints($minPoint, $maxPoint) {
$this->minPoint = $minPoint;
$this->maxPoint = $maxPoint;
$this->calculate();
}
public function setMaxTicks($maxTicks) {
$this->maxTicks = $maxTicks;
$this->calculate();
}
public function getTickSpacing() {
return $this->tickSpacing;
}
public function getNiceMin() {
return $this->niceMin;
}
public function getNiceMax() {
return $this->niceMax;
}
}
I needed this algorithm converted to C#, so here it is...
public static class NiceScale {
public static void Calculate(double min, double max, int maxTicks, out double range, out double tickSpacing, out double niceMin, out double niceMax) {
range = niceNum(max - min, false);
tickSpacing = niceNum(range / (maxTicks - 1), true);
niceMin = Math.Floor(min / tickSpacing) * tickSpacing;
niceMax = Math.Ceiling(max / tickSpacing) * tickSpacing;
}
private static double niceNum(double range, bool round) {
double pow = Math.Pow(10, Math.Floor(Math.Log10(range)));
double fraction = range / pow;
double niceFraction;
if (round) {
if (fraction < 1.5) {
niceFraction = 1;
} else if (fraction < 3) {
niceFraction = 2;
} else if (fraction < 7) {
niceFraction = 5;
} else {
niceFraction = 10;
}
} else {
if (fraction <= 1) {
niceFraction = 1;
} else if (fraction <= 2) {
niceFraction = 2;
} else if (fraction <= 5) {
niceFraction = 5;
} else {
niceFraction = 10;
}
}
return niceFraction * pow;
}
}
Here'a better organized C# code.
public class NiceScale
{
public double NiceMin { get; set; }
public double NiceMax { get; set; }
public double TickSpacing { get; private set; }
private double _minPoint;
private double _maxPoint;
private double _maxTicks = 5;
private double _range;
/**
* Instantiates a new instance of the NiceScale class.
*
* #param min the minimum data point on the axis
* #param max the maximum data point on the axis
*/
public NiceScale(double min, double max)
{
_minPoint = min;
_maxPoint = max;
Calculate();
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
private void Calculate()
{
_range = NiceNum(_maxPoint - _minPoint, false);
TickSpacing = NiceNum(_range / (_maxTicks - 1), true);
NiceMin = Math.Floor(_minPoint / TickSpacing) * TickSpacing;
NiceMax = Math.Ceiling(_maxPoint / TickSpacing) * TickSpacing;
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling if round = false.
*
* #param range the data range
* #param round whether to round the result
* #return a "nice" number to be used for the data range
*/
private double NiceNum(double range, bool round)
{
double exponent; /** exponent of range */
double fraction; /** fractional part of range */
double niceFraction; /** nice, rounded fraction */
exponent = Math.Floor(Math.Log10(range));
fraction = range / Math.Pow(10, exponent);
if (round) {
if (fraction < 1.5)
niceFraction = 1;
else if (fraction < 3)
niceFraction = 2;
else if (fraction < 7)
niceFraction = 5;
else
niceFraction = 10;
} else {
if (fraction <= 1)
niceFraction = 1;
else if (fraction <= 2)
niceFraction = 2;
else if (fraction <= 5)
niceFraction = 5;
else
niceFraction = 10;
}
return niceFraction * Math.Pow(10, exponent);
}
/**
* Sets the minimum and maximum data points for the axis.
*
* #param minPoint the minimum data point on the axis
* #param maxPoint the maximum data point on the axis
*/
public void SetMinMaxPoints(double minPoint, double maxPoint)
{
_minPoint = minPoint;
_maxPoint = maxPoint;
Calculate();
}
/**
* Sets maximum number of tick marks we're comfortable with
*
* #param maxTicks the maximum number of tick marks for the axis
*/
public void SetMaxTicks(double maxTicks)
{
_maxTicks = maxTicks;
Calculate();
}
}
Here's the Kotlin version!
import java.lang.Math.*
/**
* Instantiates a new instance of the NiceScale class.
*
* #param min Double The minimum data point.
* #param max Double The maximum data point.
*/
class NiceScale(private var minPoint: Double, private var maxPoint: Double) {
private var maxTicks = 15.0
private var range: Double = 0.0
var niceMin: Double = 0.0
var niceMax: Double = 0.0
var tickSpacing: Double = 0.0
init {
calculate()
}
/**
* Calculate and update values for tick spacing and nice
* minimum and maximum data points on the axis.
*/
private fun calculate() {
range = niceNum(maxPoint - minPoint, false)
tickSpacing = niceNum(range / (maxTicks - 1), true)
niceMin = floor(minPoint / tickSpacing) * tickSpacing
niceMax = ceil(maxPoint / tickSpacing) * tickSpacing
}
/**
* Returns a "nice" number approximately equal to range. Rounds
* the number if round = true. Takes the ceiling if round = false.
*
* #param range Double The data range.
* #param round Boolean Whether to round the result.
* #return Double A "nice" number to be used for the data range.
*/
private fun niceNum(range: Double, round: Boolean): Double {
/** Exponent of range */
val exponent: Double = floor(log10(range))
/** Fractional part of range */
val fraction: Double
/** Nice, rounded fraction */
val niceFraction: Double
fraction = range / pow(10.0, exponent)
niceFraction = if (round) {
when {
fraction < 1.5 -> 1.0
fraction < 3 -> 2.0
fraction < 7 -> 5.0
else -> 10.0
}
} else {
when {
fraction <= 1 -> 1.0
fraction <= 2 -> 2.0
fraction <= 5 -> 5.0
else -> 10.0
}
}
return niceFraction * pow(10.0, exponent)
}
/**
* Sets the minimum and maximum data points.
*
* #param minPoint Double The minimum data point.
* #param maxPoint Double The maximum data point.
*/
fun setMinMaxPoints(minPoint: Double, maxPoint: Double) {
this.minPoint = minPoint
this.maxPoint = maxPoint
calculate()
}
/**
* Sets maximum number of tick marks we're comfortable with.
*
* #param maxTicks Double The maximum number of tick marks.
*/
fun setMaxTicks(maxTicks: Double) {
this.maxTicks = maxTicks
calculate()
}
}
Since everybody and his dog is posting a translation to other popular languages, here is my version for the Nim programming language. I also added handling of cases where the amount of ticks is less than two:
import math, strutils
const
defaultMaxTicks = 10
type NiceScale = object
minPoint: float
maxPoint: float
maxTicks: int
tickSpacing: float
niceMin: float
niceMax: float
proc ff(x: float): string =
result = x.formatFloat(ffDecimal, 3)
proc `$`*(x: NiceScale): string =
result = "Input minPoint: " & x.minPoint.ff &
"\nInput maxPoint: " & x.maxPoint.ff &
"\nInput maxTicks: " & $x.maxTicks &
"\nOutput niceMin: " & x.niceMin.ff &
"\nOutput niceMax: " & x.niceMax.ff &
"\nOutput tickSpacing: " & x.tickSpacing.ff &
"\n"
proc calculate*(x: var NiceScale)
proc init*(x: var NiceScale; minPoint, maxPoint: float;
maxTicks = defaultMaxTicks) =
x.minPoint = minPoint
x.maxPoint = maxPoint
x.maxTicks = maxTicks
x.calculate
proc initScale*(minPoint, maxPoint: float;
maxTicks = defaultMaxTicks): NiceScale =
result.init(minPoint, maxPoint, maxTicks)
proc niceNum(scaleRange: float; doRound: bool): float =
var
exponent: float ## Exponent of scaleRange.
fraction: float ## Fractional part of scaleRange.
niceFraction: float ## Nice, rounded fraction.
exponent = floor(log10(scaleRange));
fraction = scaleRange / pow(10, exponent);
if doRound:
if fraction < 1.5:
niceFraction = 1
elif fraction < 3:
niceFraction = 2
elif fraction < 7:
niceFraction = 5
else:
niceFraction = 10
else:
if fraction <= 1:
niceFraction = 1
elif fraction <= 2:
niceFraction = 2
elif fraction <= 5:
niceFraction = 5
else:
niceFraction = 10
return niceFraction * pow(10, exponent)
proc calculate*(x: var NiceScale) =
assert x.maxPoint > x.minPoint, "Wrong input range!"
assert x.maxTicks >= 0, "Sorry, can't have imaginary ticks!"
let scaleRange = niceNum(x.maxPoint - x.minPoint, false)
if x.maxTicks < 2:
x.niceMin = floor(x.minPoint)
x.niceMax = ceil(x.maxPoint)
x.tickSpacing = (x.niceMax - x.niceMin) /
(if x.maxTicks == 1: 2.0 else: 1.0)
else:
x.tickSpacing = niceNum(scaleRange / (float(x.maxTicks - 1)), true)
x.niceMin = floor(x.minPoint / x.tickSpacing) * x.tickSpacing
x.niceMax = ceil(x.maxPoint / x.tickSpacing) * x.tickSpacing
when isMainModule:
var s = initScale(57.2, 103.3)
echo s
This is the comment stripped version. Full one can be read at GitHub integrated into my project.
//Swift, more compact
public struct NiceScale
{
var minPoint: Double
var maxPoint: Double
var maxTicks = 10
var tickSpacing: Double { niceNum(range: range / Double(maxTicks - 1), round: true) }
var range: Double { niceNum(range: maxPoint - minPoint, round: false) }
var niceMin: Double { floor(minPoint / tickSpacing) * tickSpacing }
var niceMax: Double { ceil(maxPoint / tickSpacing) * tickSpacing }
// min the minimum data point on the axis
// max the maximum data point on the axis
init( min: Double, max: Double, maxTicks: Int = 10)
{
minPoint = min
maxPoint = max
self.maxTicks = maxTicks
}
/**
* Returns a "nice" number approximately equal to range Rounds
* the number if round = true Takes the ceiling
* if round = false range the data range
* round whether to round the result
* return a "nice" number to be used for the data range
*/
func niceNum( range: Double, round: Bool) -> Double
{
let exponent: Double = floor(log10(range)) // exponent of range
let fraction: Double = range / pow(10, exponent) // fractional part of range
var niceFraction: Double = 10.0 // nice, rounded fraction
if round {
if fraction < 1.5 { niceFraction = 1 }
else if fraction < 3 { niceFraction = 2 }
else if fraction < 7 { niceFraction = 5 }
} else {
if fraction <= 1 { niceFraction = 1 }
else if fraction <= 2 { niceFraction = 2 }
else if fraction <= 5 { niceFraction = 5 }
}
return niceFraction * pow(10, exponent)
}
static func testNiceScale()
{
var numScale = NiceScale(min: -0.085, max: 0.173)
print("Tick Spacing:\t \( numScale.tickSpacing)")
print("Nice Minimum:\t\( numScale.niceMin)")
print("Nice Maximum:\t\( numScale.niceMax)")
numScale = NiceScale(min: 4.44, max: 7.962)
print("nice num:\t\( numScale.niceNum(range: 7.962 - 4.44, round: false))")
print("Tick Spacing:\t\( numScale.tickSpacing)")
print("Nice Minimum:\t\( numScale.niceMin)")
print("Nice Maximum:\t\( numScale.niceMax)")
}
}
<\pre> <\code>
This is the VB.NET version.
Public Class NiceScale
Private minPoint As Double
Private maxPoint As Double
Private maxTicks As Double = 10
Private tickSpacing
Private range As Double
Private niceMin As Double
Private niceMax As Double
Public Sub New(min As Double, max As Double)
minPoint = min
maxPoint = max
calculate()
End Sub
Private Sub calculate()
range = niceNum(maxPoint - minPoint, False)
tickSpacing = niceNum(range / (maxTicks - 1), True)
niceMin = Math.Floor(minPoint / tickSpacing) * tickSpacing
niceMax = Math.Ceiling(maxPoint / tickSpacing) * tickSpacing
End Sub
Private Function niceNum(range As Double, round As Boolean) As Double
Dim exponent As Double '/** exponent of range */
Dim fraction As Double '/** fractional part of range */
Dim niceFraction As Double '/** nice, rounded fraction */
exponent = Math.Floor(Math.Log10(range))
fraction = range / Math.Pow(10, exponent)
If round Then
If (fraction < 1.5) Then
niceFraction = 1
ElseIf (fraction < 3) Then
niceFraction = 2
ElseIf (fraction < 7) Then
niceFraction = 5
Else
niceFraction = 10
End If
Else
If (fraction <= 1) Then
niceFraction = 1
ElseIf (fraction <= 2) Then
niceFraction = 2
ElseIf (fraction <= 5) Then
niceFraction = 5
Else
niceFraction = 10
End If
End If
Return niceFraction * Math.Pow(10, exponent)
End Function
Public Sub setMinMaxPoints(minPoint As Double, maxPoint As Double)
minPoint = minPoint
maxPoint = maxPoint
calculate()
End Sub
Public Sub setMaxTicks(maxTicks As Double)
maxTicks = maxTicks
calculate()
End Sub
Public Function getTickSpacing() As Double
Return tickSpacing
End Function
Public Function getNiceMin() As Double
Return niceMin
End Function
Public Function getNiceMax() As Double
Return niceMax
End Function
End Class
Much BETTER and SIMPLER algorythm on swift. Size is fixed, values are not "hardcoded":
class NiceNumbers {
/// Returns nice range of specified size. Result min <= min argument, result max >= max argument.
static func getRange(forMin minInt: Int, max maxInt: Int, ofSize size: Int) -> [Int] {
let niceMinInt = getMinCloseToZero(min: minInt, max: maxInt)
let step = Double(maxInt - niceMinInt) / Double(size - 1)
let niceStepInt = Int(get(for: step, min: false))
var result = [Int]()
result.append(niceMinInt)
for i in 1...size - 1 {
result.append(niceMinInt + i * Int(niceStepInt))
}
return result
}
/// Returns nice min or zero if it is much smaller than max.
static func getMinCloseToZero(min: Int, max: Int) -> Int {
let nice = get(for: Double(min), min: true)
return nice <= (Double(max) / 10) ? 0 : Int(nice)
}
/// Get nice number. If min is true returns smaller number, if false - bigger one.
static func get(for number: Double, min: Bool) -> Double {
if number == 0 { return 0 }
let exponent = floor(log10(number)) - (min ? 0 : 1)
let fraction = number / pow(10, exponent)
let niceFraction = min ? floor(fraction) : ceil(fraction)
return niceFraction * pow(10, exponent)
}
}
Tested only on positive numbers.
Dart / Flutter Version:
import 'dart:math';
void main() {
double min = 3, max = 28;
var scale = NiceScale(min, max, 5);
print("Range: $min-$max; Max Point: ${scale.niceMax}; Min Point: ${scale.niceMin}; Steps: ${scale.tickSpacing};");
}
class NiceScale {
double _niceMin, _niceMax;
double _tickSpacing;
double get tickSpacing { return _tickSpacing; }
double get niceMin{ return _niceMin; }
double get niceMax{ return _niceMax; }
double _minPoint, _maxPoint;
double _maxTicks;
double _range;
NiceScale(double minP, double maxP, double maxTicks){
this._minPoint = minP;
this._maxPoint = maxP;
this._maxTicks = maxTicks;
_calculate();
}
void _calculate(){
_range = _niceNum(_maxPoint - _minPoint, false);
_tickSpacing = _niceNum(_range / (_maxTicks - 1), true);
_niceMin = _calcMin();
_niceMax = _calcMax();
}
double _calcMin() {
int floored = (_minPoint / _tickSpacing).floor();
return floored * _tickSpacing;
}
double _calcMax() {
int ceiled = (_maxPoint / _tickSpacing).ceil();
return ceiled * _tickSpacing;
}
double _niceNum(double range, bool round){
double exponent; /** exponent of range */
double fraction; /** fractional part of range */
double niceFraction; /** nice, rounded fraction */
exponent = (log(range)/ln10).floor().toDouble();
fraction = range / pow(10, exponent);
if (round)
{
if (fraction < 1.5)
niceFraction = 1;
else if (fraction < 3)
niceFraction = 2;
else if (fraction < 7)
niceFraction = 5;
else
niceFraction = 10;
}
else
{
if (fraction <= 1)
niceFraction = 1;
else if (fraction <= 2)
niceFraction = 2;
else if (fraction <= 5)
niceFraction = 5;
else
niceFraction = 10;
}
return niceFraction * pow(10, exponent);
}
}
Here is a Ruby version
class NiceScale
attr_accessor :min_point, :max_point
attr_reader :tick_spacing, :nice_min, :nice_max
def initialize(options = {})
#min_point = options[:min_point]
#max_point = options[:max_point]
#max_ticks = [(options[:max_ticks] || 5), 2].max
self.calculate
end
def calculate
range = nice_num(#max_point - #min_point, false)
#tick_spacing = nice_num(range / (#max_ticks - 1))
#nice_min = (#min_point / tick_spacing).floor * tick_spacing
#nice_max = (#max_point / tick_spacing).floor * tick_spacing
end
private
def nice_num(num, round = true)
num = num.to_f
exponent = num > 0 ? Math.log10(num).floor : 0
fraction = num / (10 ** exponent)
if round
if fraction < 1.5
nice_fraction = 1
elsif fraction < 3
nice_fraction = 2
elsif fraction < 7
nice_fraction = 5
else
nice_fraction = 10
end
else
if fraction <= 1
nice_fraction = 1
elsif fraction <= 2
nice_fraction = 2
elsif fraction <= 5
nice_fraction = 5
else
nice_fraction = 10
end
end
nice_fraction.to_f * (10 ** exponent)
end
end
Another JS version:
const getTicks = {
minPoint: 0,
maxPoint: 10,
maxTicks: 10,
tickSpacing: 1,
range: 1,
niceMin: 1,
niceMax: 1,
niceScale(min, max) {
this.minPoint = min;
this.maxPoint = max;
this.calculate();
return {
tickSpacing: this.tickSpacing,
niceMinimum: this.niceMin,
niceMaximum: this.niceMax
};
},
calculate() {
this.range = this.niceNum(this.maxPoint - this.minPoint, false);
this.tickSpacing = this.niceNum(this.range / (this.maxTicks - 1), true);
this.niceMin = Math.floor(this.minPoint / this.tickSpacing) * this.tickSpacing;
this.niceMax = Math.ceil(this.maxPoint / this.tickSpacing) * this.tickSpacing;
},
niceNum(localRange, round) {
var exponent; /** exponent of localRange */
var fraction; /** fractional part of localRange */
var niceFraction; /** nice, rounded fraction */
exponent = Math.floor(Math.log10(localRange));
fraction = localRange / Math.pow(10, exponent);
if (round) {
if (fraction < 1.5) niceFraction = 1;
else if (fraction < 3) niceFraction = 2;
else if (fraction < 7) niceFraction = 5;
else niceFraction = 10;
} else {
if (fraction <= 1) niceFraction = 1;
else if (fraction <= 2) niceFraction = 2;
else if (fraction <= 5) niceFraction = 5;
else niceFraction = 10;
}
return niceFraction * Math.pow(10, exponent);
},
setMinMaxPoints(localMinPoint, localMaxPoint) {
this.minPoint = localMinPoint;
this.maxPoint = localMaxPoint;
this.calculate();
},
setMaxTicks(localMaxTicks) {
this.maxTicks = localMaxTicks;
this.calculate();
}
}
Just the code entered before, embedding it inside a JS object (I don't like the global vars). It can be used like this:
const tickScale = getTicks.niceScale(minValue, maxValue);
for (let i = tickScale.niceMinimum; i < tickScale.niceMaximum; i+=tickScale.tickSpacing) {
// Whatever you want to do
}
I find it quite useful this way, and easer to isolate from the rest of the code.
The solution produces nice results, but often too few tick marks depending on the min and max point values and the snapping effect.
Here's a quick hack fix to improve the tick mark spacing.
Original:
private void calculate() {
this.range = niceNum(maxPoint - minPoint, false);
this.tickSpacing = niceNum(range / (maxTicks - 1), true);
this.niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
this.niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}
New:
private void calculate() {
this.range = niceNum(maxPoint - minPoint, false);
this.tickSpacing = niceNum(range / (maxTicks - 1), true);
// Attempt to correct tick spacing due to snapping behavior and nice method.
double tickCountF = (maxPoint - minPoint) / tickSpacing;
if( (tickCountF / maxTicks) <= 0.5)
{ tickSpacing /= 2.0; }
this.niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
this.niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
}
Here are some Swift Unit Test to verify the definition of "Nice" meets your understanding.
import XCTest
#testable import AssetsFolio
final class NiceGridLineValues_Tests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func test_NiceGridLinesValues_originalExample() throws {
let numScale = NiceGridLineValues(min: -0.085, max: 0.173)
/*
// Which will then output nicely formatted numbers for use in whatever
// application for which you need to create pretty scales. =D
Tick Spacing: 0.05
Nice Minimum: -0.1
Nice Maximum: 0.2
*/
XCTAssertEqual(0.05, numScale.tickSpacing)
XCTAssertEqual(-0.1, numScale.niceMin)
XCTAssertEqual(0.2, numScale.niceMax)
}
func test_NiceGridLinesValues_173_183_example() throws {
let numScale = NiceGridLineValues(min: 173.82, max: 182.93)
XCTAssertEqual(1.0, numScale.tickSpacing)
XCTAssertEqual(173.0, numScale.niceMin)
XCTAssertEqual(183.0, numScale.niceMax)
}
func test_NiceGridLinesValues_176_180_example() throws {
let numScale = NiceGridLineValues(min: 176.82, max: 179.99)
XCTAssertEqual(0.5, numScale.tickSpacing)
XCTAssertEqual(176.5, numScale.niceMin)
XCTAssertEqual(180.0, numScale.niceMax)
}
func test_NiceGridLinesValues_179_180_example() throws {
let numScale = NiceGridLineValues(min: 179.62, max: 179.99)
XCTAssertEqual(0.05, numScale.tickSpacing)
XCTAssertEqual(179.6, numScale.niceMin, accuracy: 0.01)
XCTAssertEqual(180.0, numScale.niceMax)
}
}
Related
I have to write a Taylor series until the 16th element that calculates sin and compare the values returned values with Math.sin. Well , everything works fine until the last time when instead of 0.00000 i get 0.006941.Where is my error and if somebody have an idea how to write this in a more professional way I would be very happy.
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
NumberFormat formatter = new DecimalFormat("#0.000000");
double val[] = {0, Math.PI / 3, Math.PI / 4, Math.PI / 6, Math.PI / 2, Math.PI};
for (int i = 0; i < val.length; i++) {
System.out.println("With Taylor method: " + formatter.format(Taylor(val[i])));
System.out.println("With Math.sin method: " + formatter.format(Math.sin(val[i])));
}
}
public static double Taylor ( double val){
ArrayList<Double> memory = new ArrayList<Double>();
double row = val;
for (int i = 0, s = 3; i < 16; i++, s = s + 2) {
double mth = Math.pow(val, s);
double result = mth / factorial(s);
memory.add(result);
}
for (int i = 0; i < 16; i++) {
if (i % 2 == 0) {
double d = memory.get(i);
row = row - d;
} else {
double d = memory.get(i);
row = row + d;
}
}
return row;
}
public static long factorial ( double n){
long fact = 1;
for (int i = 2; i <= n; i++) {
fact = fact * i;
}
return fact;
}
}
Your math is correct, but your factorials are overflowing once you get to calculating 21!. I printed out the factorials calculated.
factorial(3) = 6
factorial(5) = 120
factorial(7) = 5040
factorial(9) = 362880
factorial(11) = 39916800
factorial(13) = 6227020800
factorial(15) = 1307674368000
factorial(17) = 355687428096000
factorial(19) = 121645100408832000
factorial(21) = -4249290049419214848 // Overflow starting here!
factorial(23) = 8128291617894825984
factorial(25) = 7034535277573963776
factorial(27) = -5483646897237262336
factorial(29) = -7055958792655077376
factorial(31) = 4999213071378415616
factorial(33) = 3400198294675128320
It appears that your raising val to ever higher powers isn't significant enough to make a difference with the overflow until you get to the highest value in your array, Math.PI itself. There the error due to overflow is significant.
Instead, calculate each term using the last term as a starting point. If you have the last value you entered into memory, then just multiply val * val into that value and then divide the next two numbers in sequence for the factorial part.
That's because memory.get(i) is equal to memory.get(i - 1) * (val * val) / ((s - 1) * s). This also makes your calculation more efficient. It avoids the multiplication repetition when calculating the numerator (power part) and the denominator (the factorial calculation). This will also avoid the overflow which results from how you calculated the denominator separately.
My implementation of this idea substitutes this for the first for loop:
double mth = val;
for (int i = 0, s = 3; i < 16; i++, s = s + 2) {
mth = mth * val * val;
mth = mth / ((s - 1) * s);
memory.add(mth);
}
and places
double row = val;
between the for loops, to ensure that the first term is the initial sum as you had it before. Then you don't even need the factorial method.
This this I get 0.000000 for Math.PI.
I am trying to validate IMEI numbers using the code below, the user will enter their number on a page and this then runs isValidIMEI using a string that is converted to a long.
Now this does work for almost all IMEI's that I have come across but for a few iPhone 5's they have a leading 0, so 0133xxxxxxxxxx0 for example. When this is casted to a long the leading 0 is lost so it becomes 14 digits and fails, if I turn off the length checker this still won't work as it will be doubling the wrong digits.
Any idea how I can convert to some kind of number format but keep that leading 0?
/**
* Sum digits
*
* #param n
* #return
*/
private static int sumDig(int n)
{
int a = 0;
while (n > 0)
{
a = a + n % 10;
n = n / 10;
}
return a;
}
/**
* Is valid imei
*
* #param n
* #return
*/
public boolean isValidIMEI(long n)
{
// Converting the number into String
// for finding length
String s = Long.toString(n);
int len = s.length();
Log.v("IMEIValidation", s);
if (len != 15) {
Log.v("IMEIValidation", "Length issue");
return false;
}
int sum = 0;
for (int i = len; i >= 1; i--)
{
int d = (int)(n % 10);
// Doubling every alternate digit
if (i % 2 == 0)
d = 2 * d;
// Finding sum of the digits
sum += sumDig(d);
n = n / 10;
}
Log.v("IMEIValidation", String.valueOf(sum));
Log.v("IMEIValidation", String.valueOf(sum % 10));
return (sum % 10 == 0);
}
I have to calculate PI with this special algorithm for school:
pi = 4*(1/1 - 1/3 + 1/5 - 1/7 ... 1/n)
I've been trying a lot of things but it seems like I only get a never-ending loop because the condition for it is false, or my code is too complicated. The result I have to get is the calculated PI (only 6 decimals)=(the Algorithm for it).
Here's the code I've already tried:
public void PI()
{
double n=1.0;//while 3 counter
double z=1.0;//while 3 denominator
int i=0;//numerator for while 3
double pi=1.0;//Result
double x=0.0;//Calculated fractions
double y=1.0;//denominator for double x
double q=1; //Help for while 2
int f=0;//Help for while 3
while(new Double(Math.round(1000000.0*pi)).compareTo(new Double(Math.round(1000000.0*Math.PI)))==-1||new Double(Math.round(1000000.0*pi)).compareTo(new Double(Math.round(1000000.0*Math.PI)))==1) //while 1
{
while(new Double(Math.round(1000000.0*(4*x))).compareTo(new Double(Math.round(1000000.0*Math.PI)))==-1||new Double(Math.round(1000000.0*pi)).compareTo(new Double(Math.round(1000000.0*Math.PI)))==1)//while 2
{
if (q==1)
{
x+=0.1/y;
q++;
}
y+=2;
if(q==2)
{
x-=0.1/y;
q--;
}
y+=2;
i++;
}
pi=x*4.0;
}
while(f<=i)//while 3
{
System.out.println(n+"/"+z);
z+=2;
f++;
}
}
Your code looks overcomplicated. Try this:
/**
* Returns PI estimation based on equation:
* {#code PI' = 4*(1/1 - 1/3 + 1/5 - 1/7 ... 1/m)}
*
* #param n number of fractions in sum.
*/
public static double piEstimate(int n) {
double sum = 0;
for (int i = 0; i < n; i++) {
double fraction = (double) 1/(i*2+1);
int sign = (i % 2 == 0) ? 1 : -1;
sum += sign * fraction;
}
return 4*sum;
}
public static void main(String[] args) {
System.out.println("PI = " + piEstimate(10000));
}
you should add a System.out.println to see how the pi variable evolves in your second loop.
In a program, a double is being converted to BigDecimal. This returns a very strange error message.
public static double riemannFuncForm(double s) {
double term = Math.pow(2, s)*Math.pow(Math.PI, s-1)*
(Math.sin((Math.PI*s)/2))*gamma(1-s);
if(s == 1.0 || (s <= -1 && s % 2 == 0) )
return 0;
else if (s >= 0 && s < 2)
return getSimpsonSum(s);
else if (s > -1 && s < 0)
return term*getSimpsonSum(1-s);
else
return term*standardZeta(1-s);
}
BigDecimal val = BigDecimal.valueOf(riemannFuncForm(s));
System.out.println("Value for the Zeta Function = "
+ val.toEngineeringString());
This returns
Exception in thread "main" java.lang.NumberFormatException
What is causing this error message? Does BigDecimal.valueOf(double) not work correctly since this is referenced through another method?
Full program
/**************************************************************************
**
** Euler-Riemann Zeta Function
**
**************************************************************************
** XXXXXXXXXX
** 06/20/2015
**
** This program computes the value for Zeta(s) using the standard form
** of Zeta(s), the Riemann functional equation, and the Cauchy-Schlomilch
** transformation. A recursive method named riemannFuncForm has been created
** to handle computations of Zeta(s) for s < 2. Simpson's method is
** used to approximate the definite integral calculated by the
** Cauchy-Schlomilch transformation.
**************************************************************************/
import java.util.Scanner;
import java.math.*;
public class ZetaMain {
// Main method
public static void main(String[] args) {
ZetaMain();
}
// Asks the user to input a value for s.
public static void ZetaMain() {
double s = 0;
double start, stop, totalTime;
Scanner scan = new Scanner(System.in);
System.out.print("Enter the value of s inside the Riemann Zeta " +
"Function: ");
try {
s = scan.nextDouble();
}
catch (Exception e) {
System.out.println("You must enter a positive integer greater " +
"than 1.");
}
start = System.currentTimeMillis();
if (s == 1)
System.out.println("The zeta function is undefined for Re(s) " +
"= 1.");
else if (s < 2) {
BigDecimal val = BigDecimal.valueOf(riemannFuncForm(s));
System.out.println("Value for the Zeta Function = "
+ val.toEngineeringString());
}
else
System.out.println("Value for the Zeta Function = "
+ BigDecimal.valueOf(getStandardSum(s)).toString());
stop = System.currentTimeMillis();
totalTime = (double) (stop-start) / 1000.0;
System.out.println("Total time taken is " + totalTime + " seconds.");
}
// Standard form of the Zeta function.
public static double standardZeta(double s) {
int n = 1;
double currentSum = 0;
double relativeError = 1;
double error = 0.000001;
double remainder;
while (relativeError > error) {
currentSum = Math.pow(n, -s) + currentSum;
remainder = 1 / ((s-1)* Math.pow(n, (s-1)));
relativeError = remainder / currentSum;
n++;
}
System.out.println("The number of terms summed was " + n + ".");
return currentSum;
}
// Returns the value calculated by the Standard form of the Zeta function.
public static double getStandardSum(double s){
return standardZeta(s);
}
// Approximation of the Gamma function through the Lanczos Approximation.
public static double gamma(double s){
double[] p = {0.99999999999980993, 676.5203681218851,
-1259.1392167224028, 771.32342877765313,
-176.61502916214059, 12.507343278686905,
-0.13857109526572012, 9.9843695780195716e-6,
1.5056327351493116e-7};
int g = 7;
// Implements Euler's Reflection Formula.
if(s < 0.5) return Math.PI / (Math.sin(Math.PI * s)
*gamma(1-s));
s -= 1;
double a = p[0];
double t = s + g + 0.5;
for(int i = 1; i < p.length; i++){
a += p[i] / (s+i);
}
return Math.sqrt(2*Math.PI)*Math.pow(t, s+0.5)
*Math.exp(-t)*a;
}
/* Riemann's Functional Equation - Directly calculates the value of
Zeta(s) for s < 2.
1. The first if statement handles the case when s < 0 and s is a
multiple of 2k. These are trivial zeroes where Zeta(s) is 0.
2. The second if statement handles the values of 0 < s < 2. Simpson's
method is used to numerically compute an approximation of the
definite integral.
3. The third if statement handles the values of -1 < s < 0. Recursion
is used alongside an approximation through Simpson's method.
4. The last if statement handles the case for s <= -1 and is not a
trivial zero. Recursion is used directly against the standard form
of Zeta(s).
*/
public static double riemannFuncForm(double s) {
double term = Math.pow(2, s)*Math.pow(Math.PI, s-1)*
(Math.sin((Math.PI*s)/2))*gamma(1-s);
if(s == 1.0 || (s <= -1 && s % 2 == 0) )
return 0;
else if (s >= 0 && s < 2)
return getSimpsonSum(s);
else if (s > -1 && s < 0)
return term*getSimpsonSum(1-s);
else
return term*standardZeta(1-s);
}
// Returns the function referenced inside the right hand side of the
// Cauchy-Schlomilch transformation for Zeta(s).
public static double function(double x, double s) {
double sech = 1 / Math.cosh(x); // Hyperbolic cosecant
double squared = Math.pow(sech, 2);
return ((Math.pow(x, s)) * squared);
}
// Simpson's rule - Approximates the definite integral of f from a to b.
public static double SimpsonsRule(double a, double b, double s, int n) {
double simpson, dx, x, sum4x, sum2x;
dx = (b-a) / n;
sum4x = 0.0;
sum2x = 0.0;
// 4/3 terms
for (int i = 1; i < n; i += 2) {
x = a + i * dx;
sum4x += function(x,s);
}
// 2/3 terms
for (int i = 2; i < n-1; i += 2) {
x = a + i * dx;
sum2x += function(x,s);
}
// Compute the integral approximation.
simpson = function(a,s) + function(a,b);
simpson = (dx / 3)*(simpson + 4 * sum4x + 2 * sum2x);
return simpson;
}
// Handles the error for for f(x) = t^s * sech(t)^2. The integration is
// done from 0 to 100.
// Stop Simspson's Method when the relative error is less than 1 * 10^-6
public static double SimpsonError(double a, double b, double s, int n)
{
double futureVal;
double absError = 1.0;
double finalValueOfN;
double numberOfIterations = 0.0;
double currentVal = SimpsonsRule(a,b,s,n);
while (absError / currentVal > 0.000001) {
n = 2*n;
futureVal = SimpsonsRule(a,b,s,n);
absError = Math.abs(futureVal - currentVal) / 15;
currentVal = futureVal;
}
// Find the number of iterations. N starts at 8 and doubles
// every iteration.
finalValueOfN = n / 8;
while (finalValueOfN % 2 == 0) {
finalValueOfN = finalValueOfN / 2;
numberOfIterations++;
}
System.out.println("The number of iterations is "
+ numberOfIterations + ".");
return currentVal;
}
// Returns an approximate sum of Zeta(s) through Simpson's rule.
public static double getSimpsonSum(double s) {
double constant = Math.pow(2, (2*s)-1) / (((Math.pow(2, s)) -2)*
(gamma(1+s)));
System.out.println("Did Simpson's Method.");
return constant*SimpsonError(0, 100, s, 8);
}
}
Would I have to change all of my double calculations to BigDecimal calculations in order to fix this?
Nope. All you would need to do is to catch and handle the NumberFormatException appropriately. Or, test for NaN and Inf before attempting to convert the double.
In this case, you are only using BigDecimal for formatting in "engineering" syntax. So another alternative would be to do the formatting directly. (Though I haven't found a simple way to do that yet.)
This error occurs with you because BigDecimal.valueOf(value) does not accept "NaN" "Not a Number" as parameter and the following expression will return NaN
Math.pow(2, s)*Math.pow(Math.PI, s-1)*(Math.sin((Math.PI*s)/2))*gamma(1-s)
this Math.pow(2, s)*Math.pow(Math.PI, s-1)*(Math.sin((Math.PI*s)/2)) will evaluate -0.0
and this function gamma(1-s) will evaluate "Infinity"
So -0.0 * Infinity equal NaN in java
please see this to know When can Java produce a NaN.
When can Java produce a NaN?
How can I calculate the logarithm of a BigDecimal? Does anyone know of any algorithms I can use?
My googling so far has come up with the (useless) idea of just converting to a double and using Math.log.
I will provide the precision of the answer required.
edit: any base will do. If it's easier in base x, I'll do that.
Java Number Cruncher: The Java Programmer's Guide to Numerical Computing provides a solution using Newton's Method. Source code from the book is available here. The following has been taken from chapter 12.5 Big Decimal Functions (p330 & p331):
/**
* Compute the natural logarithm of x to a given scale, x > 0.
*/
public static BigDecimal ln(BigDecimal x, int scale)
{
// Check that x > 0.
if (x.signum() <= 0) {
throw new IllegalArgumentException("x <= 0");
}
// The number of digits to the left of the decimal point.
int magnitude = x.toString().length() - x.scale() - 1;
if (magnitude < 3) {
return lnNewton(x, scale);
}
// Compute magnitude*ln(x^(1/magnitude)).
else {
// x^(1/magnitude)
BigDecimal root = intRoot(x, magnitude, scale);
// ln(x^(1/magnitude))
BigDecimal lnRoot = lnNewton(root, scale);
// magnitude*ln(x^(1/magnitude))
return BigDecimal.valueOf(magnitude).multiply(lnRoot)
.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
}
}
/**
* Compute the natural logarithm of x to a given scale, x > 0.
* Use Newton's algorithm.
*/
private static BigDecimal lnNewton(BigDecimal x, int scale)
{
int sp1 = scale + 1;
BigDecimal n = x;
BigDecimal term;
// Convergence tolerance = 5*(10^-(scale+1))
BigDecimal tolerance = BigDecimal.valueOf(5)
.movePointLeft(sp1);
// Loop until the approximations converge
// (two successive approximations are within the tolerance).
do {
// e^x
BigDecimal eToX = exp(x, sp1);
// (e^x - n)/e^x
term = eToX.subtract(n)
.divide(eToX, sp1, BigDecimal.ROUND_DOWN);
// x - (e^x - n)/e^x
x = x.subtract(term);
Thread.yield();
} while (term.compareTo(tolerance) > 0);
return x.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
}
/**
* Compute the integral root of x to a given scale, x >= 0.
* Use Newton's algorithm.
* #param x the value of x
* #param index the integral root value
* #param scale the desired scale of the result
* #return the result value
*/
public static BigDecimal intRoot(BigDecimal x, long index,
int scale)
{
// Check that x >= 0.
if (x.signum() < 0) {
throw new IllegalArgumentException("x < 0");
}
int sp1 = scale + 1;
BigDecimal n = x;
BigDecimal i = BigDecimal.valueOf(index);
BigDecimal im1 = BigDecimal.valueOf(index-1);
BigDecimal tolerance = BigDecimal.valueOf(5)
.movePointLeft(sp1);
BigDecimal xPrev;
// The initial approximation is x/index.
x = x.divide(i, scale, BigDecimal.ROUND_HALF_EVEN);
// Loop until the approximations converge
// (two successive approximations are equal after rounding).
do {
// x^(index-1)
BigDecimal xToIm1 = intPower(x, index-1, sp1);
// x^index
BigDecimal xToI =
x.multiply(xToIm1)
.setScale(sp1, BigDecimal.ROUND_HALF_EVEN);
// n + (index-1)*(x^index)
BigDecimal numerator =
n.add(im1.multiply(xToI))
.setScale(sp1, BigDecimal.ROUND_HALF_EVEN);
// (index*(x^(index-1))
BigDecimal denominator =
i.multiply(xToIm1)
.setScale(sp1, BigDecimal.ROUND_HALF_EVEN);
// x = (n + (index-1)*(x^index)) / (index*(x^(index-1)))
xPrev = x;
x = numerator
.divide(denominator, sp1, BigDecimal.ROUND_DOWN);
Thread.yield();
} while (x.subtract(xPrev).abs().compareTo(tolerance) > 0);
return x;
}
/**
* Compute e^x to a given scale.
* Break x into its whole and fraction parts and
* compute (e^(1 + fraction/whole))^whole using Taylor's formula.
* #param x the value of x
* #param scale the desired scale of the result
* #return the result value
*/
public static BigDecimal exp(BigDecimal x, int scale)
{
// e^0 = 1
if (x.signum() == 0) {
return BigDecimal.valueOf(1);
}
// If x is negative, return 1/(e^-x).
else if (x.signum() == -1) {
return BigDecimal.valueOf(1)
.divide(exp(x.negate(), scale), scale,
BigDecimal.ROUND_HALF_EVEN);
}
// Compute the whole part of x.
BigDecimal xWhole = x.setScale(0, BigDecimal.ROUND_DOWN);
// If there isn't a whole part, compute and return e^x.
if (xWhole.signum() == 0) return expTaylor(x, scale);
// Compute the fraction part of x.
BigDecimal xFraction = x.subtract(xWhole);
// z = 1 + fraction/whole
BigDecimal z = BigDecimal.valueOf(1)
.add(xFraction.divide(
xWhole, scale,
BigDecimal.ROUND_HALF_EVEN));
// t = e^z
BigDecimal t = expTaylor(z, scale);
BigDecimal maxLong = BigDecimal.valueOf(Long.MAX_VALUE);
BigDecimal result = BigDecimal.valueOf(1);
// Compute and return t^whole using intPower().
// If whole > Long.MAX_VALUE, then first compute products
// of e^Long.MAX_VALUE.
while (xWhole.compareTo(maxLong) >= 0) {
result = result.multiply(
intPower(t, Long.MAX_VALUE, scale))
.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
xWhole = xWhole.subtract(maxLong);
Thread.yield();
}
return result.multiply(intPower(t, xWhole.longValue(), scale))
.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
}
A hacky little algorithm that works great for large numbers uses the relation log(AB) = log(A) + log(B). Here's how to do it in base 10 (which you can trivially convert to any other logarithm base):
Count the number of decimal digits in the answer. That's the integral part of your logarithm, plus one. Example: floor(log10(123456)) + 1 is 6, since 123456 has 6 digits.
You can stop here if all you need is the integer part of the logarithm: just subtract 1 from the result of step 1.
To get the fractional part of the logarithm, divide the number by 10^(number of digits), then compute the log of that using math.log10() (or whatever; use a simple series approximation if nothing else is available), and add it to the integer part. Example: to get the fractional part of log10(123456), compute math.log10(0.123456) = -0.908..., and add it to the result of step 1: 6 + -0.908 = 5.092, which is log10(123456). Note that you're basically just tacking on a decimal point to the front of the large number; there is probably a nice way to optimize this in your use case, and for really big numbers you don't even need to bother with grabbing all of the digits -- log10(0.123) is a great approximation to log10(0.123456789).
This one is super fast, because:
No toString()
No BigInteger math (Newton's/Continued fraction)
Not even instantiating a new BigInteger
Only uses a fixed number of very fast operations
One call takes about 20 microseconds (about 50k calls per second)
But:
Only works for BigInteger
Workaround for BigDecimal (not tested for speed):
Shift the decimal point until the value is > 2^53
Use toBigInteger() (uses one div internally)
This algorithm makes use of the fact that the log can be calculated as the sum of the exponent and the log of the mantissa. eg:
12345 has 5 digits, so the base 10 log is between 4 and 5.
log(12345) = 4 + log(1.2345) = 4.09149... (base 10 log)
This function calculates base 2 log because finding the number of occupied bits is trivial.
public double log(BigInteger val)
{
// Get the minimum number of bits necessary to hold this value.
int n = val.bitLength();
// Calculate the double-precision fraction of this number; as if the
// binary point was left of the most significant '1' bit.
// (Get the most significant 53 bits and divide by 2^53)
long mask = 1L << 52; // mantissa is 53 bits (including hidden bit)
long mantissa = 0;
int j = 0;
for (int i = 1; i < 54; i++)
{
j = n - i;
if (j < 0) break;
if (val.testBit(j)) mantissa |= mask;
mask >>>= 1;
}
// Round up if next bit is 1.
if (j > 0 && val.testBit(j - 1)) mantissa++;
double f = mantissa / (double)(1L << 52);
// Add the logarithm to the number of bits, and subtract 1 because the
// number of bits is always higher than necessary for a number
// (ie. log2(val)<n for every val).
return (n - 1 + Math.log(f) * 1.44269504088896340735992468100189213742664595415298D);
// Magic number converts from base e to base 2 before adding. For other
// bases, correct the result, NOT this number!
}
You could decompose it using
log(a * 10^b) = log(a) + b * log(10)
Basically b+1 is going to be the number of digits in the number, and a will be a value between 0 and 1 which you could compute the logarithm of by using regular double arithmetic.
Or there are mathematical tricks you can use - for instance, logarithms of numbers close to 1 can be computed by a series expansion
ln(x + 1) = x - x^2/2 + x^3/3 - x^4/4 + ...
Depending on what kind of number you're trying to take the logarithm of, there may be something like this you can use.
EDIT: To get the logarithm in base 10, you can divide the natural logarithm by ln(10), or similarly for any other base.
This is what I've come up with:
//http://everything2.com/index.pl?node_id=946812
public BigDecimal log10(BigDecimal b, int dp)
{
final int NUM_OF_DIGITS = dp+2; // need to add one to get the right number of dp
// and then add one again to get the next number
// so I can round it correctly.
MathContext mc = new MathContext(NUM_OF_DIGITS, RoundingMode.HALF_EVEN);
//special conditions:
// log(-x) -> exception
// log(1) == 0 exactly;
// log of a number lessthan one = -log(1/x)
if(b.signum() <= 0)
throw new ArithmeticException("log of a negative number! (or zero)");
else if(b.compareTo(BigDecimal.ONE) == 0)
return BigDecimal.ZERO;
else if(b.compareTo(BigDecimal.ONE) < 0)
return (log10((BigDecimal.ONE).divide(b,mc),dp)).negate();
StringBuffer sb = new StringBuffer();
//number of digits on the left of the decimal point
int leftDigits = b.precision() - b.scale();
//so, the first digits of the log10 are:
sb.append(leftDigits - 1).append(".");
//this is the algorithm outlined in the webpage
int n = 0;
while(n < NUM_OF_DIGITS)
{
b = (b.movePointLeft(leftDigits - 1)).pow(10, mc);
leftDigits = b.precision() - b.scale();
sb.append(leftDigits - 1);
n++;
}
BigDecimal ans = new BigDecimal(sb.toString());
//Round the number to the correct number of decimal places.
ans = ans.round(new MathContext(ans.precision() - ans.scale() + dp, RoundingMode.HALF_EVEN));
return ans;
}
If all you need is to find the powers of 10 in the number you can use:
public int calculatePowersOf10(BigDecimal value)
{
return value.round(new MathContext(1)).scale() * -1;
}
A Java implementation of Meower68 pseudcode which I tested with a few numbers:
public static BigDecimal log(int base_int, BigDecimal x) {
BigDecimal result = BigDecimal.ZERO;
BigDecimal input = new BigDecimal(x.toString());
int decimalPlaces = 100;
int scale = input.precision() + decimalPlaces;
int maxite = 10000;
int ite = 0;
BigDecimal maxError_BigDecimal = new BigDecimal(BigInteger.ONE,decimalPlaces + 1);
System.out.println("maxError_BigDecimal " + maxError_BigDecimal);
System.out.println("scale " + scale);
RoundingMode a_RoundingMode = RoundingMode.UP;
BigDecimal two_BigDecimal = new BigDecimal("2");
BigDecimal base_BigDecimal = new BigDecimal(base_int);
while (input.compareTo(base_BigDecimal) == 1) {
result = result.add(BigDecimal.ONE);
input = input.divide(base_BigDecimal, scale, a_RoundingMode);
}
BigDecimal fraction = new BigDecimal("0.5");
input = input.multiply(input);
BigDecimal resultplusfraction = result.add(fraction);
while (((resultplusfraction).compareTo(result) == 1)
&& (input.compareTo(BigDecimal.ONE) == 1)) {
if (input.compareTo(base_BigDecimal) == 1) {
input = input.divide(base_BigDecimal, scale, a_RoundingMode);
result = result.add(fraction);
}
input = input.multiply(input);
fraction = fraction.divide(two_BigDecimal, scale, a_RoundingMode);
resultplusfraction = result.add(fraction);
if (fraction.abs().compareTo(maxError_BigDecimal) == -1){
break;
}
if (maxite == ite){
break;
}
ite ++;
}
MathContext a_MathContext = new MathContext(((decimalPlaces - 1) + (result.precision() - result.scale())),RoundingMode.HALF_UP);
BigDecimal roundedResult = result.round(a_MathContext);
BigDecimal strippedRoundedResult = roundedResult.stripTrailingZeros();
//return result;
//return result.round(a_MathContext);
return strippedRoundedResult;
}
I was searching for this exact thing and eventually went with a continued fraction approach. The continued fraction can be found at here or here
Code:
import java.math.BigDecimal;
import java.math.MathContext;
public static long ITER = 1000;
public static MathContext context = new MathContext( 100 );
public static BigDecimal ln(BigDecimal x) {
if (x.equals(BigDecimal.ONE)) {
return BigDecimal.ZERO;
}
x = x.subtract(BigDecimal.ONE);
BigDecimal ret = new BigDecimal(ITER + 1);
for (long i = ITER; i >= 0; i--) {
BigDecimal N = new BigDecimal(i / 2 + 1).pow(2);
N = N.multiply(x, context);
ret = N.divide(ret, context);
N = new BigDecimal(i + 1);
ret = ret.add(N, context);
}
ret = x.divide(ret, context);
return ret;
}
Pseudocode algorithm for doing a logarithm.
Assuming we want log_n of x
double fraction, input;
int base;
double result;
result = 0;
base = n;
input = x;
while (input > base){
result++;
input /= base;
}
fraction = 1/2;
input *= input;
while (((result + fraction) > result) && (input > 1)){
if (input > base){
input /= base;
result += fraction;
}
input *= input;
fraction /= 2.0;
}
The big while loop may seem a bit confusing.
On each pass, you can either square your input or you can take the square root of your base; either way, you must divide your fraction by 2. I find squaring the input, and leaving the base alone, to be more accurate.
If the input goes to 1, we're through. The log of 1, for any base, is 0, which means we don't need to add any more.
if (result + fraction) is not greater than result, then we've hit the limits of precision for our numbering system. We can stop.
Obviously, if you're working with a system which has arbitrarily many digits of precision, you will want to put something else in there to limit the loop.
Old question, but I actually think this answer is preferable. It has good precision and supports arguments of practically any size.
private static final double LOG10 = Math.log(10.0);
/**
* Computes the natural logarithm of a BigDecimal
*
* #param val Argument: a positive BigDecimal
* #return Natural logarithm, as in Math.log()
*/
public static double logBigDecimal(BigDecimal val) {
return logBigInteger(val.unscaledValue()) + val.scale() * Math.log(10.0);
}
private static final double LOG2 = Math.log(2.0);
/**
* Computes the natural logarithm of a BigInteger. Works for really big
* integers (practically unlimited)
*
* #param val Argument, positive integer
* #return Natural logarithm, as in <tt>Math.log()</tt>
*/
public static double logBigInteger(BigInteger val) {
int blex = val.bitLength() - 1022; // any value in 60..1023 is ok
if (blex > 0)
val = val.shiftRight(blex);
double res = Math.log(val.doubleValue());
return blex > 0 ? res + blex * LOG2 : res;
}
The core logic (logBigInteger method) is copied from this other answer of mine.
I created a function for BigInteger but it can be easily modified for BigDecimal. Decomposing the log and using some properties of the log is what I do but I get only double precision. But it works for any base. :)
public double BigIntLog(BigInteger bi, double base) {
// Convert the BigInteger to BigDecimal
BigDecimal bd = new BigDecimal(bi);
// Calculate the exponent 10^exp
BigDecimal diviser = new BigDecimal(10);
diviser = diviser.pow(bi.toString().length()-1);
// Convert the BigDecimal from Integer to a decimal value
bd = bd.divide(diviser);
// Convert the BigDecimal to double
double bd_dbl = bd.doubleValue();
// return the log value
return (Math.log10(bd_dbl)+bi.toString().length()-1)/Math.log10(base);
}