I'm trying to format a percentage to have three significant figures. I'd like a fairly small percentage, something like 1075 / 107175175, to show up as 0.0001%.
var x = 1075 / 107175175;
console.log(x.toLocaleString('en-us', {
style: 'percent', minimumSignificantDigits: 1
}));
What I'm getting instead is '0.00100303078581397%.' Is this an issue with how floating point numbers are implemented in Javascript or a bug?
Have you tried using maximumSignificantDigits: 1?
console.log(x.toLocaleString('en-us', {
style: 'percent', maximumSignificantDigits: 1
}));
It's a small logical mistake, minimumSignificantDigits means show at least N numbers, while in your case i think you want to use maximumSignificantDigits which means show maximun 1 significant digit.
Related
I'm running into an interesting problem with LightningChart where it seems to be corrupting or otherwise decimating my data depending on how far it is from the DateOrigin of the chart. My data is 1000 samples per second and I am trying to display 1-2 weeks of data at a time. I am using the ChartXY class, the x-axis type is set to "linear-highPrecision" which should have 1 ms accuracy, which is all I need and I don't need any more, I am creating a LineSeries and it's data pattern is 'ProgressiveX' and regularProgressiveStep: true.
Here's what it looks like when the data is plotted near the DateOrigin.
Here's what it looks like zoomed in on the data near the DateOrigin.
That looks fantastic! And lightning chart is doing exactly what I want!
However, I would like this data to be offset correctly to show it's true absolute time.
Here's what it looks like when I offset this data by 14 days. My code to set the relative offset looks like this.
ds.addArrayY(curve.data,step=1,start=14*24*60*60*1000)
Ok, it looks alright zoomed out, but what if we zoom in?
It's gone haywire! It looks like the X axis values are being coerced to some larger step of the X axis. It gets worse the further that you go out from the DateOrigin. My fear is that this is some built-in behavior of the engine and I am expecting too much, however, it says it has 1ms resolution, so I expect that to be respected.
Here's how I create the chart.
// Create a Line Chart.
const PumpsChart = lightningChart().ChartXY({
// Set the chart into a div with id, 'target'.
// Chart's size will automatically adjust to div's size.
theme: Themes.lightGradient,
container: 'PumpsChart',
defaultAxisX: {
type: 'linear-highPrecision'
}
}).setTitle('') // Set chart title
.setTitleFont(new FontSettings({
family: waveChartFontFamily,
size: 20
}))
.setMouseInteractionWheelZoom(false)
axisPumpsChartDateTime = PumpsChart.getDefaultAxisX()
.setTickStrategy(
AxisTickStrategies.DateTime,
(tickStrategy) => tickStrategy.setDateOrigin(waveDateOrigin))
axisPumpsChartPressurePSI = PumpsChart.getDefaultAxisY()
.setTitle("Pressure (PSI)")
.setInterval(0,10000,0,true)
Here's how I create the LineSeries
newDataSeries = targetChart.chart.addLineSeries(
{
yAxis: targetChart.axis,
dataPattern: {
pattern: 'ProgressiveX',
regularProgressiveStep: true,
}
}
);
Here's how I add data to the chart:
ds.addArrayY(curve.data,step=1,start=14*24*60*60*1000)
I would prefer not to use the AxisTickStrategies.DateTime over AxisTickStrategies.DateTime for a few reasons, my data spans weeks, 100 hours is too little, I am just fine with millisecond resolution, I don't need more than that, and I need to present my data in relative and not absolute time.
Hopefully there's some parameter that I missing that I can adjust to achieve this.
EDIT
Well, this corruption is also happening with Time tick strategy s well when the data is offset relative to the origin -636 hours. I tried this with and without ProgressiveX set as DataPattern.pattern.
**** EDIT 2 ****
Well, I even tried downSampling to 20 samples per second, and changed this back to AxisTickStrategies.DateTime, it's "squishing" all the points to this magic .25 second interval for some reason.
I tried to produce a reference application in a similar situation - using DateTime ticks with high resolution time data (1000 Hz). The below snippet should open right here in stack overflow, generating a quite large set of test data (might take a while), and using date origin to show it with high zoom range.
Does this work for you? If yes, then maybe there is something off in your application which you could spot with the help of this reference application.
const { lightningChart, AxisTickStrategies } = lcjs
const { createProgressiveTraceGenerator } = xydata
createProgressiveTraceGenerator()
.setNumberOfPoints(1000 * 60 * 60 * 4)
.generate()
.toPromise()
.then((data) => {
// Just for generating data set
const dataStart = Date.now()
return data.map((p) => ({ x: dataStart - 1000 * 60 * 60 * 4 + p.x, y: p.y }))
})
.then((data) => {
// 1. Offset data X by date origin
const dateOrigin = new Date()
const tDateOrigin = dateOrigin.getTime()
data = data.map((p) => ({ x: p.x - tDateOrigin, y: p.y }))
const chart = lightningChart().ChartXY({ disableAnimations: true })
const axisX = chart.getDefaultAxisX().setTickStrategy(AxisTickStrategies.DateTime, (ticks) =>
ticks
// 2. Inform Tick Strategy of date origin
.setDateOrigin(dateOrigin),
)
const series = chart.addLineSeries({
dataPattern: {
pattern: 'ProgressiveX',
regularProgressiveStep: true,
},
})
series.add(data)
chart.addLegendBox().add(chart)
})
<script src="https://unpkg.com/#arction/lcjs#3.4.0/dist/lcjs.iife.js"></script>
<script src="https://unpkg.com/#arction/xydata#1.2.1/dist/xydata.iife.js"></script>
Looking back at your question, perhaps the original issue was on the tick formatting? When you zoom in this case, it ultimately shows just seconds on each tick rather than a full time. Was this the problem? If yes, then the issue is about formatting axis ticks and I'll have to see what can be done about that.
Could it be 32 bit floating point precision loss? Two weeks in seconds amounts to 1 209 600, and log2(1_209_600) = 20.2 = 21 bits of storage space. The mantissa of 32-bit binary floats is just 23 bits, leaving you with 2 bits for the fractionnal part, which would explain the 0.25 increments.
So if i'm correct, you would need to have the X position precision bumped to 64-bit floats. You're already using "linear-highPrecision" axis mode, though, which would have been my first guess for a solution, so as far as I can tell it doesn't actually increase the data precision to 64 bits, only the axis. Unless there's another solution I've missed, you would probably need to split your data into separate series.
EDIT: I'm not actually sure that's a problem on LightningChart's end, now that I've looked it up. OpenGL ES 3.0 (upon which WebGL 2 relies, and in turn LightningChart) requires that "high-precision" float parameters be stored in binary32 IEEE 754 standard floats.
Official spec, p. 53:
highp floating point values are stored in IEEE 754 single precision floating point format. Mediump and lowp floating point values have minimum range and precision requirements as detailed below and have maximum range and precision as defined by IEEE 754.
So given that information, it seems to be a bug in LCjs caused a WebGL technical limitation.
I'm using this BigInteger.js for some calculations:
let myBigInt = bigInt(20).pow(200) // gets 160693804425899027554196209234116260252220299378279283530137600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
I'd like to apply the logarithm to the big integer but in the docs I could not find any matching function. How can I implement a log(baseN, valueX) function for the BigInteger.js library?
Note: let myLogarithm = myBigInt.log(baseN) is not a valid implementation.
Note: After a lot of try&error I did found a working solution my own and I will post it here because I'm pretty sure there are a few more people then me that also gots faced with the same issue right there. So I hope, I could help :)
Have a look at wikipedia, as I did because theres a very nice article about baseConversion.
Below you can find a function for Math.log(base, value) that is able to calculate the log(base) from a value.
Math.log = (function() {
var log = Math.log;
return function(base, n) {
return log(n)/(base ? log(base) : 1);
};
})();
To calculate the logarithmToBaseN for bigInt-values just use this line of code:
let logarithmToBaseN = (myBigInt.toString().length * Math.log(baseN, 10) + Math.log(baseN, parseFloat("0." + myBigInt))) - 1);
Edit: This soltuion is a tiny workaround bacause parseFloat("0." + myBigInt) converts a big value like 100000 to a really small one like 0.100000,... what causes that it will be in integer precision.
According to #Jonas W's comment: The solution is very accurate for lower bases like (5, 10, ...) combined with low values like 10, 1000, 100000 - but for really big values like bigInt(20).pow(200) is it not.
Note: Using parseFloat (IEEE 754 double precision floating-point) means, you have a maximum of 52 bits of precision, which is a bit more than 15 decimal places. After that - the accuracy will be killed.
Note: For really big values bigInt(20).pow(200) combined with really big Bases like 100*(and more) it seems to be pretty accurate again.
Greetings, jonas.
foo.setAttribute("item-position", ""+bar+"");
The bar variable is a number, for example 1 or 15. What's the way to increase it on 1, so it would be 2 and 16 ?
May be something like this? But it doesn't work.
foo.setAttribute("item-position", ""+bar+""+1);
// The result should be 2 and 16, but here the result is 21 and 161.
// That is not what I want.
You're currently appending the number to the end of the string, this has nothing to do with arithmetic.
Just add the calculated result
foo.setAttribute("item-position", bar+1);
You don't have to turn it into a string, setAttribute will do that part.
Or if you want to increase the value in bar and show it, use the preincrement operator:
foo.setAttribute("item-position", ++bar);
I have a JavaScript calculator which uses the Math.cbrt() function. When I calculate the cube root of 125 it returns 4.999999999999999. I understand that I could use Math.round() to round any answers that this function returns to integer values, but I do not want to do that exactly. Is there a way to use this if and only if the result of calculation is some number followed by a string of 9s (or something similar like 4.99999998 perhaps) after the decimal?
What you are dealing with is the frustration of floating point numbers in computing. See the Floating Point Guide for more information on this critical topic.
The short version:
Certain non-integer values cannot be represented accurately by computers, so they store a value that is "near enough". Just try evaluating 3.3 / 3 in your favourite REPL.
Say what?!
Computers are supposed to be perfect at this numbers/math thing, right? Can I trust any answer they give me?
Yes, for integers, they are pretty much perfect. But for non-integer calculations, you should assume that answers won't be exact, and account for these floating point errors.
The solution in Javascript
In newer versions of Javascript, you have a defined constant Number.EPSILON, which is the smallest difference between the actual number and the approximation that it can actually store.
Using this, you can multiply that constant by the result you get and add it to the result and you should get the exact value you require.
function cbrt(n) {
return Math.cbrt(n) + (Number.EPSILON * Math.cbrt(n));
}
Alternatively, you can use the rounding behaviour of the .toFixed() method on numbers together with the parseFloat() function if you only care about numbers up to a certain number of decimal places (less than 20).
function num(n, prec) {
if (prec === void 0) prec = 8; // default to 8 decimal places
return parseFloat(n.toFixed(prec));
}
var threshold = 0.999; // set to whatever you want
var fraction = result % 1;
if (fraction >= threshold) {
result = Math.round(result);
}
I am trying to do a select box with 4 options
00,15,30,45
I want to take the current time and round it to 15 min increments, and have the value change.
I have
current_min = start_date.getMinutes();
$('#event-hour').val(current_min);
I played with this roundedMinutes=(15*Math.floor(enteredMinutes/15)) but i couldn't get it to work right.
Use Math.round instead of Math.floor and everything should be ok-- other than that, your equation for rounding to the nearest n is correct.
currentTimeRounded = (15*Math.round(date.getMinutes()/15));
js> (15*Math.round(date.getMinutes()/15));
15
Works just fine for me.