In my iOS app, I have a shape class, built with CGPoints. I save it to a file using encodeCGPoint:forKey. I read it back in. That all works.
However, the CGPoint values I read in are not exactly equal to the values I saved it. The low bits of the CGFloat values aren't stable. So CGPointEqualToPoint returns NO, which means my isEqual method returns NO. This causes me trouble and pain.
Obviously, serializing floats precisely has been a hassle since the beginning of time. But in this situation, what is the best approach? I can think of several:
write out the x and y values using encodeFloat instead of encodeCGPoint (would that help at all?)
multiply my x and y values by 256.0 before saving them (they're all going to be between -1 and 1, roughly, so this might help?)
write out the x and y values using encodeDouble instead of encodeCGPoint (still might round the lowest bit incorrectly?)
cast to NSUInteger and write them out using encodeInt32 (icky, but it would work, right?)
accept the loss of precision, and implement my isEqual method to use within-epsilon comparison rather than CGPointEqualToPoint (sigh)
EDIT-ADD: So the second half of the problem, which I was leaving out for simplicity, is that I have to implement the hash method for these shape objects.
Hashing floats is also a horrible pain (see " Good way to hash a float vector? "), and it turns out it more or less nullifies my question. The toolkit's encodeCGPoint method rounds its float values in an annoying way -- it's literally printing them to a string with the %g format -- so there's no way I can use it and still make hashing reliable.
Therefore, I'm forced to write my own encodePoint function. As long as I'm doing that, I might as well write one that encodes the value exactly. (Copy two 32-bit floats into a 64-bit integer field, and no, it's not portable, but this is iOS-only and I'm making that tradeoff.)
With reliable exact storage of CGPoints, I can go back to exact comparison and any old hash function I want. Tolerance ranges do nothing for me, so I'm just not using them for this application.
If I wanted hashing and tolerance comparisons, I'd be comparing values within a tolerance of N significant figures, not a fixed distance epsilon. (That is, I'd want 0.123456 to compare close to 0.123457, but I'd also want 1234.56 to compare close to 1234.57.) That would be stable against floating-point math errors, for both large and small values. I don't have sample code for that, but start with the frexpf() function and it shouldn't be too hard.
Directly comparing floating point numbers is usually not the right game plan. Try one of the many other options. The best solution for your problem is probably your last suggestion; I don't know why there's a "sigh" there, though. A double precision floating point number has about 16 decimal digits worth of precision - there's a very good chance that your program doesn't actually need that much precision.
Use the epsilon method, because the "low bits of the CGFloat values aren't stable" problem surfaces any time there's an implicit conversion between float and double (often in framework code. tgmath.h is useful for avoiding this in your own code.)
I use the following functions (the tolerance defaulting to 0.5 because that's useful in the common case for CGGeometry):
BOOL OTValueNearToValueWithTolerance(CGFloat v1, CGFloat v2, CGFloat tolerance)
{
return (fabs(v1 - v2) <= tolerance);
}
BOOL OTPointNearToPointWithTolerance(CGPoint p1, CGPoint p2, CGFloat tolerance)
{
return (OTValueNearToValueWithTolerance(p1.x, p2.x, tolerance) && OTValueNearToValueWithTolerance(p1.y, p2.y, tolerance));
}
BOOL OTSizeNearToSizeWithTolerance(CGSize s1, CGSize s2, CGFloat tolerance)
{
return (OTValueNearToValueWithTolerance(s1.width, s2.width, tolerance) && OTValueNearToValueWithTolerance(s1.height, s2.height, tolerance));
}
BOOL OTRectNearToRectWithTolerance(CGRect r1, CGRect r2, CGFloat tolerance)
{
return (OTPointNearToPointWithTolerance(r1.origin, r2.origin, tolerance) && OTSizeNearToSizeWithTolerance(r1.size, r2.size, tolerance));
}
BOOL OTValueNearToValue(CGFloat v1, CGFloat v2)
{
return OTValueNearToValueWithTolerance(v1, v2, 0.5);
}
BOOL OTPointNearToPoint(CGPoint p1, CGPoint p2)
{
return OTPointNearToPointWithTolerance(p1, p2, 0.5);
}
BOOL OTSizeNearToSize(CGSize s1, CGSize s2)
{
return OTSizeNearToSizeWithTolerance(s1, s2, 0.5);
}
BOOL OTRectNearToRect(CGRect r1, CGRect r2)
{
return OTRectNearToRectWithTolerance(r1, r2, 0.5);
}
BOOL OTPointNearToEdgeOfRect(CGPoint point, CGRect rect, CGFloat amount, CGRectEdge edge)
{
CGRect nearRect, otherRect;
CGRectDivide(rect, &nearRect, &otherRect, amount, edge);
return CGRectContainsPoint(nearRect, point);
}
Related
I want to get one decimal place of a double in Dart. I use the toStringAsFixed() method to get it, but it returns a round-up value.
double d1 = 1.151;
double d2 = 1.150;
print('$d1 is ${d1.toStringAsFixed(1)}');
print('$d2 is ${d2.toStringAsFixed(1)}');
Console output:
1.151 is 1.2
1.15 is 1.1
How can I get it without a round-up value? Like 1.1 for 1.151 too. Thanks in advance.
Not rounding seems highly questionable to me1, but if you really want to truncate the string representation without rounding, then I'd take the string representation, find the decimal point, and create the appropriate substring.
There are a few potential pitfalls:
The value might be so large that its normal string representation is in exponential form. Note that double.toStringAsFixed just returns the exponential form anyway for such large numbers, so maybe do the same thing.
The value might be so small that its normal string representation is in exponential form. double.toStringAsFixed already handles this, so instead of using double.toString, use double.toStringAsFixed with the maximum number of fractional digits.
The value might not have a decimal point at all (e.g. NaN, +infinity, -infinity). Just return those values as they are.
extension on double {
// Like [toStringAsFixed] but truncates (toward zero) to the specified
// number of fractional digits instead of rounding.
String toStringAsTruncated(int fractionDigits) {
// Require same limits as [toStringAsFixed].
assert(fractionDigits >= 0);
assert(fractionDigits <= 20);
if (fractionDigits == 0) {
return truncateToDouble().toString();
}
// [toString] will represent very small numbers in exponential form.
// Instead use [toStringAsFixed] with the maximum number of fractional
// digits.
var s = toStringAsFixed(20);
// [toStringAsFixed] will still represent very large numbers in
// exponential form.
if (s.contains('e')) {
// Ignore values in exponential form.
return s;
}
// Ignore unrecognized values (e.g. NaN, +infinity, -infinity).
var i = s.indexOf('.');
if (i == -1) {
return s;
}
return s.substring(0, i + fractionDigits + 1);
}
}
void main() {
var values = [
1.151,
1.15,
1.1999,
-1.1999,
1.0,
1e21,
1e-20,
double.nan,
double.infinity,
double.negativeInfinity,
];
for (var v in values) {
print(v.toStringAsTruncated(1));
}
}
Another approach one might consider is to multiply by pow(10, fractionalDigits), use double.truncateToDouble, divide by the power-of-10 used earlier, and then use .toStringAsFixed(fractionalDigits). That could work for human-scaled values, but it could generate unexpected results for very large values due to precision loss from floating-point arithmetic. (This approach would work if you used package:decimal instead of double, though.)
1 Not rounding seems especially bad given that using doubles to represent fractional base-10 numbers is inherently imprecise. For example, since the closest IEEE-754 double-precision floating number to 0.7 is 0.6999999999999999555910790149937383830547332763671875, do you really want 0.7.toStringAsTruncated(1) to return '0.6' instead of '0.7'?
In my program, there are some decimal values that should be defined float respect to their range.
But, in several calculations (multiply), ranges might be larger than 10^38, so I need to convert them to Double before the calculation.
Say the values are
let a: Float // maximum: 10
let b: Float // maximum: 10^20
let c: Float // maximum: 10^30
and the calculations are like
func multiplyAB() -> Float {
return a * b
}
func multiplyBC() -> Double {
return Double(b) * Double(c)
}
let d = multiplyBC()
What bothers me is which one is better performance-wise?
Convert from Float to Double during calculation or define a, b, c as Double?
In other words, is converting from Float to Double a handy job to CPU (like realloc memory, handle precision and sort of things) comparing to calculate all numbers in Double?
BTW, why Apple use Double as the underlying value for CGFloat?
Maximum value for Float is 10^38, which is pretty large respect to iPhone screen sizes and pixels can't be float (10.111 and 10.11 make no difference, right?).
What's the reason behind that?
from
THE SWIFT PROGRAMMING LANGUAGE
"NOTE
Double has a precision of at least 15 decimal digits, whereas the precision of Float can be as little as 6 decimal digits. The appropriate floating-point type to use depends on the nature and range of values you need to work with in your code. In situations where either type would be appropriate, Double is preferred."
According to this question, using == and != should let you check for equality between two CGPoint objects.
However, the code below fails to consider two CGPoint objects as equal even though they output the same value.
What is the right way to check equality among CGPoint objects?
Code:
let boardTilePos = boardLayer.convert(boardTile.position, from: boardTile.parent!)
let shapeTilePos = boardLayer.convert(tile.position, from: tile.parent!)
print("board tile pos: \(boardTilePos). active tile pos: \(shapeTilePos). true/false: \(shapeTilePos == boardTilePos)")
Output:
board tile pos: (175.0, 70.0). active tile pos: (175.0, 70.0). true/false: false
Unfortunately, what you see in the console is not what your real value is.
import UIKit
var x = CGPoint(x:175.0,y:70.0)
var y = CGPoint(x:175.0,y:70.00000000000001)
print("\(x.equalTo(y)), \(x == y),\(x),\(y)")
The problem is, the console only allows for 10-16 but in reality your CGFloat can go even lower than that because on 64bit architecture, CGFloat is Double.
This means you have to cast your CGPoint values to a Float if you want to get equality that will appear on the console, so you need to do something like:
if Float(boxA.x) == Float(boxB.x) && Float(boxA.y) == Float(boxB.y)
{
//We have equality
}
Now I like to take it one step further.
In most cases, we are using CGPoint to determine points on the scene. Rarely do we ever want to be dealing with 1/2 points, they make our lives just confusing.
So instead of Float, I like to cast to Int. This will guarantee if two points are lying on the same CGPoint in scene space
if Int(boxA.x) == Int(boxB.x) && Int(boxA.y) == Int(boxB.y)
{
//We have equality
}
I'm providing an alternate answer since I don't agree with Knight0fDragon's implementation. This is only if you want to deal with factions of a point. If you only care about points in whole numbers, see Knight0fDragon's answer.
You don't always have the luxury of logging points to the console, or seeing if you're trying to compare points that are the victim of floating point math, like comparing (175.0, 70.0) to (175.0, 70.00001) (which both log as (175.0, 70.0) in the console). Yes, truncating to Int is a great way of understanding why two points that appear to print to the console as equal aren't. But it's not a catch all solution one should use for comparing every point. Depending on what level of precision you need, you want to take the absolute value of the difference of both x and y for each point, and see if it is in an acceptable range of a delta you specify.
var boxA = CGPoint(x:175.0, y:70.0)
var boxB = CGPoint(x:175.0, y:70.00000000000001)
let delta: CGFloat = 0.01
if (fabs(boxA.x - boxB.x) < delta) &&
(fabs(boxA.y - boxB.y) < delta) {
// equal enough for our needs
}
The answer to the question "What is the right way to check equality among CGPoint objects?" really depends on the way you compare floating point numbers.
CGPoint provides its own comparison method: equalTo(_ point2: CGPoint)
Try this:
shapeTilePos.equalTo(boardTilePos)
I am getting lost in how nodespace coordinates and rotation are handled in scenekit.
How do I get the direction a node is facing after rotation so I can then apply force in that direction.
I assumed that a force along the -z axis applied to the node would always move it forward relative to the node however this is not the case.
I also couldn't get convertPosition:toNode to work. Variations on ship.convertPosition(SCNVector3(0,0,-0.1), toNode: ship.parentNode) made the node fly off at unpredictable directions and speeds.
What worked for me, was to grab the third row of the node's worldTransform matrix, which corresponds to it's z-forward axis:
func getZForward(node: SCNNode) -> SCNVector3 {
return SCNVector3(node.worldTransform.m31, node.worldTransform.m32, node.worldTransform.m33)
}
ship.position += getZForward(ship) * speed
// if node has a physics body, you might need to use the presentationNode, eg:
// getZForward(ship.presentationNode)
// though you probably don't want to be directly modifying the position of a node with a physics body
// overrides for SCNVector arithmetic
#if os(iOS)
typealias VecFloat = Float
#elseif os (OSX)
typealias VecFloat = CGFloat
#endif
func + (lhs: SCNVector3, rhs: SCNVector3) -> SCNVector3 {
return SCNVector3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z)
}
func * (lhs: SCNVector3, rhs: VecFloat) -> SCNVector3 {
return SCNVector3(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs)
}
func += (lhs: inout SCNVector3, rhs: SCNVector3) {
lhs = lhs + rhs
}
If the node has a physics body you might have to pass the node.presentationNode into the function.
When iOS 11/ High Sierra come out, there'll be less need for overriding SCNVector3, because all the SCNNode properties have simd equivalents, so as well as .position there's .simdPosition and so on, and there are a lot of common simd operations built in.
iOS 11 update
iOS 11 adds handy convenience functions for getting the orientation of a node. In this case the worldForward property is the one you want. Also, all of the properties on SCNNode that return SCNVector and matrix types now have versions that return simd types. Because simd already has overloads for the arithmetic operators, you no longer need to add sets of arithmetic overrides for the SCNVector and SCNMatrix types.
So we can get rid of out getZForward method above, and just have the line:
ship.simdPosition += ship.simdWorldFront * speed
The other handy set of methods that iOS 11 adds, are a set of convertVector methods, to complement the existing convertPosition methods. convertVector is the equivalent of multiplying the matrix by the vector with 0 in the w position, so that the translation of the matrix is ignored. These are the appropriate methods to use for converting things like normals, directions and so on from one node's space to another.
Because the accepted answer uses convertPosition, I believe it will only produce correct results for nodes whose translation is the 0,0,0 origin
Negative z is the "facing" direction of a node only in its own coordinate space (i.e. the one its children and geometry are positioned in). When you apply a force, you're working in the coordinate space containing the node.
So, if you want to apply a force in a node's forward direction, you'll need to convert a vector like {0,0,-1} from the node's space to its parent's using a method like convertPosition:toNode:.
I came across a bug with the 64bit processors that I wanted to share.
CGFloat test1 = 0.58;
CGFloat test2 = 0.40;
CGFloat value;
value = fmaxf( test1, test2 );
The result would be:
value = 0.5799999833106995
This obviously is a rounding issue, but if you needed to check to see which value was picked you would get an erroneous result.
if( test1 == value ){
// do something
}
however if you use either MIN( A, B ) or MAX( A, B ) it would work as expected.
I thought this is was worth sharing
Thanks
This has nothing to do with a bug in fminf or fmaxf. There is a bug in your code.
On 64-bit systems, CGFloat is typedef'd to double, but you're using the fmaxf function, which operates on float (not double), which causes its arguments to be rounded to single precision, thus changing the value. Don't do that.
On 32-bit systems, this doesn't happen because CGFloat is typedef'd to float, matching the argument and return type of fmaxf; no rounding occurs.
Instead, either include <tgmath.h> and use fmax without the f suffix, or use float instead of CGFloat.