mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-11-28 10:22:38 +03:00
Fix for ios build
This commit is contained in:
parent
b23984fda9
commit
bc15834aaf
8
.gitignore
vendored
8
.gitignore
vendored
@ -55,6 +55,9 @@ buck-out/
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
|
||||
# CocoaPods
|
||||
/ios/Pods/
|
||||
|
||||
config.js
|
||||
!src/screens/steem-connect/config.js
|
||||
google-services.json
|
||||
@ -62,7 +65,4 @@ keystore/
|
||||
.env
|
||||
package-lock.json
|
||||
my-release-key.keystore
|
||||
gradle.properties
|
||||
|
||||
# CocoaPods
|
||||
/ios/Pods/
|
||||
gradle.properties
|
Binary file not shown.
@ -1,21 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSAbstractLog.h"
|
||||
#import "MSAppCenter.h"
|
||||
#import "MSAppCenterErrors.h"
|
||||
#import "MSChannelGroupProtocol.h"
|
||||
#import "MSChannelProtocol.h"
|
||||
#import "MSConstants.h"
|
||||
#import "MSCustomProperties.h"
|
||||
#import "MSDevice.h"
|
||||
#import "MSEnable.h"
|
||||
#import "MSLog.h"
|
||||
#import "MSLogWithProperties.h"
|
||||
#import "MSLogger.h"
|
||||
#import "MSService.h"
|
||||
#import "MSServiceAbstract.h"
|
||||
#import "MSWrapperLogger.h"
|
||||
#import "MSWrapperSdk.h"
|
@ -1,8 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MSAbstractLog : NSObject
|
||||
|
||||
@end
|
@ -1,223 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSConstants.h"
|
||||
|
||||
@class MSWrapperSdk;
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
@class MSCustomProperties;
|
||||
#endif
|
||||
|
||||
@interface MSAppCenter : NSObject
|
||||
|
||||
/**
|
||||
* Returns the singleton instance of MSAppCenter.
|
||||
*/
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
* Configure the SDK with an application secret.
|
||||
*
|
||||
* @param appSecret A unique and secret key used to identify the application.
|
||||
*
|
||||
* @discussion This may be called only once per application process lifetime.
|
||||
*/
|
||||
+ (void)configureWithAppSecret:(NSString *)appSecret;
|
||||
|
||||
/**
|
||||
* Configure the SDK.
|
||||
*
|
||||
* @discussion This may be called only once per application process lifetime.
|
||||
*/
|
||||
+ (void)configure;
|
||||
|
||||
/**
|
||||
* Configure the SDK with an application secret and an array of services to start.
|
||||
*
|
||||
* @param appSecret A unique and secret key used to identify the application.
|
||||
* @param services Array of services to start.
|
||||
*
|
||||
* @discussion This may be called only once per application process lifetime.
|
||||
*/
|
||||
+ (void)start:(NSString *)appSecret withServices:(NSArray<Class> *)services;
|
||||
|
||||
/**
|
||||
* Start the SDK with an array of services.
|
||||
*
|
||||
* @param services Array of services to start.
|
||||
*
|
||||
* @discussion This may be called only once per application process lifetime.
|
||||
*/
|
||||
+ (void)startWithServices:(NSArray<Class> *)services;
|
||||
|
||||
/**
|
||||
* Start a service.
|
||||
*
|
||||
* @param service A service to start.
|
||||
*
|
||||
* @discussion This may be called only once per service per application process lifetime.
|
||||
*/
|
||||
+ (void)startService:(Class)service;
|
||||
|
||||
/**
|
||||
* Configure the SDK with an array of services to start from a library. This will not start the service at application level, it will enable
|
||||
* the service only for the library.
|
||||
*
|
||||
* @param services Array of services to start.
|
||||
*/
|
||||
+ (void)startFromLibraryWithServices:(NSArray<Class> *)services;
|
||||
|
||||
/**
|
||||
* Check whether the SDK has already been configured or not.
|
||||
*
|
||||
* @return YES if configured, NO otherwise.
|
||||
*/
|
||||
+ (BOOL)isConfigured;
|
||||
|
||||
/**
|
||||
* Check whether app is running in App Center Test Cloud.
|
||||
*
|
||||
* @return true if running in App Center Test Cloud, false otherwise.
|
||||
*/
|
||||
+ (BOOL)isRunningInAppCenterTestCloud;
|
||||
|
||||
/**
|
||||
* Change the base URL (schema + authority + port only) used to communicate with the backend.
|
||||
*
|
||||
* @param logUrl Base URL to use for backend communication.
|
||||
*/
|
||||
+ (void)setLogUrl:(NSString *)logUrl;
|
||||
|
||||
/**
|
||||
* Enable or disable the SDK as a whole. In addition to AppCenter resources, it will also enable or disable all registered services.
|
||||
* The state is persisted in the device's storage across application launches.
|
||||
*
|
||||
* @param isEnabled YES to enable, NO to disable.
|
||||
*
|
||||
* @see isEnabled
|
||||
*/
|
||||
+ (void)setEnabled:(BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Check whether the SDK is enabled or not as a whole.
|
||||
*
|
||||
* @return YES if enabled, NO otherwise.
|
||||
*
|
||||
* @see setEnabled:
|
||||
*/
|
||||
+ (BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Get log level.
|
||||
*
|
||||
* @return Log level.
|
||||
*/
|
||||
+ (MSLogLevel)logLevel;
|
||||
|
||||
/**
|
||||
* Set log level.
|
||||
*
|
||||
* @param logLevel The log level.
|
||||
*/
|
||||
+ (void)setLogLevel:(MSLogLevel)logLevel;
|
||||
|
||||
/**
|
||||
* Set log level handler.
|
||||
*
|
||||
* @param logHandler Handler.
|
||||
*/
|
||||
+ (void)setLogHandler:(MSLogHandler)logHandler;
|
||||
|
||||
/**
|
||||
* Set wrapper SDK information to use when building device properties. This is intended in case you are building a SDK that uses the App
|
||||
* Center SDK under the hood, e.g. our Xamarin SDK or ReactNative SDk.
|
||||
*
|
||||
* @param wrapperSdk Wrapper SDK information.
|
||||
*/
|
||||
+ (void)setWrapperSdk:(MSWrapperSdk *)wrapperSdk;
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
/**
|
||||
* Set the custom properties.
|
||||
*
|
||||
* @param customProperties Custom properties object.
|
||||
*/
|
||||
+ (void)setCustomProperties:(MSCustomProperties *)customProperties;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Check whether the application delegate forwarder is enabled or not.
|
||||
*
|
||||
* @return YES if enabled, NO otherwise.
|
||||
*
|
||||
* @discussion The application delegate forwarder forwards messages that target your application delegate methods via swizzling to the SDK.
|
||||
* It simplifies the SDK integration but may not be suitable to any situations. For
|
||||
* instance it should be disabled if you or one of your third party SDK is doing message forwarding on the application delegate. Message
|
||||
* forwarding usually implies the implementation of @see NSObject#forwardingTargetForSelector: or @see NSObject#forwardInvocation: methods.
|
||||
* To disable the application delegate forwarder just add the `AppCenterAppDelegateForwarderEnabled` tag to your Info .plist file and set it
|
||||
* to `0`. Then you will have to forward any application delegate needed by the SDK manually.
|
||||
*/
|
||||
+ (BOOL)isAppDelegateForwarderEnabled;
|
||||
|
||||
/**
|
||||
* Get unique installation identifier.
|
||||
*
|
||||
* @return Unique installation identifier.
|
||||
*/
|
||||
+ (NSUUID *)installId;
|
||||
|
||||
/**
|
||||
* Detect if a debugger is attached to the app process. This is only invoked once on app startup and can not detect
|
||||
* if the debugger is being attached during runtime!
|
||||
*
|
||||
* @return BOOL if the debugger is attached.
|
||||
*/
|
||||
+ (BOOL)isDebuggerAttached;
|
||||
|
||||
/**
|
||||
* Get the current version of AppCenter SDK.
|
||||
*
|
||||
* @return The current version of AppCenter SDK.
|
||||
*/
|
||||
+ (NSString *)sdkVersion;
|
||||
|
||||
/**
|
||||
* Set the maximum size of the internal storage. This method must be called before App Center is started. This method is only intended for
|
||||
* applications.
|
||||
*
|
||||
* @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size
|
||||
* (default is 4096 bytes). Values below 20,480 bytes (20 KiB) will be ignored.
|
||||
*
|
||||
* @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size
|
||||
* is successful, and `NO` otherwise. This parameter can be null.
|
||||
*
|
||||
* @discussion This only sets the maximum size of the database, but App Center modules might store additional data.
|
||||
* The value passed to this method is not persisted on disk. The default maximum database size is 10485760 bytes (10 MiB).
|
||||
*/
|
||||
+ (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(void (^)(BOOL))completionHandler;
|
||||
|
||||
/**
|
||||
* Set the user identifier.
|
||||
*
|
||||
* @param userId User identifier.
|
||||
*
|
||||
* @discussion Set the user identifier for logs sent for the default target token when the secret passed in @c
|
||||
* MSAppCenter:start:withServices: contains "target={targetToken}".
|
||||
*
|
||||
* For App Center backend the user identifier maximum length is 256 characters.
|
||||
*
|
||||
* AppCenter must be configured or started before this API can be used.
|
||||
*/
|
||||
+ (void)setUserId:(NSString *)userId;
|
||||
|
||||
/**
|
||||
* Set country code to use when building device properties.
|
||||
*
|
||||
* @param countryCode The two-letter ISO country code. @see https://www.iso.org/obp/ui/#search for more information.
|
||||
*/
|
||||
+ (void)setCountryCode:(NSString *)countryCode;
|
||||
|
||||
@end
|
@ -1,36 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define MS_APP_CENTER_BASE_DOMAIN @"com.Microsoft.AppCenter."
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Domain
|
||||
|
||||
static NSString *const kMSACErrorDomain = MS_APP_CENTER_BASE_DOMAIN @"ErrorDomain";
|
||||
|
||||
#pragma mark - General
|
||||
|
||||
// Error codes.
|
||||
NS_ENUM(NSInteger){MSACLogInvalidContainerErrorCode = 1, MSACCanceledErrorCode = 2, MSACDisabledErrorCode = 3};
|
||||
|
||||
// Error descriptions.
|
||||
static NSString const *kMSACLogInvalidContainerErrorDesc = @"Invalid log container.";
|
||||
static NSString const *kMSACCanceledErrorDesc = @"The operation was canceled.";
|
||||
static NSString const *kMSACDisabledErrorDesc = @"The service is disabled.";
|
||||
|
||||
#pragma mark - Connection
|
||||
|
||||
// Error codes.
|
||||
NS_ENUM(NSInteger){MSACConnectionPausedErrorCode = 100, MSACConnectionHttpErrorCode = 101};
|
||||
|
||||
// Error descriptions.
|
||||
static NSString const *kMSACConnectionHttpErrorDesc = @"An HTTP error occured.";
|
||||
static NSString const *kMSACConnectionPausedErrorDesc = @"Canceled, connection paused with log deletion.";
|
||||
|
||||
// Error user info keys.
|
||||
static NSString const *kMSACConnectionHttpCodeErrorKey = @"MSACConnectionHttpCode";
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,83 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_CHANNEL_GROUP_PROTOCOL_H
|
||||
#define MS_CHANNEL_GROUP_PROTOCOL_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSChannelProtocol.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class MSChannelUnitConfiguration;
|
||||
|
||||
@protocol MSIngestionProtocol;
|
||||
@protocol MSChannelUnitProtocol;
|
||||
|
||||
/**
|
||||
* `MSChannelGroupProtocol` represents a kind of channel that contains constituent MSChannelUnit objects. When an operation from the
|
||||
* `MSChannelProtocol` is performed on the group, that operation should be propagated to its constituent MSChannelUnit objects.
|
||||
*/
|
||||
@protocol MSChannelGroupProtocol <MSChannelProtocol>
|
||||
|
||||
/**
|
||||
* Initialize a channel unit with the given configuration.
|
||||
*
|
||||
* @param configuration channel configuration.
|
||||
*
|
||||
* @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs.
|
||||
*/
|
||||
- (id<MSChannelUnitProtocol>)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration;
|
||||
|
||||
/**
|
||||
* Initialize a channel unit with the given configuration.
|
||||
*
|
||||
* @param configuration channel configuration.
|
||||
* @param ingestion The alternative ingestion object
|
||||
*
|
||||
* @return The added `MSChannelUnitProtocol`. Use this object to enqueue logs.
|
||||
*/
|
||||
- (id<MSChannelUnitProtocol>)addChannelUnitWithConfiguration:(MSChannelUnitConfiguration *)configuration
|
||||
withIngestion:(nullable id<MSIngestionProtocol>)ingestion;
|
||||
|
||||
/**
|
||||
* Change the base URL (schema + authority + port only) used to communicate with the backend.
|
||||
*
|
||||
* @param logUrl base URL to use for backend communication.
|
||||
*/
|
||||
- (void)setLogUrl:(NSString *)logUrl;
|
||||
|
||||
/**
|
||||
* Set the app secret.
|
||||
*
|
||||
* @param appSecret The app secret.
|
||||
*/
|
||||
- (void)setAppSecret:(NSString *)appSecret;
|
||||
|
||||
/**
|
||||
* Set the maximum size of the internal storage. This method must be called before App Center is started.
|
||||
*
|
||||
* @discussion The default maximum database size is 10485760 bytes (10 MiB).
|
||||
*
|
||||
* @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size
|
||||
* (default is 4096 bytes). Values below 24576 bytes (24 KiB) will be ignored.
|
||||
* @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size
|
||||
* is successful, and `NO` otherwise.
|
||||
*/
|
||||
- (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(nullable void (^)(BOOL))completionHandler;
|
||||
|
||||
/**
|
||||
* Return a channel unit instance for the given groupId.
|
||||
*
|
||||
* @param groupId The group ID for a channel unit.
|
||||
*
|
||||
* @return A channel unit instance or `nil`.
|
||||
*/
|
||||
- (id<MSChannelUnitProtocol>)channelUnitForGroupId:(NSString *)groupId;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -1,63 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_CHANNEL_PROTOCOL_H
|
||||
#define MS_CHANNEL_PROTOCOL_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSEnable.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol MSChannelDelegate;
|
||||
|
||||
/**
|
||||
* `MSChannelProtocol` contains the essential operations of a channel. Channels are broadly responsible for enqueuing logs to be sent to the
|
||||
* backend and/or stored on disk.
|
||||
*/
|
||||
@protocol MSChannelProtocol <NSObject, MSEnable>
|
||||
|
||||
/**
|
||||
* Add delegate.
|
||||
*
|
||||
* @param delegate delegate.
|
||||
*/
|
||||
- (void)addDelegate:(id<MSChannelDelegate>)delegate;
|
||||
|
||||
/**
|
||||
* Remove delegate.
|
||||
*
|
||||
* @param delegate delegate.
|
||||
*/
|
||||
- (void)removeDelegate:(id<MSChannelDelegate>)delegate;
|
||||
|
||||
/**
|
||||
* Pause operations, logs will be stored but not sent.
|
||||
*
|
||||
* @param identifyingObject Object used to identify the pause request.
|
||||
*
|
||||
* @discussion A paused channel doesn't forward logs to the ingestion. The identifying object used to pause the channel can be any unique
|
||||
* object. The same identifying object must be used to call resume. For simplicity if the caller is the one owning the channel then @c self
|
||||
* can be used as identifying object.
|
||||
*
|
||||
* @see resumeWithIdentifyingObject:
|
||||
*/
|
||||
- (void)pauseWithIdentifyingObject:(id<NSObject>)identifyingObject;
|
||||
|
||||
/**
|
||||
* Resume operations, logs can be sent again.
|
||||
*
|
||||
* @param identifyingObject Object used to passed to the pause method.
|
||||
*
|
||||
* @discussion The channel only resume when all the outstanding identifying objects have been resumed.
|
||||
*
|
||||
* @see pauseWithIdentifyingObject:
|
||||
*/
|
||||
- (void)resumeWithIdentifyingObject:(id<NSObject>)identifyingObject;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif
|
@ -1,170 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Log Levels
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, MSLogLevel) {
|
||||
|
||||
/**
|
||||
* Logging will be very chatty
|
||||
*/
|
||||
MSLogLevelVerbose = 2,
|
||||
|
||||
/**
|
||||
* Debug information will be logged
|
||||
*/
|
||||
MSLogLevelDebug = 3,
|
||||
|
||||
/**
|
||||
* Information will be logged
|
||||
*/
|
||||
MSLogLevelInfo = 4,
|
||||
|
||||
/**
|
||||
* Errors and warnings will be logged
|
||||
*/
|
||||
MSLogLevelWarning = 5,
|
||||
|
||||
/**
|
||||
* Errors will be logged
|
||||
*/
|
||||
MSLogLevelError = 6,
|
||||
|
||||
/**
|
||||
* Only critical errors will be logged
|
||||
*/
|
||||
MSLogLevelAssert = 7,
|
||||
|
||||
/**
|
||||
* Logging is disabled
|
||||
*/
|
||||
MSLogLevelNone = 99
|
||||
};
|
||||
|
||||
typedef NSString * (^MSLogMessageProvider)(void);
|
||||
typedef void (^MSLogHandler)(MSLogMessageProvider messageProvider, MSLogLevel logLevel, NSString *tag, const char *file,
|
||||
const char *function, uint line);
|
||||
|
||||
/**
|
||||
* Channel priorities, check the kMSPriorityCount if you add a new value.
|
||||
* The order matters here! Values NEED to range from low priority to high priority.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, MSPriority) { MSPriorityBackground, MSPriorityDefault, MSPriorityHigh };
|
||||
static short const kMSPriorityCount = MSPriorityHigh + 1;
|
||||
|
||||
/**
|
||||
* The priority by which the modules are initialized.
|
||||
* MSPriorityMax is reserved for only 1 module and this needs to be Crashes.
|
||||
* Crashes needs to be initialized first to catch crashes in our other SDK Modules (which will hopefully never happen) and to avoid losing
|
||||
* any log at crash time.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, MSInitializationPriority) {
|
||||
MSInitializationPriorityDefault = 500,
|
||||
MSInitializationPriorityHigh = 750,
|
||||
MSInitializationPriorityMax = 999
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum with the different HTTP status codes.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, MSHTTPCodesNo) {
|
||||
|
||||
// Invalid
|
||||
MSHTTPCodesNo0XXInvalidUnknown = 0,
|
||||
|
||||
// Informational
|
||||
MSHTTPCodesNo1XXInformationalUnknown = 1,
|
||||
MSHTTPCodesNo100Continue = 100,
|
||||
MSHTTPCodesNo101SwitchingProtocols = 101,
|
||||
MSHTTPCodesNo102Processing = 102,
|
||||
|
||||
// Success
|
||||
MSHTTPCodesNo2XXSuccessUnknown = 2,
|
||||
MSHTTPCodesNo200OK = 200,
|
||||
MSHTTPCodesNo201Created = 201,
|
||||
MSHTTPCodesNo202Accepted = 202,
|
||||
MSHTTPCodesNo203NonAuthoritativeInformation = 203,
|
||||
MSHTTPCodesNo204NoContent = 204,
|
||||
MSHTTPCodesNo205ResetContent = 205,
|
||||
MSHTTPCodesNo206PartialContent = 206,
|
||||
MSHTTPCodesNo207MultiStatus = 207,
|
||||
MSHTTPCodesNo208AlreadyReported = 208,
|
||||
MSHTTPCodesNo209IMUsed = 209,
|
||||
|
||||
// Redirection
|
||||
MSHTTPCodesNo3XXSuccessUnknown = 3,
|
||||
MSHTTPCodesNo300MultipleChoices = 300,
|
||||
MSHTTPCodesNo301MovedPermanently = 301,
|
||||
MSHTTPCodesNo302Found = 302,
|
||||
MSHTTPCodesNo303SeeOther = 303,
|
||||
MSHTTPCodesNo304NotModified = 304,
|
||||
MSHTTPCodesNo305UseProxy = 305,
|
||||
MSHTTPCodesNo306SwitchProxy = 306,
|
||||
MSHTTPCodesNo307TemporaryRedirect = 307,
|
||||
MSHTTPCodesNo308PermanentRedirect = 308,
|
||||
|
||||
// Client error
|
||||
MSHTTPCodesNo4XXSuccessUnknown = 4,
|
||||
MSHTTPCodesNo400BadRequest = 400,
|
||||
MSHTTPCodesNo401Unauthorised = 401,
|
||||
MSHTTPCodesNo402PaymentRequired = 402,
|
||||
MSHTTPCodesNo403Forbidden = 403,
|
||||
MSHTTPCodesNo404NotFound = 404,
|
||||
MSHTTPCodesNo405MethodNotAllowed = 405,
|
||||
MSHTTPCodesNo406NotAcceptable = 406,
|
||||
MSHTTPCodesNo407ProxyAuthenticationRequired = 407,
|
||||
MSHTTPCodesNo408RequestTimeout = 408,
|
||||
MSHTTPCodesNo409Conflict = 409,
|
||||
MSHTTPCodesNo410Gone = 410,
|
||||
MSHTTPCodesNo411LengthRequired = 411,
|
||||
MSHTTPCodesNo412PreconditionFailed = 412,
|
||||
MSHTTPCodesNo413RequestEntityTooLarge = 413,
|
||||
MSHTTPCodesNo414RequestURITooLong = 414,
|
||||
MSHTTPCodesNo415UnsupportedMediaType = 415,
|
||||
MSHTTPCodesNo416RequestedRangeNotSatisfiable = 416,
|
||||
MSHTTPCodesNo417ExpectationFailed = 417,
|
||||
MSHTTPCodesNo418IamATeapot = 418,
|
||||
MSHTTPCodesNo419AuthenticationTimeout = 419,
|
||||
MSHTTPCodesNo420MethodFailureSpringFramework = 420,
|
||||
MSHTTPCodesNo420EnhanceYourCalmTwitter = 4200,
|
||||
MSHTTPCodesNo422UnprocessableEntity = 422,
|
||||
MSHTTPCodesNo423Locked = 423,
|
||||
MSHTTPCodesNo424FailedDependency = 424,
|
||||
MSHTTPCodesNo424MethodFailureWebDaw = 4240,
|
||||
MSHTTPCodesNo425UnorderedCollection = 425,
|
||||
MSHTTPCodesNo426UpgradeRequired = 426,
|
||||
MSHTTPCodesNo428PreconditionRequired = 428,
|
||||
MSHTTPCodesNo429TooManyRequests = 429,
|
||||
MSHTTPCodesNo431RequestHeaderFieldsTooLarge = 431,
|
||||
MSHTTPCodesNo444NoResponseNginx = 444,
|
||||
MSHTTPCodesNo449RetryWithMicrosoft = 449,
|
||||
MSHTTPCodesNo450BlockedByWindowsParentalControls = 450,
|
||||
MSHTTPCodesNo451RedirectMicrosoft = 451,
|
||||
MSHTTPCodesNo451UnavailableForLegalReasons = 4510,
|
||||
MSHTTPCodesNo494RequestHeaderTooLargeNginx = 494,
|
||||
MSHTTPCodesNo495CertErrorNginx = 495,
|
||||
MSHTTPCodesNo496NoCertNginx = 496,
|
||||
MSHTTPCodesNo497HTTPToHTTPSNginx = 497,
|
||||
MSHTTPCodesNo499ClientClosedRequestNginx = 499,
|
||||
|
||||
// Server error
|
||||
MSHTTPCodesNo5XXSuccessUnknown = 5,
|
||||
MSHTTPCodesNo500InternalServerError = 500,
|
||||
MSHTTPCodesNo501NotImplemented = 501,
|
||||
MSHTTPCodesNo502BadGateway = 502,
|
||||
MSHTTPCodesNo503ServiceUnavailable = 503,
|
||||
MSHTTPCodesNo504GatewayTimeout = 504,
|
||||
MSHTTPCodesNo505HTTPVersionNotSupported = 505,
|
||||
MSHTTPCodesNo506VariantAlsoNegotiates = 506,
|
||||
MSHTTPCodesNo507InsufficientStorage = 507,
|
||||
MSHTTPCodesNo508LoopDetected = 508,
|
||||
MSHTTPCodesNo509BandwidthLimitExceeded = 509,
|
||||
MSHTTPCodesNo510NotExtended = 510,
|
||||
MSHTTPCodesNo511NetworkAuthenticationRequired = 511,
|
||||
MSHTTPCodesNo522ConnectionTimedOut = 522,
|
||||
MSHTTPCodesNo598NetworkReadTimeoutErrorUnknown = 598,
|
||||
MSHTTPCodesNo599NetworkConnectTimeoutErrorUnknown = 599
|
||||
};
|
@ -1,65 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Custom properties builder.
|
||||
* Collects multiple properties to send in one log.
|
||||
*/
|
||||
@interface MSCustomProperties : NSObject
|
||||
|
||||
/**
|
||||
* Set the specified property value with the specified key.
|
||||
* If the properties previously contained a property for the key, the old value is replaced.
|
||||
*
|
||||
* @param key Key with which the specified value is to be set.
|
||||
* @param value Value to be set with the specified key.
|
||||
*
|
||||
* @return This instance.
|
||||
*/
|
||||
- (instancetype)setString:(NSString *)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Set the specified property value with the specified key.
|
||||
* If the properties previously contained a property for the key, the old value is replaced.
|
||||
*
|
||||
* @param key Key with which the specified value is to be set.
|
||||
* @param value Value to be set with the specified key.
|
||||
*
|
||||
* @return This instance.
|
||||
*/
|
||||
- (instancetype)setNumber:(NSNumber *)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Set the specified property value with the specified key.
|
||||
* If the properties previously contained a property for the key, the old value is replaced.
|
||||
*
|
||||
* @param key Key with which the specified value is to be set.
|
||||
* @param value Value to be set with the specified key.
|
||||
*
|
||||
* @return This instance.
|
||||
*/
|
||||
- (instancetype)setBool:(BOOL)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Set the specified property value with the specified key.
|
||||
* If the properties previously contained a property for the key, the old value is replaced.
|
||||
*
|
||||
* @param key Key with which the specified value is to be set.
|
||||
* @param value Value to be set with the specified key.
|
||||
*
|
||||
* @return This instance.
|
||||
*/
|
||||
- (instancetype)setDate:(NSDate *)value forKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Clear the property for the specified key.
|
||||
*
|
||||
* @param key Key whose mapping is to be cleared.
|
||||
*
|
||||
* @return This instance.
|
||||
*/
|
||||
- (instancetype)clearPropertyForKey:(NSString *)key;
|
||||
|
||||
@end
|
@ -1,91 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSWrapperSdk.h"
|
||||
|
||||
@interface MSDevice : MSWrapperSdk
|
||||
|
||||
/*
|
||||
* Name of the SDK. Consists of the name of the SDK and the platform, e.g. "appcenter.ios", "appcenter.android"
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *sdkName;
|
||||
|
||||
/*
|
||||
* Version of the SDK in semver format, e.g. "1.2.0" or "0.12.3-alpha.1".
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *sdkVersion;
|
||||
|
||||
/*
|
||||
* Device model (example: iPad2,3).
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *model;
|
||||
|
||||
/*
|
||||
* Device manufacturer (example: HTC).
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *oemName;
|
||||
|
||||
/*
|
||||
* OS name (example: iOS).
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *osName;
|
||||
|
||||
/*
|
||||
* OS version (example: 9.3.0).
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *osVersion;
|
||||
|
||||
/*
|
||||
* OS build code (example: LMY47X). [optional]
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *osBuild;
|
||||
|
||||
/*
|
||||
* API level when applicable like in Android (example: 15). [optional]
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSNumber *osApiLevel;
|
||||
|
||||
/*
|
||||
* Language code (example: en_US).
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *locale;
|
||||
|
||||
/*
|
||||
* The offset in minutes from UTC for the device time zone, including daylight savings time.
|
||||
*/
|
||||
@property(nonatomic, readonly, strong) NSNumber *timeZoneOffset;
|
||||
|
||||
/*
|
||||
* Screen size of the device in pixels (example: 640x480).
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *screenSize;
|
||||
|
||||
/*
|
||||
* Application version name, e.g. 1.1.0
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *appVersion;
|
||||
|
||||
/*
|
||||
* Carrier name (for mobile devices). [optional]
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *carrierName;
|
||||
|
||||
/*
|
||||
* Carrier country code (for mobile devices). [optional]
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *carrierCountry;
|
||||
|
||||
/*
|
||||
* The app's build number, e.g. 42.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *appBuild;
|
||||
|
||||
/*
|
||||
* The bundle identifier, package identifier, or namespace, depending on what the individual plattforms use, .e.g com.microsoft.example.
|
||||
* [optional]
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *appNamespace;
|
||||
|
||||
@end
|
@ -1,26 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#ifndef MS_ENABLE_H
|
||||
#define MS_ENABLE_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Protocol to define an instance that can be enabled/disabled.
|
||||
*/
|
||||
@protocol MSEnable <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
* Enable/disable this instance and delete data on disabled state.
|
||||
*
|
||||
* @param isEnabled A boolean value set to YES to enable the instance or NO to disable it.
|
||||
* @param deleteData A boolean value set to YES to delete data or NO to keep it.
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)isEnabled andDeleteDataOnDisabled:(BOOL)deleteData;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -1,68 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MSDevice;
|
||||
|
||||
@protocol MSLog <NSObject>
|
||||
|
||||
/**
|
||||
* Log type.
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *type;
|
||||
|
||||
/**
|
||||
* Log timestamp.
|
||||
*/
|
||||
@property(nonatomic, strong) NSDate *timestamp;
|
||||
|
||||
/**
|
||||
* A session identifier is used to correlate logs together. A session is an abstract concept in the API and is not necessarily an analytics
|
||||
* session, it can be used to only track crashes.
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *sid;
|
||||
|
||||
/**
|
||||
* Optional distribution group ID value.
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *distributionGroupId;
|
||||
|
||||
/**
|
||||
* Optional user identifier.
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *userId;
|
||||
|
||||
/**
|
||||
* Device properties associated to this log.
|
||||
*/
|
||||
@property(nonatomic, strong) MSDevice *device;
|
||||
|
||||
/**
|
||||
* Transient object tag. For example, a log can be tagged with a transmission target. We do this currently to prevent properties being
|
||||
* applied retroactively to previous logs by comparing their tags.
|
||||
*/
|
||||
@property(nonatomic, strong) NSObject *tag;
|
||||
|
||||
/**
|
||||
* Checks if the object's values are valid.
|
||||
*
|
||||
* @return YES, if the object is valid.
|
||||
*/
|
||||
- (BOOL)isValid;
|
||||
|
||||
/**
|
||||
* Adds a transmission target token that this log should be sent to.
|
||||
*
|
||||
* @param token The transmission target token.
|
||||
*/
|
||||
- (void)addTransmissionTargetToken:(NSString *)token;
|
||||
|
||||
/**
|
||||
* Gets all transmission target tokens that this log should be sent to.
|
||||
*
|
||||
* @returns Collection of transmission target tokens that this log should be sent to.
|
||||
*/
|
||||
- (NSSet *)transmissionTargetTokens;
|
||||
|
||||
@end
|
@ -1,15 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSAbstractLog.h"
|
||||
|
||||
@interface MSLogWithProperties : MSAbstractLog
|
||||
|
||||
/**
|
||||
* Additional key/value pair parameters. [optional]
|
||||
*/
|
||||
@property(nonatomic, strong) NSDictionary<NSString *, NSString *> *properties;
|
||||
|
||||
@end
|
@ -1,44 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSConstants.h"
|
||||
|
||||
#define MSLog(_level, _tag, _message) \
|
||||
[MSLogger logMessage:_message level:_level tag:_tag file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__]
|
||||
#define MSLogAssert(tag, format, ...) \
|
||||
MSLog(MSLogLevelAssert, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogError(tag, format, ...) \
|
||||
MSLog(MSLogLevelError, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogWarning(tag, format, ...) \
|
||||
MSLog(MSLogLevelWarning, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogInfo(tag, format, ...) \
|
||||
MSLog(MSLogLevelInfo, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogDebug(tag, format, ...) \
|
||||
MSLog(MSLogLevelDebug, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
#define MSLogVerbose(tag, format, ...) \
|
||||
MSLog(MSLogLevelVerbose, tag, (^{ \
|
||||
return [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
|
||||
}))
|
||||
|
||||
@interface MSLogger : NSObject
|
||||
|
||||
+ (void)logMessage:(MSLogMessageProvider)messageProvider
|
||||
level:(MSLogLevel)loglevel
|
||||
tag:(NSString *)tag
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(uint)line;
|
||||
|
||||
@end
|
@ -1,30 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Protocol declaring service logic.
|
||||
*/
|
||||
@protocol MSService <NSObject>
|
||||
|
||||
/**
|
||||
* Enable or disable this service.
|
||||
* The state is persisted in the device's storage across application launches.
|
||||
*
|
||||
* @param isEnabled Whether this service is enabled or not.
|
||||
*
|
||||
* @see isEnabled
|
||||
*/
|
||||
+ (void)setEnabled:(BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Indicates whether this service is enabled.
|
||||
*
|
||||
* @return `YES` if this service is enabled, `NO` if it is not.
|
||||
*
|
||||
* @see setEnabled:
|
||||
*/
|
||||
+ (BOOL)isEnabled;
|
||||
|
||||
@end
|
@ -1,50 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSService.h"
|
||||
|
||||
@protocol MSChannelGroupProtocol;
|
||||
|
||||
/**
|
||||
* Abstraction of services common logic.
|
||||
* This class is intended to be subclassed only not instantiated directly.
|
||||
*/
|
||||
@interface MSServiceAbstract : NSObject <MSService>
|
||||
|
||||
/**
|
||||
* The flag indicates whether the service is started from application or not.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL startedFromApplication;
|
||||
|
||||
/**
|
||||
* Start this service with a channel group. Also sets the flag that indicates that a service has been started.
|
||||
*
|
||||
* @param channelGroup channel group used to persist and send logs.
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
* @param fromApplication indicates whether the service started from an application or not.
|
||||
*/
|
||||
- (void)startWithChannelGroup:(id<MSChannelGroupProtocol>)channelGroup
|
||||
appSecret:(NSString *)appSecret
|
||||
transmissionTargetToken:(NSString *)token
|
||||
fromApplication:(BOOL)fromApplication;
|
||||
|
||||
/**
|
||||
* Update configuration when the service requires to start again. This method should only be called if the service is started from libraries
|
||||
* and then is being started from an application.
|
||||
*
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
*/
|
||||
- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token;
|
||||
|
||||
/**
|
||||
* Checks if the service needs the application secret.
|
||||
*
|
||||
* @return `YES` if the application secret is required, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isAppSecretRequired;
|
||||
|
||||
@end
|
@ -1,16 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSConstants.h"
|
||||
|
||||
/**
|
||||
* This is a utility for producing App Center style log messages. It is only intended for use by App Center services and wrapper SDKs of App
|
||||
* Center.
|
||||
*/
|
||||
@interface MSWrapperLogger : NSObject
|
||||
|
||||
+ (void)MSWrapperLog:(MSLogMessageProvider)message tag:(NSString *)tag level:(MSLogLevel)level;
|
||||
|
||||
@end
|
@ -1,54 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MSWrapperSdk : NSObject
|
||||
|
||||
/*
|
||||
* Version of the wrapper SDK. When the SDK is embedding another base SDK (for example Xamarin.Android wraps Android), the Xamarin specific
|
||||
* version is populated into this field while sdkVersion refers to the original Android SDK. [optional]
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *wrapperSdkVersion;
|
||||
|
||||
/*
|
||||
* Name of the wrapper SDK (examples: Xamarin, Cordova). [optional]
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *wrapperSdkName;
|
||||
|
||||
/*
|
||||
* Version of the wrapper technology framework (Xamarin runtime version or ReactNative or Cordova etc...). [optional]
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *wrapperRuntimeVersion;
|
||||
|
||||
/*
|
||||
* Label that is used to identify application code 'version' released via Live Update beacon running on device.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *liveUpdateReleaseLabel;
|
||||
|
||||
/*
|
||||
* Identifier of environment that current application release belongs to, deployment key then maps to environment like Production, Staging.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *liveUpdateDeploymentKey;
|
||||
|
||||
/*
|
||||
* Hash of all files (ReactNative or Cordova) deployed to device via LiveUpdate beacon. Helps identify the Release version on device or need
|
||||
* to download updates in future
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *liveUpdatePackageHash;
|
||||
|
||||
- (instancetype)initWithWrapperSdkVersion:(NSString *)wrapperSdkVersion
|
||||
wrapperSdkName:(NSString *)wrapperSdkName
|
||||
wrapperRuntimeVersion:(NSString *)wrapperRuntimeVersion
|
||||
liveUpdateReleaseLabel:(NSString *)liveUpdateReleaseLabel
|
||||
liveUpdateDeploymentKey:(NSString *)liveUpdateDeploymentKey
|
||||
liveUpdatePackageHash:(NSString *)liveUpdatePackageHash;
|
||||
|
||||
/**
|
||||
* Checks if the object's values are valid.
|
||||
*
|
||||
* @return YES, if the object is valid.
|
||||
*/
|
||||
- (BOOL)isValid;
|
||||
|
||||
@end
|
@ -1,13 +0,0 @@
|
||||
framework module AppCenter {
|
||||
umbrella header "AppCenter.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
|
||||
link framework "Foundation"
|
||||
link framework "CoreTelephony"
|
||||
link framework "SystemConfiguration"
|
||||
link framework "UIKit"
|
||||
link "sqlite3"
|
||||
link "z"
|
||||
}
|
Binary file not shown.
@ -1,13 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSAbstractLog.h"
|
||||
#import "MSAnalytics.h"
|
||||
#import "MSAnalyticsTransmissionTarget.h"
|
||||
#import "MSAnalyticsAuthenticationProvider.h"
|
||||
#import "MSAnalyticsAuthenticationProviderDelegate.h"
|
||||
#import "MSConstants+Flags.h"
|
||||
#import "MSEventLog.h"
|
||||
#import "MSEventProperties.h"
|
@ -1,8 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MSAbstractLog : NSObject
|
||||
|
||||
@end
|
@ -1,213 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import "MSAnalyticsTransmissionTarget.h"
|
||||
#import "MSServiceAbstract.h"
|
||||
|
||||
@class MSEventProperties;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* App Center analytics service.
|
||||
*/
|
||||
@interface MSAnalytics : MSServiceAbstract
|
||||
|
||||
/**
|
||||
* Track an event.
|
||||
*
|
||||
* @param eventName Event name. Cannot be `nil` or empty.
|
||||
*
|
||||
* @discussion Validation rules apply depending on the configured secret.
|
||||
*
|
||||
* For App Center, the name cannot be longer than 256 and is truncated otherwise.
|
||||
*
|
||||
* For One Collector, the name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression.
|
||||
*/
|
||||
+ (void)trackEvent:(NSString *)eventName;
|
||||
|
||||
/**
|
||||
* Track a custom event with optional string properties.
|
||||
*
|
||||
* @param eventName Event name. Cannot be `nil` or empty.
|
||||
* @param properties Dictionary of properties. Keys and values must not be `nil`.
|
||||
*
|
||||
* @discussion Additional validation rules apply depending on the configured secret.
|
||||
*
|
||||
* For App Center:
|
||||
*
|
||||
* - The event name cannot be longer than 256 and is truncated otherwise.
|
||||
*
|
||||
* - The property names cannot be empty.
|
||||
*
|
||||
* - The property names and values are limited to 125 characters each (truncated).
|
||||
*
|
||||
* - The number of properties per event is limited to 20 (truncated).
|
||||
*
|
||||
*
|
||||
* For One Collector:
|
||||
*
|
||||
* - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression.
|
||||
*
|
||||
* - The `baseData` and `baseDataType` properties are reserved and thus discarded.
|
||||
*
|
||||
* - The full event size when encoded as a JSON string cannot be larger than 1.9MB.
|
||||
*/
|
||||
+ (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary<NSString *, NSString *> *)properties;
|
||||
|
||||
/**
|
||||
* Track a custom event with optional string properties.
|
||||
*
|
||||
* @param eventName Event name. Cannot be `nil` or empty.
|
||||
* @param properties Dictionary of properties. Keys and values must not be `nil`.
|
||||
* @param flags Optional flags. Events tracked with the MSFlagsCritical flag will take precedence over all other events in
|
||||
* storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the
|
||||
* MSFlagsCritical flag.
|
||||
*
|
||||
* @discussion Additional validation rules apply depending on the configured secret.
|
||||
*
|
||||
* For App Center:
|
||||
*
|
||||
* - The event name cannot be longer than 256 and is truncated otherwise.
|
||||
*
|
||||
* - The property names cannot be empty.
|
||||
*
|
||||
* - The property names and values are limited to 125 characters each (truncated).
|
||||
*
|
||||
* - The number of properties per event is limited to 20 (truncated).
|
||||
*
|
||||
*
|
||||
* For One Collector:
|
||||
*
|
||||
* - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression.
|
||||
*
|
||||
* - The `baseData` and `baseDataType` properties are reserved and thus discarded.
|
||||
*
|
||||
* - The full event size when encoded as a JSON string cannot be larger than 1.9MB.
|
||||
*/
|
||||
+ (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary<NSString *, NSString *> *)properties flags:(MSFlags)flags;
|
||||
|
||||
/**
|
||||
* Track a custom event with name and optional typed properties.
|
||||
*
|
||||
* @param eventName Event name.
|
||||
* @param properties Typed properties.
|
||||
*
|
||||
* @discussion The following validation rules are applied:
|
||||
*
|
||||
* The name cannot be null or empty.
|
||||
*
|
||||
* The property names or values cannot be null.
|
||||
*
|
||||
* Double values must be finite (NaN or Infinite values are discarded).
|
||||
*
|
||||
* Additional validation rules apply depending on the configured secret.
|
||||
*
|
||||
*
|
||||
* For App Center:
|
||||
*
|
||||
* - The event name cannot be longer than 256 and is truncated otherwise.
|
||||
*
|
||||
* - The property names cannot be empty.
|
||||
*
|
||||
* - The property names and values are limited to 125 characters each (truncated).
|
||||
*
|
||||
* - The number of properties per event is limited to 20 (truncated).
|
||||
*
|
||||
*
|
||||
* For One Collector:
|
||||
*
|
||||
* - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression.
|
||||
*
|
||||
* - The `baseData` and `baseDataType` properties are reserved and thus discarded.
|
||||
*
|
||||
* - The full event size when encoded as a JSON string cannot be larger than 1.9MB.
|
||||
*/
|
||||
+ (void)trackEvent:(NSString *)eventName
|
||||
withTypedProperties:(nullable MSEventProperties *)properties NS_SWIFT_NAME(trackEvent(_:withProperties:));
|
||||
|
||||
/**
|
||||
* Track a custom event with name and optional typed properties.
|
||||
*
|
||||
* @param eventName Event name.
|
||||
* @param properties Typed properties.
|
||||
* @param flags Optional flags. Events tracked with the MSFlagsCritical flag will take precedence over all other events in
|
||||
* storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the
|
||||
* MSFlagsCritical flag.
|
||||
*
|
||||
* @discussion The following validation rules are applied:
|
||||
*
|
||||
* The name cannot be null or empty.
|
||||
*
|
||||
* The property names or values cannot be null.
|
||||
*
|
||||
* Double values must be finite (NaN or Infinite values are discarded).
|
||||
*
|
||||
* Additional validation rules apply depending on the configured secret.
|
||||
*
|
||||
*
|
||||
* For App Center:
|
||||
*
|
||||
* - The event name cannot be longer than 256 and is truncated otherwise.
|
||||
*
|
||||
* - The property names cannot be empty.
|
||||
*
|
||||
* - The property names and values are limited to 125 characters each (truncated).
|
||||
*
|
||||
* - The number of properties per event is limited to 20 (truncated).
|
||||
*
|
||||
*
|
||||
* For One Collector:
|
||||
*
|
||||
* - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression.
|
||||
*
|
||||
* - The `baseData` and `baseDataType` properties are reserved and thus discarded.
|
||||
*
|
||||
* - The full event size when encoded as a JSON string cannot be larger than 1.9MB.
|
||||
*/
|
||||
+ (void)trackEvent:(NSString *)eventName
|
||||
withTypedProperties:(nullable MSEventProperties *)properties
|
||||
flags:(MSFlags)flags NS_SWIFT_NAME(trackEvent(_:withProperties:flags:));
|
||||
|
||||
/**
|
||||
* Pause transmission of Analytics logs. While paused, Analytics logs are saved to disk.
|
||||
*
|
||||
* @see resume
|
||||
*/
|
||||
+ (void)pause;
|
||||
|
||||
/**
|
||||
* Resume transmission of Analytics logs. Any Analytics logs that accumulated on disk while paused are sent to the
|
||||
* server.
|
||||
*
|
||||
* @see pause
|
||||
*/
|
||||
+ (void)resume;
|
||||
|
||||
/**
|
||||
* Get a transmission target.
|
||||
*
|
||||
* @param token The token of the transmission target to retrieve.
|
||||
*
|
||||
* @returns The transmission target object.
|
||||
*
|
||||
* @discussion This method does not need to be annotated with
|
||||
* NS_SWIFT_NAME(transmissionTarget(forToken:)) as this is a static method that
|
||||
* doesn't get translated like a setter in Swift.
|
||||
*
|
||||
* @see MSAnalyticsTransmissionTarget for comparison.
|
||||
*/
|
||||
+ (MSAnalyticsTransmissionTarget *)transmissionTargetForToken:(NSString *)token;
|
||||
|
||||
/**
|
||||
* Set the send time interval for non-critical logs.
|
||||
* Must be between 3 seconds and 86400 seconds (1 day).
|
||||
* Must be called before Analytics service start.
|
||||
*
|
||||
* @param interval The flush interval for logs.
|
||||
*/
|
||||
+ (void)setTransmissionInterval:(NSUInteger)interval;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,65 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSAnalyticsAuthenticationProviderDelegate.h"
|
||||
|
||||
/**
|
||||
* Different authentication types, e.g. MSA Compact, MSA Delegate, AAD,... .
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, MSAnalyticsAuthenticationType) {
|
||||
|
||||
/**
|
||||
* AuthenticationType MSA Compact.
|
||||
*/
|
||||
MSAnalyticsAuthenticationTypeMsaCompact,
|
||||
|
||||
/**
|
||||
* AuthenticationType MSA Delegate.
|
||||
*/
|
||||
MSAnalyticsAuthenticationTypeMsaDelegate
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface MSAnalyticsAuthenticationProvider : NSObject
|
||||
|
||||
/**
|
||||
* The type.
|
||||
*/
|
||||
@property(nonatomic, readonly, assign) MSAnalyticsAuthenticationType type;
|
||||
|
||||
/**
|
||||
* The ticket key for this authentication provider.
|
||||
*/
|
||||
@property(nonatomic, readonly, copy) NSString *ticketKey;
|
||||
|
||||
/**
|
||||
* The ticket key as hash.
|
||||
*/
|
||||
@property(nonatomic, readonly, copy) NSString *ticketKeyHash;
|
||||
|
||||
@property(nonatomic, readonly, weak) id<MSAnalyticsAuthenticationProviderDelegate> delegate;
|
||||
|
||||
/**
|
||||
* Create a new authentication provider.
|
||||
*
|
||||
* @param type The type for the provider, e.g. MSA.
|
||||
* @param ticketKey The ticket key for the provider.
|
||||
* @param delegate The delegate.
|
||||
*
|
||||
* @return A new authentication provider.
|
||||
*/
|
||||
- (instancetype)initWithAuthenticationType:(MSAnalyticsAuthenticationType)type
|
||||
ticketKey:(NSString *)ticketKey
|
||||
delegate:(id<MSAnalyticsAuthenticationProviderDelegate>)delegate;
|
||||
|
||||
/**
|
||||
* Check expiration.
|
||||
*/
|
||||
- (void)checkTokenExpiry;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,24 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MSAnalyticsAuthenticationProvider;
|
||||
|
||||
/**
|
||||
* Completion handler that returns the authentication token and the expiry date.
|
||||
*/
|
||||
typedef void (^MSAnalyticsAuthenticationProviderCompletionBlock)(NSString *token, NSDate *expiryDate);
|
||||
|
||||
@protocol MSAnalyticsAuthenticationProviderDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* Required method that needs to be called from within your authentication flow to provide the authentication token and expiry date.
|
||||
*
|
||||
* @param authenticationProvider The authentication provider.
|
||||
* @param completionHandler The completion handler.
|
||||
*/
|
||||
- (void)authenticationProvider:(MSAnalyticsAuthenticationProvider *)authenticationProvider
|
||||
acquireTokenWithCompletionHandler:(MSAnalyticsAuthenticationProviderCompletionBlock)completionHandler;
|
||||
|
||||
@end
|
@ -1,147 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSAnalyticsAuthenticationProvider.h"
|
||||
#import "MSConstants+Flags.h"
|
||||
#import "MSPropertyConfigurator.h"
|
||||
|
||||
@class MSEventProperties;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface MSAnalyticsTransmissionTarget : NSObject
|
||||
|
||||
/**
|
||||
* Property configurator.
|
||||
*/
|
||||
@property(nonatomic, readonly, strong) MSPropertyConfigurator *propertyConfigurator;
|
||||
|
||||
+ (void)addAuthenticationProvider:(MSAnalyticsAuthenticationProvider *)authenticationProvider
|
||||
NS_SWIFT_NAME(addAuthenticationProvider(authenticationProvider:));
|
||||
|
||||
/**
|
||||
* Track an event.
|
||||
*
|
||||
* @param eventName event name.
|
||||
*/
|
||||
- (void)trackEvent:(NSString *)eventName;
|
||||
|
||||
/**
|
||||
* Track an event.
|
||||
*
|
||||
* @param eventName event name.
|
||||
* @param properties dictionary of properties.
|
||||
*/
|
||||
- (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary<NSString *, NSString *> *)properties;
|
||||
|
||||
/**
|
||||
* Track an event.
|
||||
*
|
||||
* @param eventName event name.
|
||||
* @param properties dictionary of properties.
|
||||
* @param flags Optional flags. Events tracked with the MSFlagsCritical flag will take precedence over all other events in
|
||||
* storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the
|
||||
* MSFlagsCritical flag.
|
||||
*/
|
||||
- (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary<NSString *, NSString *> *)properties flags:(MSFlags)flags;
|
||||
|
||||
/**
|
||||
* Track a custom event with name and optional typed properties.
|
||||
*
|
||||
* @param eventName Event name.
|
||||
* @param properties Typed properties.
|
||||
*
|
||||
* @discussion The following validation rules are applied:
|
||||
*
|
||||
* The name cannot be null or empty.
|
||||
*
|
||||
* The property names or values cannot be null.
|
||||
*
|
||||
* Double values must be finite (NaN or Infinite values are discarded).
|
||||
*
|
||||
* Additional validation rules apply depending on the configured secret.
|
||||
*
|
||||
* - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression.
|
||||
*
|
||||
* - The `baseData` and `baseDataType` properties are reserved and thus discarded.
|
||||
*
|
||||
* - The full event size when encoded as a JSON string cannot be larger than 1.9MB.
|
||||
*/
|
||||
- (void)trackEvent:(NSString *)eventName
|
||||
withTypedProperties:(nullable MSEventProperties *)properties NS_SWIFT_NAME(trackEvent(_:withProperties:));
|
||||
|
||||
/**
|
||||
* Track a custom event with name and optional typed properties.
|
||||
*
|
||||
* @param eventName Event name.
|
||||
* @param properties Typed properties.
|
||||
* @param flags Optional flags. Events tracked with the MSFlagsCritical flag will take precedence over all other events in
|
||||
* storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the
|
||||
* MSFlagsCritical flag.
|
||||
*
|
||||
* @discussion The following validation rules are applied:
|
||||
*
|
||||
* The name cannot be null or empty.
|
||||
*
|
||||
* The property names or values cannot be null.
|
||||
*
|
||||
* Double values must be finite (NaN or Infinite values are discarded).
|
||||
*
|
||||
* Additional validation rules apply depending on the configured secret.
|
||||
*
|
||||
* - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression.
|
||||
*
|
||||
* - The `baseData` and `baseDataType` properties are reserved and thus discarded.
|
||||
*
|
||||
* - The full event size when encoded as a JSON string cannot be larger than 1.9MB.
|
||||
*/
|
||||
- (void)trackEvent:(NSString *)eventName
|
||||
withTypedProperties:(nullable MSEventProperties *)properties
|
||||
flags:(MSFlags)flags NS_SWIFT_NAME(trackEvent(_:withProperties:flags:));
|
||||
|
||||
/**
|
||||
* Get a nested transmission target.
|
||||
*
|
||||
* @param token The token of the transmission target to retrieve.
|
||||
*
|
||||
* @returns A transmission target object nested to this parent transmission target.
|
||||
*/
|
||||
- (MSAnalyticsTransmissionTarget *)transmissionTargetForToken:(NSString *)token NS_SWIFT_NAME(transmissionTarget(forToken:));
|
||||
|
||||
/**
|
||||
* Enable or disable this transmission target. It will also enable or disable nested transmission targets.
|
||||
*
|
||||
* @param isEnabled YES to enable, NO to disable.
|
||||
*
|
||||
* @see isEnabled
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Check whether this transmission target is enabled or not.
|
||||
*
|
||||
* @return YES if enabled, NO otherwise.
|
||||
*
|
||||
* @see setEnabled:
|
||||
*/
|
||||
- (BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Pause sending logs for the transmission target. It doesn't pause any of its decendants.
|
||||
*
|
||||
* @see resume
|
||||
*/
|
||||
- (void)pause;
|
||||
|
||||
/**
|
||||
* Resume sending logs for the transmission target.
|
||||
*
|
||||
* @see pause
|
||||
*/
|
||||
- (void)resume;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,13 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, MSFlags) {
|
||||
MSFlagsNone = (0 << 0), // => 00000000
|
||||
MSFlagsNormal = (1 << 0), // => 00000001
|
||||
MSFlagsCritical = (1 << 1), // => 00000010
|
||||
MSFlagsPersistenceNormal DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsNormal") = MSFlagsNormal,
|
||||
MSFlagsPersistenceCritical DEPRECATED_MSG_ATTRIBUTE("please use MSFlagsCritical") = MSFlagsCritical,
|
||||
MSFlagsDefault = MSFlagsNormal
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import "MSLogWithNameAndProperties.h"
|
||||
|
||||
@class MSEventProperties;
|
||||
@class MSMetadataExtension;
|
||||
|
||||
@interface MSEventLog : MSLogWithNameAndProperties
|
||||
|
||||
/**
|
||||
* Unique identifier for this event.
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *eventId;
|
||||
|
||||
/**
|
||||
* Event properties.
|
||||
*/
|
||||
@property(nonatomic, strong) MSEventProperties *typedProperties;
|
||||
|
||||
@end
|
@ -1,55 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Contains typed event properties.
|
||||
*/
|
||||
@interface MSEventProperties : NSObject
|
||||
|
||||
/**
|
||||
* Set a string property.
|
||||
*
|
||||
* @param value Property value.
|
||||
* @param key Property key.
|
||||
*/
|
||||
- (instancetype)setString:(NSString *)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Set a double property.
|
||||
*
|
||||
* @param value Property value. Must be finite (`NAN` and `INFINITY` not allowed).
|
||||
* @param key Property key.
|
||||
*/
|
||||
- (instancetype)setDouble:(double)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Set a 64-bit integer property.
|
||||
*
|
||||
* @param value Property value.
|
||||
* @param key Property key.
|
||||
*/
|
||||
- (instancetype)setInt64:(int64_t)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Set a boolean property.
|
||||
*
|
||||
* @param value Property value.
|
||||
* @param key Property key.
|
||||
*/
|
||||
- (instancetype)setBool:(BOOL)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Set a date property.
|
||||
*
|
||||
* @param value Property value.
|
||||
* @param key Property key.
|
||||
*/
|
||||
- (instancetype)setDate:(NSDate *)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
@ -1,15 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSLogWithProperties.h"
|
||||
|
||||
@interface MSLogWithNameAndProperties : MSLogWithProperties
|
||||
|
||||
/**
|
||||
* Name of the event.
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *name;
|
||||
|
||||
@end
|
@ -1,15 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSAbstractLog.h"
|
||||
|
||||
@interface MSLogWithProperties : MSAbstractLog
|
||||
|
||||
/**
|
||||
* Additional key/value pair parameters. [optional]
|
||||
*/
|
||||
@property(nonatomic, strong) NSDictionary<NSString *, NSString *> *properties;
|
||||
|
||||
@end
|
@ -1,119 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface MSPropertyConfigurator : NSObject
|
||||
|
||||
/**
|
||||
* Override the application version.
|
||||
*
|
||||
* @param appVersion New application version for a transmission target.
|
||||
*/
|
||||
- (void)setAppVersion:(nullable NSString *)appVersion;
|
||||
|
||||
/**
|
||||
* Override the application name.
|
||||
*
|
||||
* @param appName New application name for a transmission target.
|
||||
*/
|
||||
- (void)setAppName:(nullable NSString *)appName;
|
||||
|
||||
/**
|
||||
* Override the application locale.
|
||||
*
|
||||
* @param appLocale New application locale for a transmission target.
|
||||
*/
|
||||
- (void)setAppLocale:(nullable NSString *)appLocale;
|
||||
|
||||
/**
|
||||
* Set the user identifier.
|
||||
* The user identifier needs to start with c: or i: or d: or w: prefixes.
|
||||
*
|
||||
* @param userId user identifier.
|
||||
*/
|
||||
- (void)setUserId:(nullable NSString *)userId;
|
||||
|
||||
/**
|
||||
* Set a string event property to be attached to events tracked by this transmission target and its child transmission targets.
|
||||
*
|
||||
* @param propertyValue Property value.
|
||||
* @param propertyKey Property key.
|
||||
*
|
||||
* @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the
|
||||
* properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from
|
||||
* the transmission target itself or its parents.
|
||||
*/
|
||||
- (void)setEventPropertyString:(NSString *)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Set a double event property to be attached to events tracked by this transmission target and its child transmission targets.
|
||||
*
|
||||
* @param propertyValue Property value. Must be finite (`NAN` and `INFINITY` not allowed).
|
||||
* @param propertyKey Property key.
|
||||
*
|
||||
* @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the
|
||||
* properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from
|
||||
* the transmission target itself or its parents.
|
||||
*/
|
||||
- (void)setEventPropertyDouble:(double)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Set a 64-bit integer event property to be attached to events tracked by this transmission target and its child transmission targets.
|
||||
*
|
||||
* @param propertyValue Property value.
|
||||
* @param propertyKey Property key.
|
||||
*
|
||||
* @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the
|
||||
* properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from
|
||||
* the transmission target itself or its parents.
|
||||
*/
|
||||
- (void)setEventPropertyInt64:(int64_t)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Set a boolean event property to be attached to events tracked by this transmission target and its child transmission targets.
|
||||
*
|
||||
* @param propertyValue Property value.
|
||||
* @param propertyKey Property key.
|
||||
*
|
||||
* @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the
|
||||
* properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from
|
||||
* the transmission target itself or its parents.
|
||||
*/
|
||||
- (void)setEventPropertyBool:(BOOL)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Set a date event property to be attached to events tracked by this transmission target and its child transmission targets.
|
||||
*
|
||||
* @param propertyValue Property value.
|
||||
* @param propertyKey Property key.
|
||||
*
|
||||
* @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the
|
||||
* properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from
|
||||
* the transmission target itself or its parents.
|
||||
*/
|
||||
- (void)setEventPropertyDate:(NSDate *)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:));
|
||||
|
||||
/**
|
||||
* Remove an event property from this transmission target.
|
||||
*
|
||||
* @param propertyKey Property key.
|
||||
*
|
||||
* @discussion This won't remove properties with the same name declared in other nested transmission targets.
|
||||
*/
|
||||
- (void)removeEventPropertyForKey:(NSString *)propertyKey NS_SWIFT_NAME(removeEventProperty(forKey:));
|
||||
|
||||
/**
|
||||
* Once called, the App Center SDK will automatically add UIDevice.identifierForVendor to common schema logs.
|
||||
*
|
||||
* @discussion Call this before starting the SDK. This setting is not persisted, so you need to call this when setting up the SDK every
|
||||
* time. If you want to provide a way for users to opt-in or opt-out of this setting, it is on you to persist their choice and configure the
|
||||
* App Center SDK accordingly.
|
||||
*/
|
||||
- (void)collectDeviceId;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@end
|
@ -1,30 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Protocol declaring service logic.
|
||||
*/
|
||||
@protocol MSService <NSObject>
|
||||
|
||||
/**
|
||||
* Enable or disable this service.
|
||||
* The state is persisted in the device's storage across application launches.
|
||||
*
|
||||
* @param isEnabled Whether this service is enabled or not.
|
||||
*
|
||||
* @see isEnabled
|
||||
*/
|
||||
+ (void)setEnabled:(BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Indicates whether this service is enabled.
|
||||
*
|
||||
* @return `YES` if this service is enabled, `NO` if it is not.
|
||||
*
|
||||
* @see setEnabled:
|
||||
*/
|
||||
+ (BOOL)isEnabled;
|
||||
|
||||
@end
|
@ -1,50 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSService.h"
|
||||
|
||||
@protocol MSChannelGroupProtocol;
|
||||
|
||||
/**
|
||||
* Abstraction of services common logic.
|
||||
* This class is intended to be subclassed only not instantiated directly.
|
||||
*/
|
||||
@interface MSServiceAbstract : NSObject <MSService>
|
||||
|
||||
/**
|
||||
* The flag indicates whether the service is started from application or not.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL startedFromApplication;
|
||||
|
||||
/**
|
||||
* Start this service with a channel group. Also sets the flag that indicates that a service has been started.
|
||||
*
|
||||
* @param channelGroup channel group used to persist and send logs.
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
* @param fromApplication indicates whether the service started from an application or not.
|
||||
*/
|
||||
- (void)startWithChannelGroup:(id<MSChannelGroupProtocol>)channelGroup
|
||||
appSecret:(NSString *)appSecret
|
||||
transmissionTargetToken:(NSString *)token
|
||||
fromApplication:(BOOL)fromApplication;
|
||||
|
||||
/**
|
||||
* Update configuration when the service requires to start again. This method should only be called if the service is started from libraries
|
||||
* and then is being started from an application.
|
||||
*
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
*/
|
||||
- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token;
|
||||
|
||||
/**
|
||||
* Checks if the service needs the application secret.
|
||||
*
|
||||
* @return `YES` if the application secret is required, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isAppSecretRequired;
|
||||
|
||||
@end
|
@ -1,9 +0,0 @@
|
||||
framework module AppCenterAnalytics {
|
||||
umbrella header "AppCenterAnalytics.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
|
||||
link framework "Foundation"
|
||||
link framework "UIKit"
|
||||
}
|
Binary file not shown.
@ -1,11 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSCrashHandlerSetupDelegate.h"
|
||||
#import "MSCrashes.h"
|
||||
#import "MSCrashesDelegate.h"
|
||||
#import "MSErrorAttachmentLog+Utility.h"
|
||||
#import "MSErrorAttachmentLog.h"
|
||||
#import "MSWrapperCrashesHelper.h"
|
@ -1,8 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MSAbstractLog : NSObject
|
||||
|
||||
@end
|
@ -1,33 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* This is required for Wrapper SDKs that need to provide custom behavior surrounding the setup of crash handlers.
|
||||
*/
|
||||
@protocol MSCrashHandlerSetupDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Callback method that will be called immediately before crash handlers are set up.
|
||||
*/
|
||||
- (void)willSetUpCrashHandlers;
|
||||
|
||||
/**
|
||||
* Callback method that will be called immediately after crash handlers are set up.
|
||||
*/
|
||||
- (void)didSetUpCrashHandlers;
|
||||
|
||||
/**
|
||||
* Callback method that gets a value indicating whether the SDK should enable an uncaught exception handler.
|
||||
*
|
||||
* @return YES if SDK should enable uncaught exception handler, otherwise NO.
|
||||
*
|
||||
* @discussion Do not register an UncaughtExceptionHandler for Xamarin as we rely on the Xamarin runtime to report NSExceptions. Registering
|
||||
* our own UncaughtExceptionHandler will cause the Xamarin debugger to not work properly (it will not stop for NSExceptions).
|
||||
*/
|
||||
- (BOOL)shouldEnableUncaughtExceptionHandler;
|
||||
|
||||
@end
|
@ -1,159 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import "MSErrorReport.h"
|
||||
#import "MSServiceAbstract.h"
|
||||
|
||||
@class MSCrashesDelegate;
|
||||
|
||||
/**
|
||||
* Custom block that handles the alert that prompts the user whether crash reports need to be processed or not.
|
||||
*
|
||||
* @return Returns YES to discard crash reports, otherwise NO.
|
||||
*/
|
||||
typedef BOOL (^MSUserConfirmationHandler)(NSArray<MSErrorReport *> *_Nonnull errorReports);
|
||||
|
||||
/**
|
||||
* Error Logging status.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, MSErrorLogSetting) {
|
||||
|
||||
/**
|
||||
* Crash reporting is disabled.
|
||||
*/
|
||||
MSErrorLogSettingDisabled = 0,
|
||||
|
||||
/**
|
||||
* User is asked each time before sending error logs.
|
||||
*/
|
||||
MSErrorLogSettingAlwaysAsk = 1,
|
||||
|
||||
/**
|
||||
* Each error log is send automatically.
|
||||
*/
|
||||
MSErrorLogSettingAutoSend = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Crash Manager alert user input.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, MSUserConfirmation) {
|
||||
|
||||
/**
|
||||
* User chose not to send the crash report.
|
||||
*/
|
||||
MSUserConfirmationDontSend = 0,
|
||||
|
||||
/**
|
||||
* User wants the crash report to be sent.
|
||||
*/
|
||||
MSUserConfirmationSend = 1,
|
||||
|
||||
/**
|
||||
* User wants to send all error logs.
|
||||
*/
|
||||
MSUserConfirmationAlways = 2
|
||||
};
|
||||
|
||||
@protocol MSCrashesDelegate;
|
||||
|
||||
@interface MSCrashes : MSServiceAbstract
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Testing Crashes Feature
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Lets the app crash for easy testing of the SDK.
|
||||
*
|
||||
* The best way to use this is to trigger the crash with a button action.
|
||||
*
|
||||
* Make sure not to let the app crash in `applicationDidFinishLaunching` or any other startup method! Since otherwise the app would crash
|
||||
* before the SDK could process it.
|
||||
*
|
||||
* Note that our SDK provides support for handling crashes that happen early on startup. Check the documentation for more information on how
|
||||
* to use this.
|
||||
*
|
||||
* If the SDK detects an App Store environment, it will _NOT_ cause the app to crash!
|
||||
*/
|
||||
+ (void)generateTestCrash;
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Helpers
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check if the app has crashed in the last session.
|
||||
*
|
||||
* @return Returns YES is the app has crashed in the last session.
|
||||
*/
|
||||
+ (BOOL)hasCrashedInLastSession;
|
||||
|
||||
/**
|
||||
* Check if the app received memory warning in the last session.
|
||||
*
|
||||
* @return Returns YES is the app received memory warning in the last session.
|
||||
*/
|
||||
+ (BOOL)hasReceivedMemoryWarningInLastSession;
|
||||
|
||||
/**
|
||||
* Provides details about the crash that occurred in the last app session
|
||||
*/
|
||||
+ (nullable MSErrorReport *)lastSessionCrashReport;
|
||||
|
||||
///-----------------------------------------------------------------------------
|
||||
/// @name Configuration
|
||||
///-----------------------------------------------------------------------------
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
/**
|
||||
* Disable the Mach exception server.
|
||||
*
|
||||
* By default, the SDK uses the Mach exception handler to catch fatal signals, e.g. stack overflows, via a Mach exception server. If you
|
||||
* want to disable the Mach exception handler, you should call this method _BEFORE_ starting the SDK. Your typical setup code would look
|
||||
* like this:
|
||||
*
|
||||
* `[MSCrashes disableMachExceptionHandler]`;
|
||||
* `[MSAppCenter start:@"YOUR_APP_ID" withServices:@[[MSCrashes class]]];`
|
||||
*
|
||||
* or if you are using Swift:
|
||||
*
|
||||
* `MSCrashes.disableMachExceptionHandler()`
|
||||
* `MSAppCenter.start("YOUR_APP_ID", withServices: [MSAnalytics.self, MSCrashes.self])`
|
||||
*
|
||||
* tvOS does not support the Mach exception handler, thus crashes that are caused by stack overflows cannot be detected. As a result,
|
||||
* disabling the Mach exception server is not available in the tvOS SDK.
|
||||
*
|
||||
* @discussion It can be useful to disable the Mach exception handler when you are debugging the Crashes service while developing,
|
||||
* especially when you attach the debugger to your application after launch.
|
||||
*/
|
||||
+ (void)disableMachExceptionHandler;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Set the delegate
|
||||
* Defines the class that implements the optional protocol `MSCrashesDelegate`.
|
||||
*
|
||||
* @see MSCrashesDelegate
|
||||
*/
|
||||
+ (void)setDelegate:(_Nullable id<MSCrashesDelegate>)delegate;
|
||||
|
||||
/**
|
||||
* Set a user confirmation handler that is invoked right before processing crash reports to determine whether sending crash reports or not.
|
||||
*
|
||||
* @param userConfirmationHandler A handler for user confirmation.
|
||||
*
|
||||
* @see MSUserConfirmationHandler
|
||||
*/
|
||||
+ (void)setUserConfirmationHandler:(_Nullable MSUserConfirmationHandler)userConfirmationHandler;
|
||||
|
||||
/**
|
||||
* Notify SDK with a confirmation to handle the crash report.
|
||||
*
|
||||
* @param userConfirmation A user confirmation.
|
||||
*
|
||||
* @see MSUserConfirmation.
|
||||
*/
|
||||
+ (void)notifyWithUserConfirmation:(MSUserConfirmation)userConfirmation;
|
||||
|
||||
@end
|
@ -1,65 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MSCrashes;
|
||||
@class MSErrorReport;
|
||||
@class MSErrorAttachmentLog;
|
||||
|
||||
@protocol MSCrashesDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Callback method that will be called before processing errors.
|
||||
*
|
||||
* @param crashes The instance of MSCrashes.
|
||||
* @param errorReport The errorReport that will be sent.
|
||||
*
|
||||
* @discussion Crashes will send logs to the server or discard/delete logs based on this method's return value.
|
||||
*/
|
||||
- (BOOL)crashes:(MSCrashes *)crashes shouldProcessErrorReport:(MSErrorReport *)errorReport;
|
||||
|
||||
/**
|
||||
* Callback method that will be called before each error will be send to the server.
|
||||
*
|
||||
* @param crashes The instance of MSCrashes.
|
||||
* @param errorReport The errorReport that will be sent.
|
||||
*
|
||||
* @discussion Use this callback to display custom UI while crashes are sent to the server.
|
||||
*/
|
||||
- (void)crashes:(MSCrashes *)crashes willSendErrorReport:(MSErrorReport *)errorReport;
|
||||
|
||||
/**
|
||||
* Callback method that will be called in case the SDK was unable to send an error report to the server.
|
||||
*
|
||||
* @param crashes The instance of MSCrashes.
|
||||
* @param errorReport The errorReport that App Center sent.
|
||||
*
|
||||
* @discussion Use this method to hide your custom UI.
|
||||
*/
|
||||
- (void)crashes:(MSCrashes *)crashes didSucceedSendingErrorReport:(MSErrorReport *)errorReport;
|
||||
|
||||
/**
|
||||
* Callback method that will be called in case the SDK was unable to send an error report to the server.
|
||||
*
|
||||
* @param crashes The instance of MSCrashes.
|
||||
* @param errorReport The errorReport that App Center tried to send.
|
||||
* @param error The error that occurred.
|
||||
*/
|
||||
- (void)crashes:(MSCrashes *)crashes didFailSendingErrorReport:(MSErrorReport *)errorReport withError:(NSError *)error;
|
||||
|
||||
/**
|
||||
* Method to get the attachments associated to an error report.
|
||||
*
|
||||
* @param crashes The instance of MSCrashes.
|
||||
* @param errorReport The errorReport associated with the returned attachments.
|
||||
*
|
||||
* @return The attachments associated with the given error report or nil if the error report doesn't have any attachments.
|
||||
*
|
||||
* @discussion Implement this method if you want attachments to the given error report.
|
||||
*/
|
||||
- (NSArray<MSErrorAttachmentLog *> *)attachmentsWithCrashes:(MSCrashes *)crashes forErrorReport:(MSErrorReport *)errorReport;
|
||||
|
||||
@end
|
@ -1,32 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import "MSErrorAttachmentLog.h"
|
||||
|
||||
// Exporting symbols for category.
|
||||
extern NSString *MSMSErrorLogAttachmentLogUtilityCategory;
|
||||
|
||||
@interface MSErrorAttachmentLog (Utility)
|
||||
|
||||
/**
|
||||
* Create an attachment with a given filename and text.
|
||||
*
|
||||
* @param filename The filename the attachment should get. If nil will get an automatically generated filename.
|
||||
* @param text The attachment text.
|
||||
*
|
||||
* @return An instance of `MSErrorAttachmentLog`.
|
||||
*/
|
||||
+ (MSErrorAttachmentLog *)attachmentWithText:(NSString *)text filename:(NSString *)filename;
|
||||
|
||||
/**
|
||||
* Create an attachment with a given filename and `NSData` object.
|
||||
*
|
||||
* @param filename The filename the attachment should get. If nil will get an automatically generated filename.
|
||||
* @param data The attachment data as NSData.
|
||||
* @param contentType The content type of your data as MIME type.
|
||||
*
|
||||
* @return An instance of `MSErrorAttachmentLog`.
|
||||
*/
|
||||
+ (MSErrorAttachmentLog *)attachmentWithBinary:(NSData *)data filename:(NSString *)filename contentType:(NSString *)contentType;
|
||||
|
||||
@end
|
@ -1,49 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSAbstractLog.h"
|
||||
|
||||
/**
|
||||
* Error attachment log.
|
||||
*/
|
||||
@interface MSErrorAttachmentLog : MSAbstractLog
|
||||
|
||||
/**
|
||||
* Content type (text/plain for text).
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *contentType;
|
||||
|
||||
/**
|
||||
* File name.
|
||||
*/
|
||||
@property(nonatomic, copy) NSString *filename;
|
||||
|
||||
/**
|
||||
* The attachment data.
|
||||
*/
|
||||
@property(nonatomic, copy) NSData *data;
|
||||
|
||||
/**
|
||||
* Initialize an attachment with a given filename and `NSData` object.
|
||||
*
|
||||
* @param filename The filename the attachment should get. If nil will get an automatically generated filename.
|
||||
* @param data The attachment data as `NSData`.
|
||||
* @param contentType The content type of your data as MIME type.
|
||||
*
|
||||
* @return An instance of `MSErrorAttachmentLog`.
|
||||
*/
|
||||
- (instancetype)initWithFilename:(NSString *)filename attachmentBinary:(NSData *)data contentType:(NSString *)contentType;
|
||||
|
||||
/**
|
||||
* Initialize an attachment with a given filename and text.
|
||||
*
|
||||
* @param filename The filename the attachment should get. If nil will get an automatically generated filename.
|
||||
* @param text The attachment text.
|
||||
*
|
||||
* @return An instance of `MSErrorAttachmentLog`.
|
||||
*/
|
||||
- (instancetype)initWithFilename:(NSString *)filename attachmentText:(NSString *)text;
|
||||
|
||||
@end
|
@ -1,78 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MSDevice;
|
||||
|
||||
@interface MSErrorReport : NSObject
|
||||
|
||||
/**
|
||||
* UUID for the crash report.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *incidentIdentifier;
|
||||
|
||||
/**
|
||||
* UUID for the app installation on the device.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *reporterKey;
|
||||
|
||||
/**
|
||||
* Signal that caused the crash.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *signal;
|
||||
|
||||
/**
|
||||
* Exception name that triggered the crash, nil if the crash was not caused by an exception.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *exceptionName;
|
||||
|
||||
/**
|
||||
* Exception reason, nil if the crash was not caused by an exception.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *exceptionReason;
|
||||
|
||||
/**
|
||||
* Date and time the app started, nil if unknown.
|
||||
*/
|
||||
@property(nonatomic, readonly, strong) NSDate *appStartTime;
|
||||
|
||||
/**
|
||||
* Date and time the error occurred, nil if unknown
|
||||
*/
|
||||
@property(nonatomic, readonly, strong) NSDate *appErrorTime;
|
||||
|
||||
/**
|
||||
* Device information of the app when it crashed.
|
||||
*/
|
||||
@property(nonatomic, readonly, strong) MSDevice *device;
|
||||
|
||||
/**
|
||||
* Identifier of the app process that crashed.
|
||||
*/
|
||||
@property(nonatomic, readonly, assign) NSUInteger appProcessIdentifier;
|
||||
|
||||
// TODO Please review this doc that contains method name which doesn't exist.
|
||||
/**
|
||||
* Indicates if the app was killed while being in foreground from the iOS.
|
||||
*
|
||||
* If `[MSCrashes enableAppNotTerminatingCleanlyDetection]` is enabled, use this on startup to check if the app starts the first time after
|
||||
* it was killed by iOS in the previous session.
|
||||
*
|
||||
* This can happen if it consumed too much memory or the watchdog killed the app because it took too long to startup or blocks the main
|
||||
* thread for too long, or other reasons. See Apple documentation:
|
||||
* https://developer.apple.com/library/ios/qa/qa1693/_index.html.
|
||||
*
|
||||
* See `[MSCrashes enableAppNotTerminatingCleanlyDetection]` for more details about which kind of kills can be detected.
|
||||
*
|
||||
* @return YES if the details represent an app kill instead of a crash
|
||||
*
|
||||
* @warning This property only has a correct value, once `[BITHockeyManager startManager]` was invoked! In addition, it is automatically
|
||||
* disabled while a debugger session is active!
|
||||
*
|
||||
* @see `[MSCrashes enableAppNotTerminatingCleanlyDetection]`
|
||||
* @see `[MSCrashes didReceiveMemoryWarningInLastSession]`
|
||||
*/
|
||||
- (BOOL)isAppKill;
|
||||
|
||||
@end
|
@ -1,30 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Protocol declaring service logic.
|
||||
*/
|
||||
@protocol MSService <NSObject>
|
||||
|
||||
/**
|
||||
* Enable or disable this service.
|
||||
* The state is persisted in the device's storage across application launches.
|
||||
*
|
||||
* @param isEnabled Whether this service is enabled or not.
|
||||
*
|
||||
* @see isEnabled
|
||||
*/
|
||||
+ (void)setEnabled:(BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Indicates whether this service is enabled.
|
||||
*
|
||||
* @return `YES` if this service is enabled, `NO` if it is not.
|
||||
*
|
||||
* @see setEnabled:
|
||||
*/
|
||||
+ (BOOL)isEnabled;
|
||||
|
||||
@end
|
@ -1,50 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSService.h"
|
||||
|
||||
@protocol MSChannelGroupProtocol;
|
||||
|
||||
/**
|
||||
* Abstraction of services common logic.
|
||||
* This class is intended to be subclassed only not instantiated directly.
|
||||
*/
|
||||
@interface MSServiceAbstract : NSObject <MSService>
|
||||
|
||||
/**
|
||||
* The flag indicates whether the service is started from application or not.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL startedFromApplication;
|
||||
|
||||
/**
|
||||
* Start this service with a channel group. Also sets the flag that indicates that a service has been started.
|
||||
*
|
||||
* @param channelGroup channel group used to persist and send logs.
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
* @param fromApplication indicates whether the service started from an application or not.
|
||||
*/
|
||||
- (void)startWithChannelGroup:(id<MSChannelGroupProtocol>)channelGroup
|
||||
appSecret:(NSString *)appSecret
|
||||
transmissionTargetToken:(NSString *)token
|
||||
fromApplication:(BOOL)fromApplication;
|
||||
|
||||
/**
|
||||
* Update configuration when the service requires to start again. This method should only be called if the service is started from libraries
|
||||
* and then is being started from an application.
|
||||
*
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
*/
|
||||
- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token;
|
||||
|
||||
/**
|
||||
* Checks if the service needs the application secret.
|
||||
*
|
||||
* @return `YES` if the application secret is required, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isAppSecretRequired;
|
||||
|
||||
@end
|
@ -1,61 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSCrashHandlerSetupDelegate.h"
|
||||
|
||||
@class MSErrorReport;
|
||||
@class MSErrorAttachmentLog;
|
||||
|
||||
/**
|
||||
* This general class allows wrappers to supplement the Crashes SDK with their own behavior.
|
||||
*/
|
||||
@interface MSWrapperCrashesHelper : NSObject
|
||||
|
||||
/**
|
||||
* Sets the crash handler setup delegate.
|
||||
*
|
||||
* @param delegate The delegate to set.
|
||||
*/
|
||||
+ (void)setCrashHandlerSetupDelegate:(id<MSCrashHandlerSetupDelegate>)delegate;
|
||||
|
||||
/**
|
||||
* Gets the crash handler setup delegate.
|
||||
*
|
||||
* @return The delegate being used by Crashes.
|
||||
*/
|
||||
+ (id<MSCrashHandlerSetupDelegate>)getCrashHandlerSetupDelegate;
|
||||
|
||||
/**
|
||||
* Enables or disables automatic crash processing.
|
||||
*
|
||||
* @param automaticProcessing Passing NO causes SDK not to send reports immediately, even if "Always Send" is true.
|
||||
*/
|
||||
+ (void)setAutomaticProcessing:(BOOL)automaticProcessing;
|
||||
|
||||
/**
|
||||
* Gets a list of unprocessed crash reports. Will block until the service starts.
|
||||
*
|
||||
* @return An array of unprocessed error reports.
|
||||
*/
|
||||
+ (NSArray<MSErrorReport *> *)unprocessedCrashReports;
|
||||
|
||||
/**
|
||||
* Resumes processing for a given subset of the unprocessed reports.
|
||||
*
|
||||
* @param filteredIds An array containing the errorId/incidentIdentifier of each report that should be sent.
|
||||
*
|
||||
* @return YES if should "Always Send" is true.
|
||||
*/
|
||||
+ (BOOL)sendCrashReportsOrAwaitUserConfirmationForFilteredIds:(NSArray<NSString *> *)filteredIds;
|
||||
|
||||
/**
|
||||
* Sends error attachments for a particular error report.
|
||||
*
|
||||
* @param errorAttachments An array of error attachments that should be sent.
|
||||
* @param incidentIdentifier The identifier of the error report that the attachments will be associated with.
|
||||
*/
|
||||
+ (void)sendErrorAttachments:(NSArray<MSErrorAttachmentLog *> *)errorAttachments withIncidentIdentifier:(NSString *)incidentIdentifier;
|
||||
|
||||
@end
|
@ -1,10 +0,0 @@
|
||||
framework module AppCenterCrashes {
|
||||
umbrella header "AppCenterCrashes.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
|
||||
link framework "Foundation"
|
||||
link "c++"
|
||||
link "z"
|
||||
}
|
Binary file not shown.
@ -1,8 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSPush.h"
|
||||
#import "MSPushDelegate.h"
|
||||
#import "MSPushNotification.h"
|
@ -1,49 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import "MSServiceAbstract.h"
|
||||
#import "MSPushDelegate.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* App Center push service.
|
||||
*/
|
||||
@interface MSPush : MSServiceAbstract
|
||||
|
||||
/**
|
||||
* Callback for successful registration with push token.
|
||||
*
|
||||
* @param deviceToken The device token for remote notifications.
|
||||
*/
|
||||
+ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
|
||||
|
||||
/**
|
||||
* Callback for unsuccessful registration with error.
|
||||
*
|
||||
* @param error Error of unsuccessful registration.
|
||||
*/
|
||||
+ (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
|
||||
|
||||
/**
|
||||
* Callback for notification with user info.
|
||||
*
|
||||
* @param userInfo The user info for the remote notification.
|
||||
*
|
||||
* @return YES if the notification was sent via App Center.
|
||||
*/
|
||||
+ (BOOL)didReceiveRemoteNotification:(NSDictionary *)userInfo;
|
||||
|
||||
/**
|
||||
* Set the delegate.
|
||||
* Defines the class that implements the optional protocol `MSPushDelegate`.
|
||||
*
|
||||
* @param delegate The delegate.
|
||||
*
|
||||
* @see MSPushDelegate
|
||||
*/
|
||||
+ (void)setDelegate:(nullable id<MSPushDelegate>)delegate;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@end
|
@ -1,22 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MSPush;
|
||||
@class MSPushNotification;
|
||||
|
||||
@protocol MSPushDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Callback method that will be called whenever a push notification is clicked from notification center or a notification is received in
|
||||
* foreground.
|
||||
*
|
||||
* @param push The instance of MSPush.
|
||||
* @param pushNotification The push notification details.
|
||||
*/
|
||||
- (void)push:(MSPush *)push didReceivePushNotification:(MSPushNotification *)pushNotification;
|
||||
|
||||
@end
|
@ -1,23 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface MSPushNotification : NSObject
|
||||
|
||||
/**
|
||||
* Notification title.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *title;
|
||||
|
||||
/**
|
||||
* Notification message.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSString *message;
|
||||
|
||||
/**
|
||||
* Custom data for the notification.
|
||||
*/
|
||||
@property(nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *customData;
|
||||
|
||||
@end
|
@ -1,30 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Protocol declaring service logic.
|
||||
*/
|
||||
@protocol MSService <NSObject>
|
||||
|
||||
/**
|
||||
* Enable or disable this service.
|
||||
* The state is persisted in the device's storage across application launches.
|
||||
*
|
||||
* @param isEnabled Whether this service is enabled or not.
|
||||
*
|
||||
* @see isEnabled
|
||||
*/
|
||||
+ (void)setEnabled:(BOOL)isEnabled;
|
||||
|
||||
/**
|
||||
* Indicates whether this service is enabled.
|
||||
*
|
||||
* @return `YES` if this service is enabled, `NO` if it is not.
|
||||
*
|
||||
* @see setEnabled:
|
||||
*/
|
||||
+ (BOOL)isEnabled;
|
||||
|
||||
@end
|
@ -1,50 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "MSService.h"
|
||||
|
||||
@protocol MSChannelGroupProtocol;
|
||||
|
||||
/**
|
||||
* Abstraction of services common logic.
|
||||
* This class is intended to be subclassed only not instantiated directly.
|
||||
*/
|
||||
@interface MSServiceAbstract : NSObject <MSService>
|
||||
|
||||
/**
|
||||
* The flag indicates whether the service is started from application or not.
|
||||
*/
|
||||
@property(nonatomic, assign) BOOL startedFromApplication;
|
||||
|
||||
/**
|
||||
* Start this service with a channel group. Also sets the flag that indicates that a service has been started.
|
||||
*
|
||||
* @param channelGroup channel group used to persist and send logs.
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
* @param fromApplication indicates whether the service started from an application or not.
|
||||
*/
|
||||
- (void)startWithChannelGroup:(id<MSChannelGroupProtocol>)channelGroup
|
||||
appSecret:(NSString *)appSecret
|
||||
transmissionTargetToken:(NSString *)token
|
||||
fromApplication:(BOOL)fromApplication;
|
||||
|
||||
/**
|
||||
* Update configuration when the service requires to start again. This method should only be called if the service is started from libraries
|
||||
* and then is being started from an application.
|
||||
*
|
||||
* @param appSecret app secret for the SDK.
|
||||
* @param token default transmission target token for this service.
|
||||
*/
|
||||
- (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTargetToken:(NSString *)token;
|
||||
|
||||
/**
|
||||
* Checks if the service needs the application secret.
|
||||
*
|
||||
* @return `YES` if the application secret is required, `NO` otherwise.
|
||||
*/
|
||||
- (BOOL)isAppSecretRequired;
|
||||
|
||||
@end
|
@ -1,10 +0,0 @@
|
||||
framework module AppCenterPush {
|
||||
umbrella header "AppCenterPush.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
|
||||
link framework "Foundation"
|
||||
link framework "UIKit"
|
||||
link framework "UserNotifications"
|
||||
}
|
25
ios/Pods/AppCenter/AppCenter-SDK-Apple/iOS/LICENSE
generated
25
ios/Pods/AppCenter/AppCenter-SDK-Apple/iOS/LICENSE
generated
@ -1,25 +0,0 @@
|
||||
Visual Studio App Center SDK for Apple platforms
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
Binary file not shown.
@ -1,24 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MSWrapperSdk;
|
||||
|
||||
@interface AppCenterReactNativeShared : NSObject
|
||||
|
||||
+ (void)setAppSecret:(NSString *)secret;
|
||||
|
||||
+ (NSString *)getAppSecret;
|
||||
|
||||
+ (void)configureAppCenter;
|
||||
|
||||
+ (MSWrapperSdk *)getWrapperSdk;
|
||||
|
||||
+ (void)setWrapperSdk:(MSWrapperSdk *)sdk;
|
||||
|
||||
+ (void)setStartAutomatically:(BOOL)shouldStartAutomatically;
|
||||
|
||||
+ (NSDictionary *)getConfiguration;
|
||||
|
||||
@end
|
@ -1,8 +0,0 @@
|
||||
framework module AppCenterReactNativeShared {
|
||||
umbrella header "AppCenterReactNativeShared.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
|
||||
link framework "Foundation"
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
Visual Studio App Center Plugin for React Native
|
||||
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
ios/Pods/DoubleConversion/LICENSE
generated
26
ios/Pods/DoubleConversion/LICENSE
generated
@ -1,26 +0,0 @@
|
||||
Copyright 2006-2011, the V8 project authors. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
54
ios/Pods/DoubleConversion/README
generated
54
ios/Pods/DoubleConversion/README
generated
@ -1,54 +0,0 @@
|
||||
http://code.google.com/p/double-conversion
|
||||
|
||||
This project (double-conversion) provides binary-decimal and decimal-binary
|
||||
routines for IEEE doubles.
|
||||
|
||||
The library consists of efficient conversion routines that have been extracted
|
||||
from the V8 JavaScript engine. The code has been refactored and improved so that
|
||||
it can be used more easily in other projects.
|
||||
|
||||
There is extensive documentation in src/double-conversion.h. Other examples can
|
||||
be found in test/cctest/test-conversions.cc.
|
||||
|
||||
|
||||
Building
|
||||
========
|
||||
|
||||
This library can be built with scons [0] or cmake [1].
|
||||
The checked-in Makefile simply forwards to scons, and provides a
|
||||
shortcut to run all tests:
|
||||
|
||||
make
|
||||
make test
|
||||
|
||||
Scons
|
||||
-----
|
||||
|
||||
The easiest way to install this library is to use `scons`. It builds
|
||||
the static and shared library, and is set up to install those at the
|
||||
correct locations:
|
||||
|
||||
scons install
|
||||
|
||||
Use the `DESTDIR` option to change the target directory:
|
||||
|
||||
scons DESTDIR=alternative_directory install
|
||||
|
||||
Cmake
|
||||
-----
|
||||
|
||||
To use cmake run `cmake .` in the root directory. This overwrites the
|
||||
existing Makefile.
|
||||
|
||||
Use `-DBUILD_SHARED_LIBS=ON` to enable the compilation of shared libraries.
|
||||
Note that this disables static libraries. There is currently no way to
|
||||
build both libraries at the same time with cmake.
|
||||
|
||||
Use `-DBUILD_TESTING=ON` to build the test executable.
|
||||
|
||||
cmake . -DBUILD_TESTING=ON
|
||||
make
|
||||
test/cctest/cctest --list | tr -d '<' | xargs test/cctest/cctest
|
||||
|
||||
[0]: http://www.scons.org
|
||||
[1]: http://www.cmake.org
|
@ -1,641 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "bignum-dtoa.h"
|
||||
|
||||
#include "bignum.h"
|
||||
#include "ieee.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
static int NormalizedExponent(uint64_t significand, int exponent) {
|
||||
ASSERT(significand != 0);
|
||||
while ((significand & Double::kHiddenBit) == 0) {
|
||||
significand = significand << 1;
|
||||
exponent = exponent - 1;
|
||||
}
|
||||
return exponent;
|
||||
}
|
||||
|
||||
|
||||
// Forward declarations:
|
||||
// Returns an estimation of k such that 10^(k-1) <= v < 10^k.
|
||||
static int EstimatePower(int exponent);
|
||||
// Computes v / 10^estimated_power exactly, as a ratio of two bignums, numerator
|
||||
// and denominator.
|
||||
static void InitialScaledStartValues(uint64_t significand,
|
||||
int exponent,
|
||||
bool lower_boundary_is_closer,
|
||||
int estimated_power,
|
||||
bool need_boundary_deltas,
|
||||
Bignum* numerator,
|
||||
Bignum* denominator,
|
||||
Bignum* delta_minus,
|
||||
Bignum* delta_plus);
|
||||
// Multiplies numerator/denominator so that its values lies in the range 1-10.
|
||||
// Returns decimal_point s.t.
|
||||
// v = numerator'/denominator' * 10^(decimal_point-1)
|
||||
// where numerator' and denominator' are the values of numerator and
|
||||
// denominator after the call to this function.
|
||||
static void FixupMultiply10(int estimated_power, bool is_even,
|
||||
int* decimal_point,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Bignum* delta_minus, Bignum* delta_plus);
|
||||
// Generates digits from the left to the right and stops when the generated
|
||||
// digits yield the shortest decimal representation of v.
|
||||
static void GenerateShortestDigits(Bignum* numerator, Bignum* denominator,
|
||||
Bignum* delta_minus, Bignum* delta_plus,
|
||||
bool is_even,
|
||||
Vector<char> buffer, int* length);
|
||||
// Generates 'requested_digits' after the decimal point.
|
||||
static void BignumToFixed(int requested_digits, int* decimal_point,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Vector<char>(buffer), int* length);
|
||||
// Generates 'count' digits of numerator/denominator.
|
||||
// Once 'count' digits have been produced rounds the result depending on the
|
||||
// remainder (remainders of exactly .5 round upwards). Might update the
|
||||
// decimal_point when rounding up (for example for 0.9999).
|
||||
static void GenerateCountedDigits(int count, int* decimal_point,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Vector<char>(buffer), int* length);
|
||||
|
||||
|
||||
void BignumDtoa(double v, BignumDtoaMode mode, int requested_digits,
|
||||
Vector<char> buffer, int* length, int* decimal_point) {
|
||||
ASSERT(v > 0);
|
||||
ASSERT(!Double(v).IsSpecial());
|
||||
uint64_t significand;
|
||||
int exponent;
|
||||
bool lower_boundary_is_closer;
|
||||
if (mode == BIGNUM_DTOA_SHORTEST_SINGLE) {
|
||||
float f = static_cast<float>(v);
|
||||
ASSERT(f == v);
|
||||
significand = Single(f).Significand();
|
||||
exponent = Single(f).Exponent();
|
||||
lower_boundary_is_closer = Single(f).LowerBoundaryIsCloser();
|
||||
} else {
|
||||
significand = Double(v).Significand();
|
||||
exponent = Double(v).Exponent();
|
||||
lower_boundary_is_closer = Double(v).LowerBoundaryIsCloser();
|
||||
}
|
||||
bool need_boundary_deltas =
|
||||
(mode == BIGNUM_DTOA_SHORTEST || mode == BIGNUM_DTOA_SHORTEST_SINGLE);
|
||||
|
||||
bool is_even = (significand & 1) == 0;
|
||||
int normalized_exponent = NormalizedExponent(significand, exponent);
|
||||
// estimated_power might be too low by 1.
|
||||
int estimated_power = EstimatePower(normalized_exponent);
|
||||
|
||||
// Shortcut for Fixed.
|
||||
// The requested digits correspond to the digits after the point. If the
|
||||
// number is much too small, then there is no need in trying to get any
|
||||
// digits.
|
||||
if (mode == BIGNUM_DTOA_FIXED && -estimated_power - 1 > requested_digits) {
|
||||
buffer[0] = '\0';
|
||||
*length = 0;
|
||||
// Set decimal-point to -requested_digits. This is what Gay does.
|
||||
// Note that it should not have any effect anyways since the string is
|
||||
// empty.
|
||||
*decimal_point = -requested_digits;
|
||||
return;
|
||||
}
|
||||
|
||||
Bignum numerator;
|
||||
Bignum denominator;
|
||||
Bignum delta_minus;
|
||||
Bignum delta_plus;
|
||||
// Make sure the bignum can grow large enough. The smallest double equals
|
||||
// 4e-324. In this case the denominator needs fewer than 324*4 binary digits.
|
||||
// The maximum double is 1.7976931348623157e308 which needs fewer than
|
||||
// 308*4 binary digits.
|
||||
ASSERT(Bignum::kMaxSignificantBits >= 324*4);
|
||||
InitialScaledStartValues(significand, exponent, lower_boundary_is_closer,
|
||||
estimated_power, need_boundary_deltas,
|
||||
&numerator, &denominator,
|
||||
&delta_minus, &delta_plus);
|
||||
// We now have v = (numerator / denominator) * 10^estimated_power.
|
||||
FixupMultiply10(estimated_power, is_even, decimal_point,
|
||||
&numerator, &denominator,
|
||||
&delta_minus, &delta_plus);
|
||||
// We now have v = (numerator / denominator) * 10^(decimal_point-1), and
|
||||
// 1 <= (numerator + delta_plus) / denominator < 10
|
||||
switch (mode) {
|
||||
case BIGNUM_DTOA_SHORTEST:
|
||||
case BIGNUM_DTOA_SHORTEST_SINGLE:
|
||||
GenerateShortestDigits(&numerator, &denominator,
|
||||
&delta_minus, &delta_plus,
|
||||
is_even, buffer, length);
|
||||
break;
|
||||
case BIGNUM_DTOA_FIXED:
|
||||
BignumToFixed(requested_digits, decimal_point,
|
||||
&numerator, &denominator,
|
||||
buffer, length);
|
||||
break;
|
||||
case BIGNUM_DTOA_PRECISION:
|
||||
GenerateCountedDigits(requested_digits, decimal_point,
|
||||
&numerator, &denominator,
|
||||
buffer, length);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
buffer[*length] = '\0';
|
||||
}
|
||||
|
||||
|
||||
// The procedure starts generating digits from the left to the right and stops
|
||||
// when the generated digits yield the shortest decimal representation of v. A
|
||||
// decimal representation of v is a number lying closer to v than to any other
|
||||
// double, so it converts to v when read.
|
||||
//
|
||||
// This is true if d, the decimal representation, is between m- and m+, the
|
||||
// upper and lower boundaries. d must be strictly between them if !is_even.
|
||||
// m- := (numerator - delta_minus) / denominator
|
||||
// m+ := (numerator + delta_plus) / denominator
|
||||
//
|
||||
// Precondition: 0 <= (numerator+delta_plus) / denominator < 10.
|
||||
// If 1 <= (numerator+delta_plus) / denominator < 10 then no leading 0 digit
|
||||
// will be produced. This should be the standard precondition.
|
||||
static void GenerateShortestDigits(Bignum* numerator, Bignum* denominator,
|
||||
Bignum* delta_minus, Bignum* delta_plus,
|
||||
bool is_even,
|
||||
Vector<char> buffer, int* length) {
|
||||
// Small optimization: if delta_minus and delta_plus are the same just reuse
|
||||
// one of the two bignums.
|
||||
if (Bignum::Equal(*delta_minus, *delta_plus)) {
|
||||
delta_plus = delta_minus;
|
||||
}
|
||||
*length = 0;
|
||||
for (;;) {
|
||||
uint16_t digit;
|
||||
digit = numerator->DivideModuloIntBignum(*denominator);
|
||||
ASSERT(digit <= 9); // digit is a uint16_t and therefore always positive.
|
||||
// digit = numerator / denominator (integer division).
|
||||
// numerator = numerator % denominator.
|
||||
buffer[(*length)++] = static_cast<char>(digit + '0');
|
||||
|
||||
// Can we stop already?
|
||||
// If the remainder of the division is less than the distance to the lower
|
||||
// boundary we can stop. In this case we simply round down (discarding the
|
||||
// remainder).
|
||||
// Similarly we test if we can round up (using the upper boundary).
|
||||
bool in_delta_room_minus;
|
||||
bool in_delta_room_plus;
|
||||
if (is_even) {
|
||||
in_delta_room_minus = Bignum::LessEqual(*numerator, *delta_minus);
|
||||
} else {
|
||||
in_delta_room_minus = Bignum::Less(*numerator, *delta_minus);
|
||||
}
|
||||
if (is_even) {
|
||||
in_delta_room_plus =
|
||||
Bignum::PlusCompare(*numerator, *delta_plus, *denominator) >= 0;
|
||||
} else {
|
||||
in_delta_room_plus =
|
||||
Bignum::PlusCompare(*numerator, *delta_plus, *denominator) > 0;
|
||||
}
|
||||
if (!in_delta_room_minus && !in_delta_room_plus) {
|
||||
// Prepare for next iteration.
|
||||
numerator->Times10();
|
||||
delta_minus->Times10();
|
||||
// We optimized delta_plus to be equal to delta_minus (if they share the
|
||||
// same value). So don't multiply delta_plus if they point to the same
|
||||
// object.
|
||||
if (delta_minus != delta_plus) {
|
||||
delta_plus->Times10();
|
||||
}
|
||||
} else if (in_delta_room_minus && in_delta_room_plus) {
|
||||
// Let's see if 2*numerator < denominator.
|
||||
// If yes, then the next digit would be < 5 and we can round down.
|
||||
int compare = Bignum::PlusCompare(*numerator, *numerator, *denominator);
|
||||
if (compare < 0) {
|
||||
// Remaining digits are less than .5. -> Round down (== do nothing).
|
||||
} else if (compare > 0) {
|
||||
// Remaining digits are more than .5 of denominator. -> Round up.
|
||||
// Note that the last digit could not be a '9' as otherwise the whole
|
||||
// loop would have stopped earlier.
|
||||
// We still have an assert here in case the preconditions were not
|
||||
// satisfied.
|
||||
ASSERT(buffer[(*length) - 1] != '9');
|
||||
buffer[(*length) - 1]++;
|
||||
} else {
|
||||
// Halfway case.
|
||||
// TODO(floitsch): need a way to solve half-way cases.
|
||||
// For now let's round towards even (since this is what Gay seems to
|
||||
// do).
|
||||
|
||||
if ((buffer[(*length) - 1] - '0') % 2 == 0) {
|
||||
// Round down => Do nothing.
|
||||
} else {
|
||||
ASSERT(buffer[(*length) - 1] != '9');
|
||||
buffer[(*length) - 1]++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if (in_delta_room_minus) {
|
||||
// Round down (== do nothing).
|
||||
return;
|
||||
} else { // in_delta_room_plus
|
||||
// Round up.
|
||||
// Note again that the last digit could not be '9' since this would have
|
||||
// stopped the loop earlier.
|
||||
// We still have an ASSERT here, in case the preconditions were not
|
||||
// satisfied.
|
||||
ASSERT(buffer[(*length) -1] != '9');
|
||||
buffer[(*length) - 1]++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Let v = numerator / denominator < 10.
|
||||
// Then we generate 'count' digits of d = x.xxxxx... (without the decimal point)
|
||||
// from left to right. Once 'count' digits have been produced we decide wether
|
||||
// to round up or down. Remainders of exactly .5 round upwards. Numbers such
|
||||
// as 9.999999 propagate a carry all the way, and change the
|
||||
// exponent (decimal_point), when rounding upwards.
|
||||
static void GenerateCountedDigits(int count, int* decimal_point,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Vector<char> buffer, int* length) {
|
||||
ASSERT(count >= 0);
|
||||
for (int i = 0; i < count - 1; ++i) {
|
||||
uint16_t digit;
|
||||
digit = numerator->DivideModuloIntBignum(*denominator);
|
||||
ASSERT(digit <= 9); // digit is a uint16_t and therefore always positive.
|
||||
// digit = numerator / denominator (integer division).
|
||||
// numerator = numerator % denominator.
|
||||
buffer[i] = static_cast<char>(digit + '0');
|
||||
// Prepare for next iteration.
|
||||
numerator->Times10();
|
||||
}
|
||||
// Generate the last digit.
|
||||
uint16_t digit;
|
||||
digit = numerator->DivideModuloIntBignum(*denominator);
|
||||
if (Bignum::PlusCompare(*numerator, *numerator, *denominator) >= 0) {
|
||||
digit++;
|
||||
}
|
||||
ASSERT(digit <= 10);
|
||||
buffer[count - 1] = static_cast<char>(digit + '0');
|
||||
// Correct bad digits (in case we had a sequence of '9's). Propagate the
|
||||
// carry until we hat a non-'9' or til we reach the first digit.
|
||||
for (int i = count - 1; i > 0; --i) {
|
||||
if (buffer[i] != '0' + 10) break;
|
||||
buffer[i] = '0';
|
||||
buffer[i - 1]++;
|
||||
}
|
||||
if (buffer[0] == '0' + 10) {
|
||||
// Propagate a carry past the top place.
|
||||
buffer[0] = '1';
|
||||
(*decimal_point)++;
|
||||
}
|
||||
*length = count;
|
||||
}
|
||||
|
||||
|
||||
// Generates 'requested_digits' after the decimal point. It might omit
|
||||
// trailing '0's. If the input number is too small then no digits at all are
|
||||
// generated (ex.: 2 fixed digits for 0.00001).
|
||||
//
|
||||
// Input verifies: 1 <= (numerator + delta) / denominator < 10.
|
||||
static void BignumToFixed(int requested_digits, int* decimal_point,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Vector<char>(buffer), int* length) {
|
||||
// Note that we have to look at more than just the requested_digits, since
|
||||
// a number could be rounded up. Example: v=0.5 with requested_digits=0.
|
||||
// Even though the power of v equals 0 we can't just stop here.
|
||||
if (-(*decimal_point) > requested_digits) {
|
||||
// The number is definitively too small.
|
||||
// Ex: 0.001 with requested_digits == 1.
|
||||
// Set decimal-point to -requested_digits. This is what Gay does.
|
||||
// Note that it should not have any effect anyways since the string is
|
||||
// empty.
|
||||
*decimal_point = -requested_digits;
|
||||
*length = 0;
|
||||
return;
|
||||
} else if (-(*decimal_point) == requested_digits) {
|
||||
// We only need to verify if the number rounds down or up.
|
||||
// Ex: 0.04 and 0.06 with requested_digits == 1.
|
||||
ASSERT(*decimal_point == -requested_digits);
|
||||
// Initially the fraction lies in range (1, 10]. Multiply the denominator
|
||||
// by 10 so that we can compare more easily.
|
||||
denominator->Times10();
|
||||
if (Bignum::PlusCompare(*numerator, *numerator, *denominator) >= 0) {
|
||||
// If the fraction is >= 0.5 then we have to include the rounded
|
||||
// digit.
|
||||
buffer[0] = '1';
|
||||
*length = 1;
|
||||
(*decimal_point)++;
|
||||
} else {
|
||||
// Note that we caught most of similar cases earlier.
|
||||
*length = 0;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// The requested digits correspond to the digits after the point.
|
||||
// The variable 'needed_digits' includes the digits before the point.
|
||||
int needed_digits = (*decimal_point) + requested_digits;
|
||||
GenerateCountedDigits(needed_digits, decimal_point,
|
||||
numerator, denominator,
|
||||
buffer, length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns an estimation of k such that 10^(k-1) <= v < 10^k where
|
||||
// v = f * 2^exponent and 2^52 <= f < 2^53.
|
||||
// v is hence a normalized double with the given exponent. The output is an
|
||||
// approximation for the exponent of the decimal approimation .digits * 10^k.
|
||||
//
|
||||
// The result might undershoot by 1 in which case 10^k <= v < 10^k+1.
|
||||
// Note: this property holds for v's upper boundary m+ too.
|
||||
// 10^k <= m+ < 10^k+1.
|
||||
// (see explanation below).
|
||||
//
|
||||
// Examples:
|
||||
// EstimatePower(0) => 16
|
||||
// EstimatePower(-52) => 0
|
||||
//
|
||||
// Note: e >= 0 => EstimatedPower(e) > 0. No similar claim can be made for e<0.
|
||||
static int EstimatePower(int exponent) {
|
||||
// This function estimates log10 of v where v = f*2^e (with e == exponent).
|
||||
// Note that 10^floor(log10(v)) <= v, but v <= 10^ceil(log10(v)).
|
||||
// Note that f is bounded by its container size. Let p = 53 (the double's
|
||||
// significand size). Then 2^(p-1) <= f < 2^p.
|
||||
//
|
||||
// Given that log10(v) == log2(v)/log2(10) and e+(len(f)-1) is quite close
|
||||
// to log2(v) the function is simplified to (e+(len(f)-1)/log2(10)).
|
||||
// The computed number undershoots by less than 0.631 (when we compute log3
|
||||
// and not log10).
|
||||
//
|
||||
// Optimization: since we only need an approximated result this computation
|
||||
// can be performed on 64 bit integers. On x86/x64 architecture the speedup is
|
||||
// not really measurable, though.
|
||||
//
|
||||
// Since we want to avoid overshooting we decrement by 1e10 so that
|
||||
// floating-point imprecisions don't affect us.
|
||||
//
|
||||
// Explanation for v's boundary m+: the computation takes advantage of
|
||||
// the fact that 2^(p-1) <= f < 2^p. Boundaries still satisfy this requirement
|
||||
// (even for denormals where the delta can be much more important).
|
||||
|
||||
const double k1Log10 = 0.30102999566398114; // 1/lg(10)
|
||||
|
||||
// For doubles len(f) == 53 (don't forget the hidden bit).
|
||||
const int kSignificandSize = Double::kSignificandSize;
|
||||
double estimate = ceil((exponent + kSignificandSize - 1) * k1Log10 - 1e-10);
|
||||
return static_cast<int>(estimate);
|
||||
}
|
||||
|
||||
|
||||
// See comments for InitialScaledStartValues.
|
||||
static void InitialScaledStartValuesPositiveExponent(
|
||||
uint64_t significand, int exponent,
|
||||
int estimated_power, bool need_boundary_deltas,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Bignum* delta_minus, Bignum* delta_plus) {
|
||||
// A positive exponent implies a positive power.
|
||||
ASSERT(estimated_power >= 0);
|
||||
// Since the estimated_power is positive we simply multiply the denominator
|
||||
// by 10^estimated_power.
|
||||
|
||||
// numerator = v.
|
||||
numerator->AssignUInt64(significand);
|
||||
numerator->ShiftLeft(exponent);
|
||||
// denominator = 10^estimated_power.
|
||||
denominator->AssignPowerUInt16(10, estimated_power);
|
||||
|
||||
if (need_boundary_deltas) {
|
||||
// Introduce a common denominator so that the deltas to the boundaries are
|
||||
// integers.
|
||||
denominator->ShiftLeft(1);
|
||||
numerator->ShiftLeft(1);
|
||||
// Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common
|
||||
// denominator (of 2) delta_plus equals 2^e.
|
||||
delta_plus->AssignUInt16(1);
|
||||
delta_plus->ShiftLeft(exponent);
|
||||
// Same for delta_minus. The adjustments if f == 2^p-1 are done later.
|
||||
delta_minus->AssignUInt16(1);
|
||||
delta_minus->ShiftLeft(exponent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// See comments for InitialScaledStartValues
|
||||
static void InitialScaledStartValuesNegativeExponentPositivePower(
|
||||
uint64_t significand, int exponent,
|
||||
int estimated_power, bool need_boundary_deltas,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Bignum* delta_minus, Bignum* delta_plus) {
|
||||
// v = f * 2^e with e < 0, and with estimated_power >= 0.
|
||||
// This means that e is close to 0 (have a look at how estimated_power is
|
||||
// computed).
|
||||
|
||||
// numerator = significand
|
||||
// since v = significand * 2^exponent this is equivalent to
|
||||
// numerator = v * / 2^-exponent
|
||||
numerator->AssignUInt64(significand);
|
||||
// denominator = 10^estimated_power * 2^-exponent (with exponent < 0)
|
||||
denominator->AssignPowerUInt16(10, estimated_power);
|
||||
denominator->ShiftLeft(-exponent);
|
||||
|
||||
if (need_boundary_deltas) {
|
||||
// Introduce a common denominator so that the deltas to the boundaries are
|
||||
// integers.
|
||||
denominator->ShiftLeft(1);
|
||||
numerator->ShiftLeft(1);
|
||||
// Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common
|
||||
// denominator (of 2) delta_plus equals 2^e.
|
||||
// Given that the denominator already includes v's exponent the distance
|
||||
// to the boundaries is simply 1.
|
||||
delta_plus->AssignUInt16(1);
|
||||
// Same for delta_minus. The adjustments if f == 2^p-1 are done later.
|
||||
delta_minus->AssignUInt16(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// See comments for InitialScaledStartValues
|
||||
static void InitialScaledStartValuesNegativeExponentNegativePower(
|
||||
uint64_t significand, int exponent,
|
||||
int estimated_power, bool need_boundary_deltas,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Bignum* delta_minus, Bignum* delta_plus) {
|
||||
// Instead of multiplying the denominator with 10^estimated_power we
|
||||
// multiply all values (numerator and deltas) by 10^-estimated_power.
|
||||
|
||||
// Use numerator as temporary container for power_ten.
|
||||
Bignum* power_ten = numerator;
|
||||
power_ten->AssignPowerUInt16(10, -estimated_power);
|
||||
|
||||
if (need_boundary_deltas) {
|
||||
// Since power_ten == numerator we must make a copy of 10^estimated_power
|
||||
// before we complete the computation of the numerator.
|
||||
// delta_plus = delta_minus = 10^estimated_power
|
||||
delta_plus->AssignBignum(*power_ten);
|
||||
delta_minus->AssignBignum(*power_ten);
|
||||
}
|
||||
|
||||
// numerator = significand * 2 * 10^-estimated_power
|
||||
// since v = significand * 2^exponent this is equivalent to
|
||||
// numerator = v * 10^-estimated_power * 2 * 2^-exponent.
|
||||
// Remember: numerator has been abused as power_ten. So no need to assign it
|
||||
// to itself.
|
||||
ASSERT(numerator == power_ten);
|
||||
numerator->MultiplyByUInt64(significand);
|
||||
|
||||
// denominator = 2 * 2^-exponent with exponent < 0.
|
||||
denominator->AssignUInt16(1);
|
||||
denominator->ShiftLeft(-exponent);
|
||||
|
||||
if (need_boundary_deltas) {
|
||||
// Introduce a common denominator so that the deltas to the boundaries are
|
||||
// integers.
|
||||
numerator->ShiftLeft(1);
|
||||
denominator->ShiftLeft(1);
|
||||
// With this shift the boundaries have their correct value, since
|
||||
// delta_plus = 10^-estimated_power, and
|
||||
// delta_minus = 10^-estimated_power.
|
||||
// These assignments have been done earlier.
|
||||
// The adjustments if f == 2^p-1 (lower boundary is closer) are done later.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Let v = significand * 2^exponent.
|
||||
// Computes v / 10^estimated_power exactly, as a ratio of two bignums, numerator
|
||||
// and denominator. The functions GenerateShortestDigits and
|
||||
// GenerateCountedDigits will then convert this ratio to its decimal
|
||||
// representation d, with the required accuracy.
|
||||
// Then d * 10^estimated_power is the representation of v.
|
||||
// (Note: the fraction and the estimated_power might get adjusted before
|
||||
// generating the decimal representation.)
|
||||
//
|
||||
// The initial start values consist of:
|
||||
// - a scaled numerator: s.t. numerator/denominator == v / 10^estimated_power.
|
||||
// - a scaled (common) denominator.
|
||||
// optionally (used by GenerateShortestDigits to decide if it has the shortest
|
||||
// decimal converting back to v):
|
||||
// - v - m-: the distance to the lower boundary.
|
||||
// - m+ - v: the distance to the upper boundary.
|
||||
//
|
||||
// v, m+, m-, and therefore v - m- and m+ - v all share the same denominator.
|
||||
//
|
||||
// Let ep == estimated_power, then the returned values will satisfy:
|
||||
// v / 10^ep = numerator / denominator.
|
||||
// v's boundarys m- and m+:
|
||||
// m- / 10^ep == v / 10^ep - delta_minus / denominator
|
||||
// m+ / 10^ep == v / 10^ep + delta_plus / denominator
|
||||
// Or in other words:
|
||||
// m- == v - delta_minus * 10^ep / denominator;
|
||||
// m+ == v + delta_plus * 10^ep / denominator;
|
||||
//
|
||||
// Since 10^(k-1) <= v < 10^k (with k == estimated_power)
|
||||
// or 10^k <= v < 10^(k+1)
|
||||
// we then have 0.1 <= numerator/denominator < 1
|
||||
// or 1 <= numerator/denominator < 10
|
||||
//
|
||||
// It is then easy to kickstart the digit-generation routine.
|
||||
//
|
||||
// The boundary-deltas are only filled if the mode equals BIGNUM_DTOA_SHORTEST
|
||||
// or BIGNUM_DTOA_SHORTEST_SINGLE.
|
||||
|
||||
static void InitialScaledStartValues(uint64_t significand,
|
||||
int exponent,
|
||||
bool lower_boundary_is_closer,
|
||||
int estimated_power,
|
||||
bool need_boundary_deltas,
|
||||
Bignum* numerator,
|
||||
Bignum* denominator,
|
||||
Bignum* delta_minus,
|
||||
Bignum* delta_plus) {
|
||||
if (exponent >= 0) {
|
||||
InitialScaledStartValuesPositiveExponent(
|
||||
significand, exponent, estimated_power, need_boundary_deltas,
|
||||
numerator, denominator, delta_minus, delta_plus);
|
||||
} else if (estimated_power >= 0) {
|
||||
InitialScaledStartValuesNegativeExponentPositivePower(
|
||||
significand, exponent, estimated_power, need_boundary_deltas,
|
||||
numerator, denominator, delta_minus, delta_plus);
|
||||
} else {
|
||||
InitialScaledStartValuesNegativeExponentNegativePower(
|
||||
significand, exponent, estimated_power, need_boundary_deltas,
|
||||
numerator, denominator, delta_minus, delta_plus);
|
||||
}
|
||||
|
||||
if (need_boundary_deltas && lower_boundary_is_closer) {
|
||||
// The lower boundary is closer at half the distance of "normal" numbers.
|
||||
// Increase the common denominator and adapt all but the delta_minus.
|
||||
denominator->ShiftLeft(1); // *2
|
||||
numerator->ShiftLeft(1); // *2
|
||||
delta_plus->ShiftLeft(1); // *2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This routine multiplies numerator/denominator so that its values lies in the
|
||||
// range 1-10. That is after a call to this function we have:
|
||||
// 1 <= (numerator + delta_plus) /denominator < 10.
|
||||
// Let numerator the input before modification and numerator' the argument
|
||||
// after modification, then the output-parameter decimal_point is such that
|
||||
// numerator / denominator * 10^estimated_power ==
|
||||
// numerator' / denominator' * 10^(decimal_point - 1)
|
||||
// In some cases estimated_power was too low, and this is already the case. We
|
||||
// then simply adjust the power so that 10^(k-1) <= v < 10^k (with k ==
|
||||
// estimated_power) but do not touch the numerator or denominator.
|
||||
// Otherwise the routine multiplies the numerator and the deltas by 10.
|
||||
static void FixupMultiply10(int estimated_power, bool is_even,
|
||||
int* decimal_point,
|
||||
Bignum* numerator, Bignum* denominator,
|
||||
Bignum* delta_minus, Bignum* delta_plus) {
|
||||
bool in_range;
|
||||
if (is_even) {
|
||||
// For IEEE doubles half-way cases (in decimal system numbers ending with 5)
|
||||
// are rounded to the closest floating-point number with even significand.
|
||||
in_range = Bignum::PlusCompare(*numerator, *delta_plus, *denominator) >= 0;
|
||||
} else {
|
||||
in_range = Bignum::PlusCompare(*numerator, *delta_plus, *denominator) > 0;
|
||||
}
|
||||
if (in_range) {
|
||||
// Since numerator + delta_plus >= denominator we already have
|
||||
// 1 <= numerator/denominator < 10. Simply update the estimated_power.
|
||||
*decimal_point = estimated_power + 1;
|
||||
} else {
|
||||
*decimal_point = estimated_power;
|
||||
numerator->Times10();
|
||||
if (Bignum::Equal(*delta_minus, *delta_plus)) {
|
||||
delta_minus->Times10();
|
||||
delta_plus->AssignBignum(*delta_minus);
|
||||
} else {
|
||||
delta_minus->Times10();
|
||||
delta_plus->Times10();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace double_conversion
|
@ -1,84 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_BIGNUM_DTOA_H_
|
||||
#define DOUBLE_CONVERSION_BIGNUM_DTOA_H_
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
enum BignumDtoaMode {
|
||||
// Return the shortest correct representation.
|
||||
// For example the output of 0.299999999999999988897 is (the less accurate but
|
||||
// correct) 0.3.
|
||||
BIGNUM_DTOA_SHORTEST,
|
||||
// Same as BIGNUM_DTOA_SHORTEST but for single-precision floats.
|
||||
BIGNUM_DTOA_SHORTEST_SINGLE,
|
||||
// Return a fixed number of digits after the decimal point.
|
||||
// For instance fixed(0.1, 4) becomes 0.1000
|
||||
// If the input number is big, the output will be big.
|
||||
BIGNUM_DTOA_FIXED,
|
||||
// Return a fixed number of digits, no matter what the exponent is.
|
||||
BIGNUM_DTOA_PRECISION
|
||||
};
|
||||
|
||||
// Converts the given double 'v' to ascii.
|
||||
// The result should be interpreted as buffer * 10^(point-length).
|
||||
// The buffer will be null-terminated.
|
||||
//
|
||||
// The input v must be > 0 and different from NaN, and Infinity.
|
||||
//
|
||||
// The output depends on the given mode:
|
||||
// - SHORTEST: produce the least amount of digits for which the internal
|
||||
// identity requirement is still satisfied. If the digits are printed
|
||||
// (together with the correct exponent) then reading this number will give
|
||||
// 'v' again. The buffer will choose the representation that is closest to
|
||||
// 'v'. If there are two at the same distance, than the number is round up.
|
||||
// In this mode the 'requested_digits' parameter is ignored.
|
||||
// - FIXED: produces digits necessary to print a given number with
|
||||
// 'requested_digits' digits after the decimal point. The produced digits
|
||||
// might be too short in which case the caller has to fill the gaps with '0's.
|
||||
// Example: toFixed(0.001, 5) is allowed to return buffer="1", point=-2.
|
||||
// Halfway cases are rounded up. The call toFixed(0.15, 2) thus returns
|
||||
// buffer="2", point=0.
|
||||
// Note: the length of the returned buffer has no meaning wrt the significance
|
||||
// of its digits. That is, just because it contains '0's does not mean that
|
||||
// any other digit would not satisfy the internal identity requirement.
|
||||
// - PRECISION: produces 'requested_digits' where the first digit is not '0'.
|
||||
// Even though the length of produced digits usually equals
|
||||
// 'requested_digits', the function is allowed to return fewer digits, in
|
||||
// which case the caller has to fill the missing digits with '0's.
|
||||
// Halfway cases are again rounded up.
|
||||
// 'BignumDtoa' expects the given buffer to be big enough to hold all digits
|
||||
// and a terminating null-character.
|
||||
void BignumDtoa(double v, BignumDtoaMode mode, int requested_digits,
|
||||
Vector<char> buffer, int* length, int* point);
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_BIGNUM_DTOA_H_
|
766
ios/Pods/DoubleConversion/double-conversion/bignum.cc
generated
766
ios/Pods/DoubleConversion/double-conversion/bignum.cc
generated
@ -1,766 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "bignum.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
Bignum::Bignum()
|
||||
: bigits_(bigits_buffer_, kBigitCapacity), used_digits_(0), exponent_(0) {
|
||||
for (int i = 0; i < kBigitCapacity; ++i) {
|
||||
bigits_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename S>
|
||||
static int BitSize(S value) {
|
||||
(void) value; // Mark variable as used.
|
||||
return 8 * sizeof(value);
|
||||
}
|
||||
|
||||
// Guaranteed to lie in one Bigit.
|
||||
void Bignum::AssignUInt16(uint16_t value) {
|
||||
ASSERT(kBigitSize >= BitSize(value));
|
||||
Zero();
|
||||
if (value == 0) return;
|
||||
|
||||
EnsureCapacity(1);
|
||||
bigits_[0] = value;
|
||||
used_digits_ = 1;
|
||||
}
|
||||
|
||||
|
||||
void Bignum::AssignUInt64(uint64_t value) {
|
||||
const int kUInt64Size = 64;
|
||||
|
||||
Zero();
|
||||
if (value == 0) return;
|
||||
|
||||
int needed_bigits = kUInt64Size / kBigitSize + 1;
|
||||
EnsureCapacity(needed_bigits);
|
||||
for (int i = 0; i < needed_bigits; ++i) {
|
||||
bigits_[i] = value & kBigitMask;
|
||||
value = value >> kBigitSize;
|
||||
}
|
||||
used_digits_ = needed_bigits;
|
||||
Clamp();
|
||||
}
|
||||
|
||||
|
||||
void Bignum::AssignBignum(const Bignum& other) {
|
||||
exponent_ = other.exponent_;
|
||||
for (int i = 0; i < other.used_digits_; ++i) {
|
||||
bigits_[i] = other.bigits_[i];
|
||||
}
|
||||
// Clear the excess digits (if there were any).
|
||||
for (int i = other.used_digits_; i < used_digits_; ++i) {
|
||||
bigits_[i] = 0;
|
||||
}
|
||||
used_digits_ = other.used_digits_;
|
||||
}
|
||||
|
||||
|
||||
static uint64_t ReadUInt64(Vector<const char> buffer,
|
||||
int from,
|
||||
int digits_to_read) {
|
||||
uint64_t result = 0;
|
||||
for (int i = from; i < from + digits_to_read; ++i) {
|
||||
int digit = buffer[i] - '0';
|
||||
ASSERT(0 <= digit && digit <= 9);
|
||||
result = result * 10 + digit;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void Bignum::AssignDecimalString(Vector<const char> value) {
|
||||
// 2^64 = 18446744073709551616 > 10^19
|
||||
const int kMaxUint64DecimalDigits = 19;
|
||||
Zero();
|
||||
int length = value.length();
|
||||
int pos = 0;
|
||||
// Let's just say that each digit needs 4 bits.
|
||||
while (length >= kMaxUint64DecimalDigits) {
|
||||
uint64_t digits = ReadUInt64(value, pos, kMaxUint64DecimalDigits);
|
||||
pos += kMaxUint64DecimalDigits;
|
||||
length -= kMaxUint64DecimalDigits;
|
||||
MultiplyByPowerOfTen(kMaxUint64DecimalDigits);
|
||||
AddUInt64(digits);
|
||||
}
|
||||
uint64_t digits = ReadUInt64(value, pos, length);
|
||||
MultiplyByPowerOfTen(length);
|
||||
AddUInt64(digits);
|
||||
Clamp();
|
||||
}
|
||||
|
||||
|
||||
static int HexCharValue(char c) {
|
||||
if ('0' <= c && c <= '9') return c - '0';
|
||||
if ('a' <= c && c <= 'f') return 10 + c - 'a';
|
||||
ASSERT('A' <= c && c <= 'F');
|
||||
return 10 + c - 'A';
|
||||
}
|
||||
|
||||
|
||||
void Bignum::AssignHexString(Vector<const char> value) {
|
||||
Zero();
|
||||
int length = value.length();
|
||||
|
||||
int needed_bigits = length * 4 / kBigitSize + 1;
|
||||
EnsureCapacity(needed_bigits);
|
||||
int string_index = length - 1;
|
||||
for (int i = 0; i < needed_bigits - 1; ++i) {
|
||||
// These bigits are guaranteed to be "full".
|
||||
Chunk current_bigit = 0;
|
||||
for (int j = 0; j < kBigitSize / 4; j++) {
|
||||
current_bigit += HexCharValue(value[string_index--]) << (j * 4);
|
||||
}
|
||||
bigits_[i] = current_bigit;
|
||||
}
|
||||
used_digits_ = needed_bigits - 1;
|
||||
|
||||
Chunk most_significant_bigit = 0; // Could be = 0;
|
||||
for (int j = 0; j <= string_index; ++j) {
|
||||
most_significant_bigit <<= 4;
|
||||
most_significant_bigit += HexCharValue(value[j]);
|
||||
}
|
||||
if (most_significant_bigit != 0) {
|
||||
bigits_[used_digits_] = most_significant_bigit;
|
||||
used_digits_++;
|
||||
}
|
||||
Clamp();
|
||||
}
|
||||
|
||||
|
||||
void Bignum::AddUInt64(uint64_t operand) {
|
||||
if (operand == 0) return;
|
||||
Bignum other;
|
||||
other.AssignUInt64(operand);
|
||||
AddBignum(other);
|
||||
}
|
||||
|
||||
|
||||
void Bignum::AddBignum(const Bignum& other) {
|
||||
ASSERT(IsClamped());
|
||||
ASSERT(other.IsClamped());
|
||||
|
||||
// If this has a greater exponent than other append zero-bigits to this.
|
||||
// After this call exponent_ <= other.exponent_.
|
||||
Align(other);
|
||||
|
||||
// There are two possibilities:
|
||||
// aaaaaaaaaaa 0000 (where the 0s represent a's exponent)
|
||||
// bbbbb 00000000
|
||||
// ----------------
|
||||
// ccccccccccc 0000
|
||||
// or
|
||||
// aaaaaaaaaa 0000
|
||||
// bbbbbbbbb 0000000
|
||||
// -----------------
|
||||
// cccccccccccc 0000
|
||||
// In both cases we might need a carry bigit.
|
||||
|
||||
EnsureCapacity(1 + Max(BigitLength(), other.BigitLength()) - exponent_);
|
||||
Chunk carry = 0;
|
||||
int bigit_pos = other.exponent_ - exponent_;
|
||||
ASSERT(bigit_pos >= 0);
|
||||
for (int i = 0; i < other.used_digits_; ++i) {
|
||||
Chunk sum = bigits_[bigit_pos] + other.bigits_[i] + carry;
|
||||
bigits_[bigit_pos] = sum & kBigitMask;
|
||||
carry = sum >> kBigitSize;
|
||||
bigit_pos++;
|
||||
}
|
||||
|
||||
while (carry != 0) {
|
||||
Chunk sum = bigits_[bigit_pos] + carry;
|
||||
bigits_[bigit_pos] = sum & kBigitMask;
|
||||
carry = sum >> kBigitSize;
|
||||
bigit_pos++;
|
||||
}
|
||||
used_digits_ = Max(bigit_pos, used_digits_);
|
||||
ASSERT(IsClamped());
|
||||
}
|
||||
|
||||
|
||||
void Bignum::SubtractBignum(const Bignum& other) {
|
||||
ASSERT(IsClamped());
|
||||
ASSERT(other.IsClamped());
|
||||
// We require this to be bigger than other.
|
||||
ASSERT(LessEqual(other, *this));
|
||||
|
||||
Align(other);
|
||||
|
||||
int offset = other.exponent_ - exponent_;
|
||||
Chunk borrow = 0;
|
||||
int i;
|
||||
for (i = 0; i < other.used_digits_; ++i) {
|
||||
ASSERT((borrow == 0) || (borrow == 1));
|
||||
Chunk difference = bigits_[i + offset] - other.bigits_[i] - borrow;
|
||||
bigits_[i + offset] = difference & kBigitMask;
|
||||
borrow = difference >> (kChunkSize - 1);
|
||||
}
|
||||
while (borrow != 0) {
|
||||
Chunk difference = bigits_[i + offset] - borrow;
|
||||
bigits_[i + offset] = difference & kBigitMask;
|
||||
borrow = difference >> (kChunkSize - 1);
|
||||
++i;
|
||||
}
|
||||
Clamp();
|
||||
}
|
||||
|
||||
|
||||
void Bignum::ShiftLeft(int shift_amount) {
|
||||
if (used_digits_ == 0) return;
|
||||
exponent_ += shift_amount / kBigitSize;
|
||||
int local_shift = shift_amount % kBigitSize;
|
||||
EnsureCapacity(used_digits_ + 1);
|
||||
BigitsShiftLeft(local_shift);
|
||||
}
|
||||
|
||||
|
||||
void Bignum::MultiplyByUInt32(uint32_t factor) {
|
||||
if (factor == 1) return;
|
||||
if (factor == 0) {
|
||||
Zero();
|
||||
return;
|
||||
}
|
||||
if (used_digits_ == 0) return;
|
||||
|
||||
// The product of a bigit with the factor is of size kBigitSize + 32.
|
||||
// Assert that this number + 1 (for the carry) fits into double chunk.
|
||||
ASSERT(kDoubleChunkSize >= kBigitSize + 32 + 1);
|
||||
DoubleChunk carry = 0;
|
||||
for (int i = 0; i < used_digits_; ++i) {
|
||||
DoubleChunk product = static_cast<DoubleChunk>(factor) * bigits_[i] + carry;
|
||||
bigits_[i] = static_cast<Chunk>(product & kBigitMask);
|
||||
carry = (product >> kBigitSize);
|
||||
}
|
||||
while (carry != 0) {
|
||||
EnsureCapacity(used_digits_ + 1);
|
||||
bigits_[used_digits_] = carry & kBigitMask;
|
||||
used_digits_++;
|
||||
carry >>= kBigitSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Bignum::MultiplyByUInt64(uint64_t factor) {
|
||||
if (factor == 1) return;
|
||||
if (factor == 0) {
|
||||
Zero();
|
||||
return;
|
||||
}
|
||||
ASSERT(kBigitSize < 32);
|
||||
uint64_t carry = 0;
|
||||
uint64_t low = factor & 0xFFFFFFFF;
|
||||
uint64_t high = factor >> 32;
|
||||
for (int i = 0; i < used_digits_; ++i) {
|
||||
uint64_t product_low = low * bigits_[i];
|
||||
uint64_t product_high = high * bigits_[i];
|
||||
uint64_t tmp = (carry & kBigitMask) + product_low;
|
||||
bigits_[i] = tmp & kBigitMask;
|
||||
carry = (carry >> kBigitSize) + (tmp >> kBigitSize) +
|
||||
(product_high << (32 - kBigitSize));
|
||||
}
|
||||
while (carry != 0) {
|
||||
EnsureCapacity(used_digits_ + 1);
|
||||
bigits_[used_digits_] = carry & kBigitMask;
|
||||
used_digits_++;
|
||||
carry >>= kBigitSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Bignum::MultiplyByPowerOfTen(int exponent) {
|
||||
const uint64_t kFive27 = UINT64_2PART_C(0x6765c793, fa10079d);
|
||||
const uint16_t kFive1 = 5;
|
||||
const uint16_t kFive2 = kFive1 * 5;
|
||||
const uint16_t kFive3 = kFive2 * 5;
|
||||
const uint16_t kFive4 = kFive3 * 5;
|
||||
const uint16_t kFive5 = kFive4 * 5;
|
||||
const uint16_t kFive6 = kFive5 * 5;
|
||||
const uint32_t kFive7 = kFive6 * 5;
|
||||
const uint32_t kFive8 = kFive7 * 5;
|
||||
const uint32_t kFive9 = kFive8 * 5;
|
||||
const uint32_t kFive10 = kFive9 * 5;
|
||||
const uint32_t kFive11 = kFive10 * 5;
|
||||
const uint32_t kFive12 = kFive11 * 5;
|
||||
const uint32_t kFive13 = kFive12 * 5;
|
||||
const uint32_t kFive1_to_12[] =
|
||||
{ kFive1, kFive2, kFive3, kFive4, kFive5, kFive6,
|
||||
kFive7, kFive8, kFive9, kFive10, kFive11, kFive12 };
|
||||
|
||||
ASSERT(exponent >= 0);
|
||||
if (exponent == 0) return;
|
||||
if (used_digits_ == 0) return;
|
||||
|
||||
// We shift by exponent at the end just before returning.
|
||||
int remaining_exponent = exponent;
|
||||
while (remaining_exponent >= 27) {
|
||||
MultiplyByUInt64(kFive27);
|
||||
remaining_exponent -= 27;
|
||||
}
|
||||
while (remaining_exponent >= 13) {
|
||||
MultiplyByUInt32(kFive13);
|
||||
remaining_exponent -= 13;
|
||||
}
|
||||
if (remaining_exponent > 0) {
|
||||
MultiplyByUInt32(kFive1_to_12[remaining_exponent - 1]);
|
||||
}
|
||||
ShiftLeft(exponent);
|
||||
}
|
||||
|
||||
|
||||
void Bignum::Square() {
|
||||
ASSERT(IsClamped());
|
||||
int product_length = 2 * used_digits_;
|
||||
EnsureCapacity(product_length);
|
||||
|
||||
// Comba multiplication: compute each column separately.
|
||||
// Example: r = a2a1a0 * b2b1b0.
|
||||
// r = 1 * a0b0 +
|
||||
// 10 * (a1b0 + a0b1) +
|
||||
// 100 * (a2b0 + a1b1 + a0b2) +
|
||||
// 1000 * (a2b1 + a1b2) +
|
||||
// 10000 * a2b2
|
||||
//
|
||||
// In the worst case we have to accumulate nb-digits products of digit*digit.
|
||||
//
|
||||
// Assert that the additional number of bits in a DoubleChunk are enough to
|
||||
// sum up used_digits of Bigit*Bigit.
|
||||
if ((1 << (2 * (kChunkSize - kBigitSize))) <= used_digits_) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
DoubleChunk accumulator = 0;
|
||||
// First shift the digits so we don't overwrite them.
|
||||
int copy_offset = used_digits_;
|
||||
for (int i = 0; i < used_digits_; ++i) {
|
||||
bigits_[copy_offset + i] = bigits_[i];
|
||||
}
|
||||
// We have two loops to avoid some 'if's in the loop.
|
||||
for (int i = 0; i < used_digits_; ++i) {
|
||||
// Process temporary digit i with power i.
|
||||
// The sum of the two indices must be equal to i.
|
||||
int bigit_index1 = i;
|
||||
int bigit_index2 = 0;
|
||||
// Sum all of the sub-products.
|
||||
while (bigit_index1 >= 0) {
|
||||
Chunk chunk1 = bigits_[copy_offset + bigit_index1];
|
||||
Chunk chunk2 = bigits_[copy_offset + bigit_index2];
|
||||
accumulator += static_cast<DoubleChunk>(chunk1) * chunk2;
|
||||
bigit_index1--;
|
||||
bigit_index2++;
|
||||
}
|
||||
bigits_[i] = static_cast<Chunk>(accumulator) & kBigitMask;
|
||||
accumulator >>= kBigitSize;
|
||||
}
|
||||
for (int i = used_digits_; i < product_length; ++i) {
|
||||
int bigit_index1 = used_digits_ - 1;
|
||||
int bigit_index2 = i - bigit_index1;
|
||||
// Invariant: sum of both indices is again equal to i.
|
||||
// Inner loop runs 0 times on last iteration, emptying accumulator.
|
||||
while (bigit_index2 < used_digits_) {
|
||||
Chunk chunk1 = bigits_[copy_offset + bigit_index1];
|
||||
Chunk chunk2 = bigits_[copy_offset + bigit_index2];
|
||||
accumulator += static_cast<DoubleChunk>(chunk1) * chunk2;
|
||||
bigit_index1--;
|
||||
bigit_index2++;
|
||||
}
|
||||
// The overwritten bigits_[i] will never be read in further loop iterations,
|
||||
// because bigit_index1 and bigit_index2 are always greater
|
||||
// than i - used_digits_.
|
||||
bigits_[i] = static_cast<Chunk>(accumulator) & kBigitMask;
|
||||
accumulator >>= kBigitSize;
|
||||
}
|
||||
// Since the result was guaranteed to lie inside the number the
|
||||
// accumulator must be 0 now.
|
||||
ASSERT(accumulator == 0);
|
||||
|
||||
// Don't forget to update the used_digits and the exponent.
|
||||
used_digits_ = product_length;
|
||||
exponent_ *= 2;
|
||||
Clamp();
|
||||
}
|
||||
|
||||
|
||||
void Bignum::AssignPowerUInt16(uint16_t base, int power_exponent) {
|
||||
ASSERT(base != 0);
|
||||
ASSERT(power_exponent >= 0);
|
||||
if (power_exponent == 0) {
|
||||
AssignUInt16(1);
|
||||
return;
|
||||
}
|
||||
Zero();
|
||||
int shifts = 0;
|
||||
// We expect base to be in range 2-32, and most often to be 10.
|
||||
// It does not make much sense to implement different algorithms for counting
|
||||
// the bits.
|
||||
while ((base & 1) == 0) {
|
||||
base >>= 1;
|
||||
shifts++;
|
||||
}
|
||||
int bit_size = 0;
|
||||
int tmp_base = base;
|
||||
while (tmp_base != 0) {
|
||||
tmp_base >>= 1;
|
||||
bit_size++;
|
||||
}
|
||||
int final_size = bit_size * power_exponent;
|
||||
// 1 extra bigit for the shifting, and one for rounded final_size.
|
||||
EnsureCapacity(final_size / kBigitSize + 2);
|
||||
|
||||
// Left to Right exponentiation.
|
||||
int mask = 1;
|
||||
while (power_exponent >= mask) mask <<= 1;
|
||||
|
||||
// The mask is now pointing to the bit above the most significant 1-bit of
|
||||
// power_exponent.
|
||||
// Get rid of first 1-bit;
|
||||
mask >>= 2;
|
||||
uint64_t this_value = base;
|
||||
|
||||
bool delayed_multipliciation = false;
|
||||
const uint64_t max_32bits = 0xFFFFFFFF;
|
||||
while (mask != 0 && this_value <= max_32bits) {
|
||||
this_value = this_value * this_value;
|
||||
// Verify that there is enough space in this_value to perform the
|
||||
// multiplication. The first bit_size bits must be 0.
|
||||
if ((power_exponent & mask) != 0) {
|
||||
uint64_t base_bits_mask =
|
||||
~((static_cast<uint64_t>(1) << (64 - bit_size)) - 1);
|
||||
bool high_bits_zero = (this_value & base_bits_mask) == 0;
|
||||
if (high_bits_zero) {
|
||||
this_value *= base;
|
||||
} else {
|
||||
delayed_multipliciation = true;
|
||||
}
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
AssignUInt64(this_value);
|
||||
if (delayed_multipliciation) {
|
||||
MultiplyByUInt32(base);
|
||||
}
|
||||
|
||||
// Now do the same thing as a bignum.
|
||||
while (mask != 0) {
|
||||
Square();
|
||||
if ((power_exponent & mask) != 0) {
|
||||
MultiplyByUInt32(base);
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
// And finally add the saved shifts.
|
||||
ShiftLeft(shifts * power_exponent);
|
||||
}
|
||||
|
||||
|
||||
// Precondition: this/other < 16bit.
|
||||
uint16_t Bignum::DivideModuloIntBignum(const Bignum& other) {
|
||||
ASSERT(IsClamped());
|
||||
ASSERT(other.IsClamped());
|
||||
ASSERT(other.used_digits_ > 0);
|
||||
|
||||
// Easy case: if we have less digits than the divisor than the result is 0.
|
||||
// Note: this handles the case where this == 0, too.
|
||||
if (BigitLength() < other.BigitLength()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Align(other);
|
||||
|
||||
uint16_t result = 0;
|
||||
|
||||
// Start by removing multiples of 'other' until both numbers have the same
|
||||
// number of digits.
|
||||
while (BigitLength() > other.BigitLength()) {
|
||||
// This naive approach is extremely inefficient if `this` divided by other
|
||||
// is big. This function is implemented for doubleToString where
|
||||
// the result should be small (less than 10).
|
||||
ASSERT(other.bigits_[other.used_digits_ - 1] >= ((1 << kBigitSize) / 16));
|
||||
ASSERT(bigits_[used_digits_ - 1] < 0x10000);
|
||||
// Remove the multiples of the first digit.
|
||||
// Example this = 23 and other equals 9. -> Remove 2 multiples.
|
||||
result += static_cast<uint16_t>(bigits_[used_digits_ - 1]);
|
||||
SubtractTimes(other, bigits_[used_digits_ - 1]);
|
||||
}
|
||||
|
||||
ASSERT(BigitLength() == other.BigitLength());
|
||||
|
||||
// Both bignums are at the same length now.
|
||||
// Since other has more than 0 digits we know that the access to
|
||||
// bigits_[used_digits_ - 1] is safe.
|
||||
Chunk this_bigit = bigits_[used_digits_ - 1];
|
||||
Chunk other_bigit = other.bigits_[other.used_digits_ - 1];
|
||||
|
||||
if (other.used_digits_ == 1) {
|
||||
// Shortcut for easy (and common) case.
|
||||
int quotient = this_bigit / other_bigit;
|
||||
bigits_[used_digits_ - 1] = this_bigit - other_bigit * quotient;
|
||||
ASSERT(quotient < 0x10000);
|
||||
result += static_cast<uint16_t>(quotient);
|
||||
Clamp();
|
||||
return result;
|
||||
}
|
||||
|
||||
int division_estimate = this_bigit / (other_bigit + 1);
|
||||
ASSERT(division_estimate < 0x10000);
|
||||
result += static_cast<uint16_t>(division_estimate);
|
||||
SubtractTimes(other, division_estimate);
|
||||
|
||||
if (other_bigit * (division_estimate + 1) > this_bigit) {
|
||||
// No need to even try to subtract. Even if other's remaining digits were 0
|
||||
// another subtraction would be too much.
|
||||
return result;
|
||||
}
|
||||
|
||||
while (LessEqual(other, *this)) {
|
||||
SubtractBignum(other);
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template<typename S>
|
||||
static int SizeInHexChars(S number) {
|
||||
ASSERT(number > 0);
|
||||
int result = 0;
|
||||
while (number != 0) {
|
||||
number >>= 4;
|
||||
result++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static char HexCharOfValue(int value) {
|
||||
ASSERT(0 <= value && value <= 16);
|
||||
if (value < 10) return static_cast<char>(value + '0');
|
||||
return static_cast<char>(value - 10 + 'A');
|
||||
}
|
||||
|
||||
|
||||
bool Bignum::ToHexString(char* buffer, int buffer_size) const {
|
||||
ASSERT(IsClamped());
|
||||
// Each bigit must be printable as separate hex-character.
|
||||
ASSERT(kBigitSize % 4 == 0);
|
||||
const int kHexCharsPerBigit = kBigitSize / 4;
|
||||
|
||||
if (used_digits_ == 0) {
|
||||
if (buffer_size < 2) return false;
|
||||
buffer[0] = '0';
|
||||
buffer[1] = '\0';
|
||||
return true;
|
||||
}
|
||||
// We add 1 for the terminating '\0' character.
|
||||
int needed_chars = (BigitLength() - 1) * kHexCharsPerBigit +
|
||||
SizeInHexChars(bigits_[used_digits_ - 1]) + 1;
|
||||
if (needed_chars > buffer_size) return false;
|
||||
int string_index = needed_chars - 1;
|
||||
buffer[string_index--] = '\0';
|
||||
for (int i = 0; i < exponent_; ++i) {
|
||||
for (int j = 0; j < kHexCharsPerBigit; ++j) {
|
||||
buffer[string_index--] = '0';
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < used_digits_ - 1; ++i) {
|
||||
Chunk current_bigit = bigits_[i];
|
||||
for (int j = 0; j < kHexCharsPerBigit; ++j) {
|
||||
buffer[string_index--] = HexCharOfValue(current_bigit & 0xF);
|
||||
current_bigit >>= 4;
|
||||
}
|
||||
}
|
||||
// And finally the last bigit.
|
||||
Chunk most_significant_bigit = bigits_[used_digits_ - 1];
|
||||
while (most_significant_bigit != 0) {
|
||||
buffer[string_index--] = HexCharOfValue(most_significant_bigit & 0xF);
|
||||
most_significant_bigit >>= 4;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Bignum::Chunk Bignum::BigitAt(int index) const {
|
||||
if (index >= BigitLength()) return 0;
|
||||
if (index < exponent_) return 0;
|
||||
return bigits_[index - exponent_];
|
||||
}
|
||||
|
||||
|
||||
int Bignum::Compare(const Bignum& a, const Bignum& b) {
|
||||
ASSERT(a.IsClamped());
|
||||
ASSERT(b.IsClamped());
|
||||
int bigit_length_a = a.BigitLength();
|
||||
int bigit_length_b = b.BigitLength();
|
||||
if (bigit_length_a < bigit_length_b) return -1;
|
||||
if (bigit_length_a > bigit_length_b) return +1;
|
||||
for (int i = bigit_length_a - 1; i >= Min(a.exponent_, b.exponent_); --i) {
|
||||
Chunk bigit_a = a.BigitAt(i);
|
||||
Chunk bigit_b = b.BigitAt(i);
|
||||
if (bigit_a < bigit_b) return -1;
|
||||
if (bigit_a > bigit_b) return +1;
|
||||
// Otherwise they are equal up to this digit. Try the next digit.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int Bignum::PlusCompare(const Bignum& a, const Bignum& b, const Bignum& c) {
|
||||
ASSERT(a.IsClamped());
|
||||
ASSERT(b.IsClamped());
|
||||
ASSERT(c.IsClamped());
|
||||
if (a.BigitLength() < b.BigitLength()) {
|
||||
return PlusCompare(b, a, c);
|
||||
}
|
||||
if (a.BigitLength() + 1 < c.BigitLength()) return -1;
|
||||
if (a.BigitLength() > c.BigitLength()) return +1;
|
||||
// The exponent encodes 0-bigits. So if there are more 0-digits in 'a' than
|
||||
// 'b' has digits, then the bigit-length of 'a'+'b' must be equal to the one
|
||||
// of 'a'.
|
||||
if (a.exponent_ >= b.BigitLength() && a.BigitLength() < c.BigitLength()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Chunk borrow = 0;
|
||||
// Starting at min_exponent all digits are == 0. So no need to compare them.
|
||||
int min_exponent = Min(Min(a.exponent_, b.exponent_), c.exponent_);
|
||||
for (int i = c.BigitLength() - 1; i >= min_exponent; --i) {
|
||||
Chunk chunk_a = a.BigitAt(i);
|
||||
Chunk chunk_b = b.BigitAt(i);
|
||||
Chunk chunk_c = c.BigitAt(i);
|
||||
Chunk sum = chunk_a + chunk_b;
|
||||
if (sum > chunk_c + borrow) {
|
||||
return +1;
|
||||
} else {
|
||||
borrow = chunk_c + borrow - sum;
|
||||
if (borrow > 1) return -1;
|
||||
borrow <<= kBigitSize;
|
||||
}
|
||||
}
|
||||
if (borrow == 0) return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void Bignum::Clamp() {
|
||||
while (used_digits_ > 0 && bigits_[used_digits_ - 1] == 0) {
|
||||
used_digits_--;
|
||||
}
|
||||
if (used_digits_ == 0) {
|
||||
// Zero.
|
||||
exponent_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool Bignum::IsClamped() const {
|
||||
return used_digits_ == 0 || bigits_[used_digits_ - 1] != 0;
|
||||
}
|
||||
|
||||
|
||||
void Bignum::Zero() {
|
||||
for (int i = 0; i < used_digits_; ++i) {
|
||||
bigits_[i] = 0;
|
||||
}
|
||||
used_digits_ = 0;
|
||||
exponent_ = 0;
|
||||
}
|
||||
|
||||
|
||||
void Bignum::Align(const Bignum& other) {
|
||||
if (exponent_ > other.exponent_) {
|
||||
// If "X" represents a "hidden" digit (by the exponent) then we are in the
|
||||
// following case (a == this, b == other):
|
||||
// a: aaaaaaXXXX or a: aaaaaXXX
|
||||
// b: bbbbbbX b: bbbbbbbbXX
|
||||
// We replace some of the hidden digits (X) of a with 0 digits.
|
||||
// a: aaaaaa000X or a: aaaaa0XX
|
||||
int zero_digits = exponent_ - other.exponent_;
|
||||
EnsureCapacity(used_digits_ + zero_digits);
|
||||
for (int i = used_digits_ - 1; i >= 0; --i) {
|
||||
bigits_[i + zero_digits] = bigits_[i];
|
||||
}
|
||||
for (int i = 0; i < zero_digits; ++i) {
|
||||
bigits_[i] = 0;
|
||||
}
|
||||
used_digits_ += zero_digits;
|
||||
exponent_ -= zero_digits;
|
||||
ASSERT(used_digits_ >= 0);
|
||||
ASSERT(exponent_ >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Bignum::BigitsShiftLeft(int shift_amount) {
|
||||
ASSERT(shift_amount < kBigitSize);
|
||||
ASSERT(shift_amount >= 0);
|
||||
Chunk carry = 0;
|
||||
for (int i = 0; i < used_digits_; ++i) {
|
||||
Chunk new_carry = bigits_[i] >> (kBigitSize - shift_amount);
|
||||
bigits_[i] = ((bigits_[i] << shift_amount) + carry) & kBigitMask;
|
||||
carry = new_carry;
|
||||
}
|
||||
if (carry != 0) {
|
||||
bigits_[used_digits_] = carry;
|
||||
used_digits_++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Bignum::SubtractTimes(const Bignum& other, int factor) {
|
||||
ASSERT(exponent_ <= other.exponent_);
|
||||
if (factor < 3) {
|
||||
for (int i = 0; i < factor; ++i) {
|
||||
SubtractBignum(other);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Chunk borrow = 0;
|
||||
int exponent_diff = other.exponent_ - exponent_;
|
||||
for (int i = 0; i < other.used_digits_; ++i) {
|
||||
DoubleChunk product = static_cast<DoubleChunk>(factor) * other.bigits_[i];
|
||||
DoubleChunk remove = borrow + product;
|
||||
Chunk difference = bigits_[i + exponent_diff] - (remove & kBigitMask);
|
||||
bigits_[i + exponent_diff] = difference & kBigitMask;
|
||||
borrow = static_cast<Chunk>((difference >> (kChunkSize - 1)) +
|
||||
(remove >> kBigitSize));
|
||||
}
|
||||
for (int i = other.used_digits_ + exponent_diff; i < used_digits_; ++i) {
|
||||
if (borrow == 0) return;
|
||||
Chunk difference = bigits_[i] - borrow;
|
||||
bigits_[i] = difference & kBigitMask;
|
||||
borrow = difference >> (kChunkSize - 1);
|
||||
}
|
||||
Clamp();
|
||||
}
|
||||
|
||||
|
||||
} // namespace double_conversion
|
145
ios/Pods/DoubleConversion/double-conversion/bignum.h
generated
145
ios/Pods/DoubleConversion/double-conversion/bignum.h
generated
@ -1,145 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_BIGNUM_H_
|
||||
#define DOUBLE_CONVERSION_BIGNUM_H_
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
class Bignum {
|
||||
public:
|
||||
// 3584 = 128 * 28. We can represent 2^3584 > 10^1000 accurately.
|
||||
// This bignum can encode much bigger numbers, since it contains an
|
||||
// exponent.
|
||||
static const int kMaxSignificantBits = 3584;
|
||||
|
||||
Bignum();
|
||||
void AssignUInt16(uint16_t value);
|
||||
void AssignUInt64(uint64_t value);
|
||||
void AssignBignum(const Bignum& other);
|
||||
|
||||
void AssignDecimalString(Vector<const char> value);
|
||||
void AssignHexString(Vector<const char> value);
|
||||
|
||||
void AssignPowerUInt16(uint16_t base, int exponent);
|
||||
|
||||
void AddUInt16(uint16_t operand);
|
||||
void AddUInt64(uint64_t operand);
|
||||
void AddBignum(const Bignum& other);
|
||||
// Precondition: this >= other.
|
||||
void SubtractBignum(const Bignum& other);
|
||||
|
||||
void Square();
|
||||
void ShiftLeft(int shift_amount);
|
||||
void MultiplyByUInt32(uint32_t factor);
|
||||
void MultiplyByUInt64(uint64_t factor);
|
||||
void MultiplyByPowerOfTen(int exponent);
|
||||
void Times10() { return MultiplyByUInt32(10); }
|
||||
// Pseudocode:
|
||||
// int result = this / other;
|
||||
// this = this % other;
|
||||
// In the worst case this function is in O(this/other).
|
||||
uint16_t DivideModuloIntBignum(const Bignum& other);
|
||||
|
||||
bool ToHexString(char* buffer, int buffer_size) const;
|
||||
|
||||
// Returns
|
||||
// -1 if a < b,
|
||||
// 0 if a == b, and
|
||||
// +1 if a > b.
|
||||
static int Compare(const Bignum& a, const Bignum& b);
|
||||
static bool Equal(const Bignum& a, const Bignum& b) {
|
||||
return Compare(a, b) == 0;
|
||||
}
|
||||
static bool LessEqual(const Bignum& a, const Bignum& b) {
|
||||
return Compare(a, b) <= 0;
|
||||
}
|
||||
static bool Less(const Bignum& a, const Bignum& b) {
|
||||
return Compare(a, b) < 0;
|
||||
}
|
||||
// Returns Compare(a + b, c);
|
||||
static int PlusCompare(const Bignum& a, const Bignum& b, const Bignum& c);
|
||||
// Returns a + b == c
|
||||
static bool PlusEqual(const Bignum& a, const Bignum& b, const Bignum& c) {
|
||||
return PlusCompare(a, b, c) == 0;
|
||||
}
|
||||
// Returns a + b <= c
|
||||
static bool PlusLessEqual(const Bignum& a, const Bignum& b, const Bignum& c) {
|
||||
return PlusCompare(a, b, c) <= 0;
|
||||
}
|
||||
// Returns a + b < c
|
||||
static bool PlusLess(const Bignum& a, const Bignum& b, const Bignum& c) {
|
||||
return PlusCompare(a, b, c) < 0;
|
||||
}
|
||||
private:
|
||||
typedef uint32_t Chunk;
|
||||
typedef uint64_t DoubleChunk;
|
||||
|
||||
static const int kChunkSize = sizeof(Chunk) * 8;
|
||||
static const int kDoubleChunkSize = sizeof(DoubleChunk) * 8;
|
||||
// With bigit size of 28 we loose some bits, but a double still fits easily
|
||||
// into two chunks, and more importantly we can use the Comba multiplication.
|
||||
static const int kBigitSize = 28;
|
||||
static const Chunk kBigitMask = (1 << kBigitSize) - 1;
|
||||
// Every instance allocates kBigitLength chunks on the stack. Bignums cannot
|
||||
// grow. There are no checks if the stack-allocated space is sufficient.
|
||||
static const int kBigitCapacity = kMaxSignificantBits / kBigitSize;
|
||||
|
||||
void EnsureCapacity(int size) {
|
||||
if (size > kBigitCapacity) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
void Align(const Bignum& other);
|
||||
void Clamp();
|
||||
bool IsClamped() const;
|
||||
void Zero();
|
||||
// Requires this to have enough capacity (no tests done).
|
||||
// Updates used_digits_ if necessary.
|
||||
// shift_amount must be < kBigitSize.
|
||||
void BigitsShiftLeft(int shift_amount);
|
||||
// BigitLength includes the "hidden" digits encoded in the exponent.
|
||||
int BigitLength() const { return used_digits_ + exponent_; }
|
||||
Chunk BigitAt(int index) const;
|
||||
void SubtractTimes(const Bignum& other, int factor);
|
||||
|
||||
Chunk bigits_buffer_[kBigitCapacity];
|
||||
// A vector backed by bigits_buffer_. This way accesses to the array are
|
||||
// checked for out-of-bounds errors.
|
||||
Vector<Chunk> bigits_;
|
||||
int used_digits_;
|
||||
// The Bignum's value equals value(bigits_) * 2^(exponent_ * kBigitSize).
|
||||
int exponent_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Bignum);
|
||||
};
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_BIGNUM_H_
|
@ -1,176 +0,0 @@
|
||||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include "cached-powers.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
struct CachedPower {
|
||||
uint64_t significand;
|
||||
int16_t binary_exponent;
|
||||
int16_t decimal_exponent;
|
||||
};
|
||||
|
||||
static const CachedPower kCachedPowers[] = {
|
||||
{UINT64_2PART_C(0xfa8fd5a0, 081c0288), -1220, -348},
|
||||
{UINT64_2PART_C(0xbaaee17f, a23ebf76), -1193, -340},
|
||||
{UINT64_2PART_C(0x8b16fb20, 3055ac76), -1166, -332},
|
||||
{UINT64_2PART_C(0xcf42894a, 5dce35ea), -1140, -324},
|
||||
{UINT64_2PART_C(0x9a6bb0aa, 55653b2d), -1113, -316},
|
||||
{UINT64_2PART_C(0xe61acf03, 3d1a45df), -1087, -308},
|
||||
{UINT64_2PART_C(0xab70fe17, c79ac6ca), -1060, -300},
|
||||
{UINT64_2PART_C(0xff77b1fc, bebcdc4f), -1034, -292},
|
||||
{UINT64_2PART_C(0xbe5691ef, 416bd60c), -1007, -284},
|
||||
{UINT64_2PART_C(0x8dd01fad, 907ffc3c), -980, -276},
|
||||
{UINT64_2PART_C(0xd3515c28, 31559a83), -954, -268},
|
||||
{UINT64_2PART_C(0x9d71ac8f, ada6c9b5), -927, -260},
|
||||
{UINT64_2PART_C(0xea9c2277, 23ee8bcb), -901, -252},
|
||||
{UINT64_2PART_C(0xaecc4991, 4078536d), -874, -244},
|
||||
{UINT64_2PART_C(0x823c1279, 5db6ce57), -847, -236},
|
||||
{UINT64_2PART_C(0xc2109436, 4dfb5637), -821, -228},
|
||||
{UINT64_2PART_C(0x9096ea6f, 3848984f), -794, -220},
|
||||
{UINT64_2PART_C(0xd77485cb, 25823ac7), -768, -212},
|
||||
{UINT64_2PART_C(0xa086cfcd, 97bf97f4), -741, -204},
|
||||
{UINT64_2PART_C(0xef340a98, 172aace5), -715, -196},
|
||||
{UINT64_2PART_C(0xb23867fb, 2a35b28e), -688, -188},
|
||||
{UINT64_2PART_C(0x84c8d4df, d2c63f3b), -661, -180},
|
||||
{UINT64_2PART_C(0xc5dd4427, 1ad3cdba), -635, -172},
|
||||
{UINT64_2PART_C(0x936b9fce, bb25c996), -608, -164},
|
||||
{UINT64_2PART_C(0xdbac6c24, 7d62a584), -582, -156},
|
||||
{UINT64_2PART_C(0xa3ab6658, 0d5fdaf6), -555, -148},
|
||||
{UINT64_2PART_C(0xf3e2f893, dec3f126), -529, -140},
|
||||
{UINT64_2PART_C(0xb5b5ada8, aaff80b8), -502, -132},
|
||||
{UINT64_2PART_C(0x87625f05, 6c7c4a8b), -475, -124},
|
||||
{UINT64_2PART_C(0xc9bcff60, 34c13053), -449, -116},
|
||||
{UINT64_2PART_C(0x964e858c, 91ba2655), -422, -108},
|
||||
{UINT64_2PART_C(0xdff97724, 70297ebd), -396, -100},
|
||||
{UINT64_2PART_C(0xa6dfbd9f, b8e5b88f), -369, -92},
|
||||
{UINT64_2PART_C(0xf8a95fcf, 88747d94), -343, -84},
|
||||
{UINT64_2PART_C(0xb9447093, 8fa89bcf), -316, -76},
|
||||
{UINT64_2PART_C(0x8a08f0f8, bf0f156b), -289, -68},
|
||||
{UINT64_2PART_C(0xcdb02555, 653131b6), -263, -60},
|
||||
{UINT64_2PART_C(0x993fe2c6, d07b7fac), -236, -52},
|
||||
{UINT64_2PART_C(0xe45c10c4, 2a2b3b06), -210, -44},
|
||||
{UINT64_2PART_C(0xaa242499, 697392d3), -183, -36},
|
||||
{UINT64_2PART_C(0xfd87b5f2, 8300ca0e), -157, -28},
|
||||
{UINT64_2PART_C(0xbce50864, 92111aeb), -130, -20},
|
||||
{UINT64_2PART_C(0x8cbccc09, 6f5088cc), -103, -12},
|
||||
{UINT64_2PART_C(0xd1b71758, e219652c), -77, -4},
|
||||
{UINT64_2PART_C(0x9c400000, 00000000), -50, 4},
|
||||
{UINT64_2PART_C(0xe8d4a510, 00000000), -24, 12},
|
||||
{UINT64_2PART_C(0xad78ebc5, ac620000), 3, 20},
|
||||
{UINT64_2PART_C(0x813f3978, f8940984), 30, 28},
|
||||
{UINT64_2PART_C(0xc097ce7b, c90715b3), 56, 36},
|
||||
{UINT64_2PART_C(0x8f7e32ce, 7bea5c70), 83, 44},
|
||||
{UINT64_2PART_C(0xd5d238a4, abe98068), 109, 52},
|
||||
{UINT64_2PART_C(0x9f4f2726, 179a2245), 136, 60},
|
||||
{UINT64_2PART_C(0xed63a231, d4c4fb27), 162, 68},
|
||||
{UINT64_2PART_C(0xb0de6538, 8cc8ada8), 189, 76},
|
||||
{UINT64_2PART_C(0x83c7088e, 1aab65db), 216, 84},
|
||||
{UINT64_2PART_C(0xc45d1df9, 42711d9a), 242, 92},
|
||||
{UINT64_2PART_C(0x924d692c, a61be758), 269, 100},
|
||||
{UINT64_2PART_C(0xda01ee64, 1a708dea), 295, 108},
|
||||
{UINT64_2PART_C(0xa26da399, 9aef774a), 322, 116},
|
||||
{UINT64_2PART_C(0xf209787b, b47d6b85), 348, 124},
|
||||
{UINT64_2PART_C(0xb454e4a1, 79dd1877), 375, 132},
|
||||
{UINT64_2PART_C(0x865b8692, 5b9bc5c2), 402, 140},
|
||||
{UINT64_2PART_C(0xc83553c5, c8965d3d), 428, 148},
|
||||
{UINT64_2PART_C(0x952ab45c, fa97a0b3), 455, 156},
|
||||
{UINT64_2PART_C(0xde469fbd, 99a05fe3), 481, 164},
|
||||
{UINT64_2PART_C(0xa59bc234, db398c25), 508, 172},
|
||||
{UINT64_2PART_C(0xf6c69a72, a3989f5c), 534, 180},
|
||||
{UINT64_2PART_C(0xb7dcbf53, 54e9bece), 561, 188},
|
||||
{UINT64_2PART_C(0x88fcf317, f22241e2), 588, 196},
|
||||
{UINT64_2PART_C(0xcc20ce9b, d35c78a5), 614, 204},
|
||||
{UINT64_2PART_C(0x98165af3, 7b2153df), 641, 212},
|
||||
{UINT64_2PART_C(0xe2a0b5dc, 971f303a), 667, 220},
|
||||
{UINT64_2PART_C(0xa8d9d153, 5ce3b396), 694, 228},
|
||||
{UINT64_2PART_C(0xfb9b7cd9, a4a7443c), 720, 236},
|
||||
{UINT64_2PART_C(0xbb764c4c, a7a44410), 747, 244},
|
||||
{UINT64_2PART_C(0x8bab8eef, b6409c1a), 774, 252},
|
||||
{UINT64_2PART_C(0xd01fef10, a657842c), 800, 260},
|
||||
{UINT64_2PART_C(0x9b10a4e5, e9913129), 827, 268},
|
||||
{UINT64_2PART_C(0xe7109bfb, a19c0c9d), 853, 276},
|
||||
{UINT64_2PART_C(0xac2820d9, 623bf429), 880, 284},
|
||||
{UINT64_2PART_C(0x80444b5e, 7aa7cf85), 907, 292},
|
||||
{UINT64_2PART_C(0xbf21e440, 03acdd2d), 933, 300},
|
||||
{UINT64_2PART_C(0x8e679c2f, 5e44ff8f), 960, 308},
|
||||
{UINT64_2PART_C(0xd433179d, 9c8cb841), 986, 316},
|
||||
{UINT64_2PART_C(0x9e19db92, b4e31ba9), 1013, 324},
|
||||
{UINT64_2PART_C(0xeb96bf6e, badf77d9), 1039, 332},
|
||||
{UINT64_2PART_C(0xaf87023b, 9bf0ee6b), 1066, 340},
|
||||
};
|
||||
|
||||
static const int kCachedPowersLength = ARRAY_SIZE(kCachedPowers);
|
||||
static const int kCachedPowersOffset = 348; // -1 * the first decimal_exponent.
|
||||
static const double kD_1_LOG2_10 = 0.30102999566398114; // 1 / lg(10)
|
||||
// Difference between the decimal exponents in the table above.
|
||||
const int PowersOfTenCache::kDecimalExponentDistance = 8;
|
||||
const int PowersOfTenCache::kMinDecimalExponent = -348;
|
||||
const int PowersOfTenCache::kMaxDecimalExponent = 340;
|
||||
|
||||
void PowersOfTenCache::GetCachedPowerForBinaryExponentRange(
|
||||
int min_exponent,
|
||||
int max_exponent,
|
||||
DiyFp* power,
|
||||
int* decimal_exponent) {
|
||||
int kQ = DiyFp::kSignificandSize;
|
||||
double k = ceil((min_exponent + kQ - 1) * kD_1_LOG2_10);
|
||||
int foo = kCachedPowersOffset;
|
||||
int index =
|
||||
(foo + static_cast<int>(k) - 1) / kDecimalExponentDistance + 1;
|
||||
ASSERT(0 <= index && index < kCachedPowersLength);
|
||||
CachedPower cached_power = kCachedPowers[index];
|
||||
ASSERT(min_exponent <= cached_power.binary_exponent);
|
||||
(void) max_exponent; // Mark variable as used.
|
||||
ASSERT(cached_power.binary_exponent <= max_exponent);
|
||||
*decimal_exponent = cached_power.decimal_exponent;
|
||||
*power = DiyFp(cached_power.significand, cached_power.binary_exponent);
|
||||
}
|
||||
|
||||
|
||||
void PowersOfTenCache::GetCachedPowerForDecimalExponent(int requested_exponent,
|
||||
DiyFp* power,
|
||||
int* found_exponent) {
|
||||
ASSERT(kMinDecimalExponent <= requested_exponent);
|
||||
ASSERT(requested_exponent < kMaxDecimalExponent + kDecimalExponentDistance);
|
||||
int index =
|
||||
(requested_exponent + kCachedPowersOffset) / kDecimalExponentDistance;
|
||||
CachedPower cached_power = kCachedPowers[index];
|
||||
*power = DiyFp(cached_power.significand, cached_power.binary_exponent);
|
||||
*found_exponent = cached_power.decimal_exponent;
|
||||
ASSERT(*found_exponent <= requested_exponent);
|
||||
ASSERT(requested_exponent < *found_exponent + kDecimalExponentDistance);
|
||||
}
|
||||
|
||||
} // namespace double_conversion
|
@ -1,64 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_CACHED_POWERS_H_
|
||||
#define DOUBLE_CONVERSION_CACHED_POWERS_H_
|
||||
|
||||
#include "diy-fp.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
class PowersOfTenCache {
|
||||
public:
|
||||
|
||||
// Not all powers of ten are cached. The decimal exponent of two neighboring
|
||||
// cached numbers will differ by kDecimalExponentDistance.
|
||||
static const int kDecimalExponentDistance;
|
||||
|
||||
static const int kMinDecimalExponent;
|
||||
static const int kMaxDecimalExponent;
|
||||
|
||||
// Returns a cached power-of-ten with a binary exponent in the range
|
||||
// [min_exponent; max_exponent] (boundaries included).
|
||||
static void GetCachedPowerForBinaryExponentRange(int min_exponent,
|
||||
int max_exponent,
|
||||
DiyFp* power,
|
||||
int* decimal_exponent);
|
||||
|
||||
// Returns a cached power of ten x ~= 10^k such that
|
||||
// k <= decimal_exponent < k + kCachedPowersDecimalDistance.
|
||||
// The given decimal_exponent must satisfy
|
||||
// kMinDecimalExponent <= requested_exponent, and
|
||||
// requested_exponent < kMaxDecimalExponent + kDecimalExponentDistance.
|
||||
static void GetCachedPowerForDecimalExponent(int requested_exponent,
|
||||
DiyFp* power,
|
||||
int* found_exponent);
|
||||
};
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_CACHED_POWERS_H_
|
@ -1,57 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#include "diy-fp.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
void DiyFp::Multiply(const DiyFp& other) {
|
||||
// Simply "emulates" a 128 bit multiplication.
|
||||
// However: the resulting number only contains 64 bits. The least
|
||||
// significant 64 bits are only used for rounding the most significant 64
|
||||
// bits.
|
||||
const uint64_t kM32 = 0xFFFFFFFFU;
|
||||
uint64_t a = f_ >> 32;
|
||||
uint64_t b = f_ & kM32;
|
||||
uint64_t c = other.f_ >> 32;
|
||||
uint64_t d = other.f_ & kM32;
|
||||
uint64_t ac = a * c;
|
||||
uint64_t bc = b * c;
|
||||
uint64_t ad = a * d;
|
||||
uint64_t bd = b * d;
|
||||
uint64_t tmp = (bd >> 32) + (ad & kM32) + (bc & kM32);
|
||||
// By adding 1U << 31 to tmp we round the final result.
|
||||
// Halfway cases will be round up.
|
||||
tmp += 1U << 31;
|
||||
uint64_t result_f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32);
|
||||
e_ += other.e_ + 64;
|
||||
f_ = result_f;
|
||||
}
|
||||
|
||||
} // namespace double_conversion
|
118
ios/Pods/DoubleConversion/double-conversion/diy-fp.h
generated
118
ios/Pods/DoubleConversion/double-conversion/diy-fp.h
generated
@ -1,118 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_DIY_FP_H_
|
||||
#define DOUBLE_CONVERSION_DIY_FP_H_
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
// This "Do It Yourself Floating Point" class implements a floating-point number
|
||||
// with a uint64 significand and an int exponent. Normalized DiyFp numbers will
|
||||
// have the most significant bit of the significand set.
|
||||
// Multiplication and Subtraction do not normalize their results.
|
||||
// DiyFp are not designed to contain special doubles (NaN and Infinity).
|
||||
class DiyFp {
|
||||
public:
|
||||
static const int kSignificandSize = 64;
|
||||
|
||||
DiyFp() : f_(0), e_(0) {}
|
||||
DiyFp(uint64_t f, int e) : f_(f), e_(e) {}
|
||||
|
||||
// this = this - other.
|
||||
// The exponents of both numbers must be the same and the significand of this
|
||||
// must be bigger than the significand of other.
|
||||
// The result will not be normalized.
|
||||
void Subtract(const DiyFp& other) {
|
||||
ASSERT(e_ == other.e_);
|
||||
ASSERT(f_ >= other.f_);
|
||||
f_ -= other.f_;
|
||||
}
|
||||
|
||||
// Returns a - b.
|
||||
// The exponents of both numbers must be the same and this must be bigger
|
||||
// than other. The result will not be normalized.
|
||||
static DiyFp Minus(const DiyFp& a, const DiyFp& b) {
|
||||
DiyFp result = a;
|
||||
result.Subtract(b);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// this = this * other.
|
||||
void Multiply(const DiyFp& other);
|
||||
|
||||
// returns a * b;
|
||||
static DiyFp Times(const DiyFp& a, const DiyFp& b) {
|
||||
DiyFp result = a;
|
||||
result.Multiply(b);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Normalize() {
|
||||
ASSERT(f_ != 0);
|
||||
uint64_t f = f_;
|
||||
int e = e_;
|
||||
|
||||
// This method is mainly called for normalizing boundaries. In general
|
||||
// boundaries need to be shifted by 10 bits. We thus optimize for this case.
|
||||
const uint64_t k10MSBits = UINT64_2PART_C(0xFFC00000, 00000000);
|
||||
while ((f & k10MSBits) == 0) {
|
||||
f <<= 10;
|
||||
e -= 10;
|
||||
}
|
||||
while ((f & kUint64MSB) == 0) {
|
||||
f <<= 1;
|
||||
e--;
|
||||
}
|
||||
f_ = f;
|
||||
e_ = e;
|
||||
}
|
||||
|
||||
static DiyFp Normalize(const DiyFp& a) {
|
||||
DiyFp result = a;
|
||||
result.Normalize();
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t f() const { return f_; }
|
||||
int e() const { return e_; }
|
||||
|
||||
void set_f(uint64_t new_value) { f_ = new_value; }
|
||||
void set_e(int new_value) { e_ = new_value; }
|
||||
|
||||
private:
|
||||
static const uint64_t kUint64MSB = UINT64_2PART_C(0x80000000, 00000000);
|
||||
|
||||
uint64_t f_;
|
||||
int e_;
|
||||
};
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_DIY_FP_H_
|
@ -1,910 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "double-conversion.h"
|
||||
|
||||
#include "bignum-dtoa.h"
|
||||
#include "fast-dtoa.h"
|
||||
#include "fixed-dtoa.h"
|
||||
#include "ieee.h"
|
||||
#include "strtod.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
const DoubleToStringConverter& DoubleToStringConverter::EcmaScriptConverter() {
|
||||
int flags = UNIQUE_ZERO | EMIT_POSITIVE_EXPONENT_SIGN;
|
||||
static DoubleToStringConverter converter(flags,
|
||||
"Infinity",
|
||||
"NaN",
|
||||
'e',
|
||||
-6, 21,
|
||||
6, 0);
|
||||
return converter;
|
||||
}
|
||||
|
||||
|
||||
bool DoubleToStringConverter::HandleSpecialValues(
|
||||
double value,
|
||||
StringBuilder* result_builder) const {
|
||||
Double double_inspect(value);
|
||||
if (double_inspect.IsInfinite()) {
|
||||
if (infinity_symbol_ == NULL) return false;
|
||||
if (value < 0) {
|
||||
result_builder->AddCharacter('-');
|
||||
}
|
||||
result_builder->AddString(infinity_symbol_);
|
||||
return true;
|
||||
}
|
||||
if (double_inspect.IsNan()) {
|
||||
if (nan_symbol_ == NULL) return false;
|
||||
result_builder->AddString(nan_symbol_);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void DoubleToStringConverter::CreateExponentialRepresentation(
|
||||
const char* decimal_digits,
|
||||
int length,
|
||||
int exponent,
|
||||
StringBuilder* result_builder) const {
|
||||
ASSERT(length != 0);
|
||||
result_builder->AddCharacter(decimal_digits[0]);
|
||||
if (length != 1) {
|
||||
result_builder->AddCharacter('.');
|
||||
result_builder->AddSubstring(&decimal_digits[1], length-1);
|
||||
}
|
||||
result_builder->AddCharacter(exponent_character_);
|
||||
if (exponent < 0) {
|
||||
result_builder->AddCharacter('-');
|
||||
exponent = -exponent;
|
||||
} else {
|
||||
if ((flags_ & EMIT_POSITIVE_EXPONENT_SIGN) != 0) {
|
||||
result_builder->AddCharacter('+');
|
||||
}
|
||||
}
|
||||
if (exponent == 0) {
|
||||
result_builder->AddCharacter('0');
|
||||
return;
|
||||
}
|
||||
ASSERT(exponent < 1e4);
|
||||
const int kMaxExponentLength = 5;
|
||||
char buffer[kMaxExponentLength + 1];
|
||||
buffer[kMaxExponentLength] = '\0';
|
||||
int first_char_pos = kMaxExponentLength;
|
||||
while (exponent > 0) {
|
||||
buffer[--first_char_pos] = '0' + (exponent % 10);
|
||||
exponent /= 10;
|
||||
}
|
||||
result_builder->AddSubstring(&buffer[first_char_pos],
|
||||
kMaxExponentLength - first_char_pos);
|
||||
}
|
||||
|
||||
|
||||
void DoubleToStringConverter::CreateDecimalRepresentation(
|
||||
const char* decimal_digits,
|
||||
int length,
|
||||
int decimal_point,
|
||||
int digits_after_point,
|
||||
StringBuilder* result_builder) const {
|
||||
// Create a representation that is padded with zeros if needed.
|
||||
if (decimal_point <= 0) {
|
||||
// "0.00000decimal_rep".
|
||||
result_builder->AddCharacter('0');
|
||||
if (digits_after_point > 0) {
|
||||
result_builder->AddCharacter('.');
|
||||
result_builder->AddPadding('0', -decimal_point);
|
||||
ASSERT(length <= digits_after_point - (-decimal_point));
|
||||
result_builder->AddSubstring(decimal_digits, length);
|
||||
int remaining_digits = digits_after_point - (-decimal_point) - length;
|
||||
result_builder->AddPadding('0', remaining_digits);
|
||||
}
|
||||
} else if (decimal_point >= length) {
|
||||
// "decimal_rep0000.00000" or "decimal_rep.0000"
|
||||
result_builder->AddSubstring(decimal_digits, length);
|
||||
result_builder->AddPadding('0', decimal_point - length);
|
||||
if (digits_after_point > 0) {
|
||||
result_builder->AddCharacter('.');
|
||||
result_builder->AddPadding('0', digits_after_point);
|
||||
}
|
||||
} else {
|
||||
// "decima.l_rep000"
|
||||
ASSERT(digits_after_point > 0);
|
||||
result_builder->AddSubstring(decimal_digits, decimal_point);
|
||||
result_builder->AddCharacter('.');
|
||||
ASSERT(length - decimal_point <= digits_after_point);
|
||||
result_builder->AddSubstring(&decimal_digits[decimal_point],
|
||||
length - decimal_point);
|
||||
int remaining_digits = digits_after_point - (length - decimal_point);
|
||||
result_builder->AddPadding('0', remaining_digits);
|
||||
}
|
||||
if (digits_after_point == 0) {
|
||||
if ((flags_ & EMIT_TRAILING_DECIMAL_POINT) != 0) {
|
||||
result_builder->AddCharacter('.');
|
||||
}
|
||||
if ((flags_ & EMIT_TRAILING_ZERO_AFTER_POINT) != 0) {
|
||||
result_builder->AddCharacter('0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DoubleToStringConverter::ToShortestIeeeNumber(
|
||||
double value,
|
||||
StringBuilder* result_builder,
|
||||
DoubleToStringConverter::DtoaMode mode) const {
|
||||
ASSERT(mode == SHORTEST || mode == SHORTEST_SINGLE);
|
||||
if (Double(value).IsSpecial()) {
|
||||
return HandleSpecialValues(value, result_builder);
|
||||
}
|
||||
|
||||
int decimal_point;
|
||||
bool sign;
|
||||
const int kDecimalRepCapacity = kBase10MaximalLength + 1;
|
||||
char decimal_rep[kDecimalRepCapacity];
|
||||
int decimal_rep_length;
|
||||
|
||||
DoubleToAscii(value, mode, 0, decimal_rep, kDecimalRepCapacity,
|
||||
&sign, &decimal_rep_length, &decimal_point);
|
||||
|
||||
bool unique_zero = (flags_ & UNIQUE_ZERO) != 0;
|
||||
if (sign && (value != 0.0 || !unique_zero)) {
|
||||
result_builder->AddCharacter('-');
|
||||
}
|
||||
|
||||
int exponent = decimal_point - 1;
|
||||
if ((decimal_in_shortest_low_ <= exponent) &&
|
||||
(exponent < decimal_in_shortest_high_)) {
|
||||
CreateDecimalRepresentation(decimal_rep, decimal_rep_length,
|
||||
decimal_point,
|
||||
Max(0, decimal_rep_length - decimal_point),
|
||||
result_builder);
|
||||
} else {
|
||||
CreateExponentialRepresentation(decimal_rep, decimal_rep_length, exponent,
|
||||
result_builder);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DoubleToStringConverter::ToFixed(double value,
|
||||
int requested_digits,
|
||||
StringBuilder* result_builder) const {
|
||||
ASSERT(kMaxFixedDigitsBeforePoint == 60);
|
||||
const double kFirstNonFixed = 1e60;
|
||||
|
||||
if (Double(value).IsSpecial()) {
|
||||
return HandleSpecialValues(value, result_builder);
|
||||
}
|
||||
|
||||
if (requested_digits > kMaxFixedDigitsAfterPoint) return false;
|
||||
if (value >= kFirstNonFixed || value <= -kFirstNonFixed) return false;
|
||||
|
||||
// Find a sufficiently precise decimal representation of n.
|
||||
int decimal_point;
|
||||
bool sign;
|
||||
// Add space for the '\0' byte.
|
||||
const int kDecimalRepCapacity =
|
||||
kMaxFixedDigitsBeforePoint + kMaxFixedDigitsAfterPoint + 1;
|
||||
char decimal_rep[kDecimalRepCapacity];
|
||||
int decimal_rep_length;
|
||||
DoubleToAscii(value, FIXED, requested_digits,
|
||||
decimal_rep, kDecimalRepCapacity,
|
||||
&sign, &decimal_rep_length, &decimal_point);
|
||||
|
||||
bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0);
|
||||
if (sign && (value != 0.0 || !unique_zero)) {
|
||||
result_builder->AddCharacter('-');
|
||||
}
|
||||
|
||||
CreateDecimalRepresentation(decimal_rep, decimal_rep_length, decimal_point,
|
||||
requested_digits, result_builder);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DoubleToStringConverter::ToExponential(
|
||||
double value,
|
||||
int requested_digits,
|
||||
StringBuilder* result_builder) const {
|
||||
if (Double(value).IsSpecial()) {
|
||||
return HandleSpecialValues(value, result_builder);
|
||||
}
|
||||
|
||||
if (requested_digits < -1) return false;
|
||||
if (requested_digits > kMaxExponentialDigits) return false;
|
||||
|
||||
int decimal_point;
|
||||
bool sign;
|
||||
// Add space for digit before the decimal point and the '\0' character.
|
||||
const int kDecimalRepCapacity = kMaxExponentialDigits + 2;
|
||||
ASSERT(kDecimalRepCapacity > kBase10MaximalLength);
|
||||
char decimal_rep[kDecimalRepCapacity];
|
||||
int decimal_rep_length;
|
||||
|
||||
if (requested_digits == -1) {
|
||||
DoubleToAscii(value, SHORTEST, 0,
|
||||
decimal_rep, kDecimalRepCapacity,
|
||||
&sign, &decimal_rep_length, &decimal_point);
|
||||
} else {
|
||||
DoubleToAscii(value, PRECISION, requested_digits + 1,
|
||||
decimal_rep, kDecimalRepCapacity,
|
||||
&sign, &decimal_rep_length, &decimal_point);
|
||||
ASSERT(decimal_rep_length <= requested_digits + 1);
|
||||
|
||||
for (int i = decimal_rep_length; i < requested_digits + 1; ++i) {
|
||||
decimal_rep[i] = '0';
|
||||
}
|
||||
decimal_rep_length = requested_digits + 1;
|
||||
}
|
||||
|
||||
bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0);
|
||||
if (sign && (value != 0.0 || !unique_zero)) {
|
||||
result_builder->AddCharacter('-');
|
||||
}
|
||||
|
||||
int exponent = decimal_point - 1;
|
||||
CreateExponentialRepresentation(decimal_rep,
|
||||
decimal_rep_length,
|
||||
exponent,
|
||||
result_builder);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DoubleToStringConverter::ToPrecision(double value,
|
||||
int precision,
|
||||
StringBuilder* result_builder) const {
|
||||
if (Double(value).IsSpecial()) {
|
||||
return HandleSpecialValues(value, result_builder);
|
||||
}
|
||||
|
||||
if (precision < kMinPrecisionDigits || precision > kMaxPrecisionDigits) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find a sufficiently precise decimal representation of n.
|
||||
int decimal_point;
|
||||
bool sign;
|
||||
// Add one for the terminating null character.
|
||||
const int kDecimalRepCapacity = kMaxPrecisionDigits + 1;
|
||||
char decimal_rep[kDecimalRepCapacity];
|
||||
int decimal_rep_length;
|
||||
|
||||
DoubleToAscii(value, PRECISION, precision,
|
||||
decimal_rep, kDecimalRepCapacity,
|
||||
&sign, &decimal_rep_length, &decimal_point);
|
||||
ASSERT(decimal_rep_length <= precision);
|
||||
|
||||
bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0);
|
||||
if (sign && (value != 0.0 || !unique_zero)) {
|
||||
result_builder->AddCharacter('-');
|
||||
}
|
||||
|
||||
// The exponent if we print the number as x.xxeyyy. That is with the
|
||||
// decimal point after the first digit.
|
||||
int exponent = decimal_point - 1;
|
||||
|
||||
int extra_zero = ((flags_ & EMIT_TRAILING_ZERO_AFTER_POINT) != 0) ? 1 : 0;
|
||||
if ((-decimal_point + 1 > max_leading_padding_zeroes_in_precision_mode_) ||
|
||||
(decimal_point - precision + extra_zero >
|
||||
max_trailing_padding_zeroes_in_precision_mode_)) {
|
||||
// Fill buffer to contain 'precision' digits.
|
||||
// Usually the buffer is already at the correct length, but 'DoubleToAscii'
|
||||
// is allowed to return less characters.
|
||||
for (int i = decimal_rep_length; i < precision; ++i) {
|
||||
decimal_rep[i] = '0';
|
||||
}
|
||||
|
||||
CreateExponentialRepresentation(decimal_rep,
|
||||
precision,
|
||||
exponent,
|
||||
result_builder);
|
||||
} else {
|
||||
CreateDecimalRepresentation(decimal_rep, decimal_rep_length, decimal_point,
|
||||
Max(0, precision - decimal_point),
|
||||
result_builder);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static BignumDtoaMode DtoaToBignumDtoaMode(
|
||||
DoubleToStringConverter::DtoaMode dtoa_mode) {
|
||||
switch (dtoa_mode) {
|
||||
case DoubleToStringConverter::SHORTEST: return BIGNUM_DTOA_SHORTEST;
|
||||
case DoubleToStringConverter::SHORTEST_SINGLE:
|
||||
return BIGNUM_DTOA_SHORTEST_SINGLE;
|
||||
case DoubleToStringConverter::FIXED: return BIGNUM_DTOA_FIXED;
|
||||
case DoubleToStringConverter::PRECISION: return BIGNUM_DTOA_PRECISION;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DoubleToStringConverter::DoubleToAscii(double v,
|
||||
DtoaMode mode,
|
||||
int requested_digits,
|
||||
char* buffer,
|
||||
int buffer_length,
|
||||
bool* sign,
|
||||
int* length,
|
||||
int* point) {
|
||||
Vector<char> vector(buffer, buffer_length);
|
||||
ASSERT(!Double(v).IsSpecial());
|
||||
ASSERT(mode == SHORTEST || mode == SHORTEST_SINGLE || requested_digits >= 0);
|
||||
|
||||
if (Double(v).Sign() < 0) {
|
||||
*sign = true;
|
||||
v = -v;
|
||||
} else {
|
||||
*sign = false;
|
||||
}
|
||||
|
||||
if (mode == PRECISION && requested_digits == 0) {
|
||||
vector[0] = '\0';
|
||||
*length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (v == 0) {
|
||||
vector[0] = '0';
|
||||
vector[1] = '\0';
|
||||
*length = 1;
|
||||
*point = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
bool fast_worked;
|
||||
switch (mode) {
|
||||
case SHORTEST:
|
||||
fast_worked = FastDtoa(v, FAST_DTOA_SHORTEST, 0, vector, length, point);
|
||||
break;
|
||||
case SHORTEST_SINGLE:
|
||||
fast_worked = FastDtoa(v, FAST_DTOA_SHORTEST_SINGLE, 0,
|
||||
vector, length, point);
|
||||
break;
|
||||
case FIXED:
|
||||
fast_worked = FastFixedDtoa(v, requested_digits, vector, length, point);
|
||||
break;
|
||||
case PRECISION:
|
||||
fast_worked = FastDtoa(v, FAST_DTOA_PRECISION, requested_digits,
|
||||
vector, length, point);
|
||||
break;
|
||||
default:
|
||||
fast_worked = false;
|
||||
UNREACHABLE();
|
||||
}
|
||||
if (fast_worked) return;
|
||||
|
||||
// If the fast dtoa didn't succeed use the slower bignum version.
|
||||
BignumDtoaMode bignum_mode = DtoaToBignumDtoaMode(mode);
|
||||
BignumDtoa(v, bignum_mode, requested_digits, vector, length, point);
|
||||
vector[*length] = '\0';
|
||||
}
|
||||
|
||||
|
||||
// Consumes the given substring from the iterator.
|
||||
// Returns false, if the substring does not match.
|
||||
static bool ConsumeSubString(const char** current,
|
||||
const char* end,
|
||||
const char* substring) {
|
||||
ASSERT(**current == *substring);
|
||||
for (substring++; *substring != '\0'; substring++) {
|
||||
++*current;
|
||||
if (*current == end || **current != *substring) return false;
|
||||
}
|
||||
++*current;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Maximum number of significant digits in decimal representation.
|
||||
// The longest possible double in decimal representation is
|
||||
// (2^53 - 1) * 2 ^ -1074 that is (2 ^ 53 - 1) * 5 ^ 1074 / 10 ^ 1074
|
||||
// (768 digits). If we parse a number whose first digits are equal to a
|
||||
// mean of 2 adjacent doubles (that could have up to 769 digits) the result
|
||||
// must be rounded to the bigger one unless the tail consists of zeros, so
|
||||
// we don't need to preserve all the digits.
|
||||
const int kMaxSignificantDigits = 772;
|
||||
|
||||
|
||||
// Returns true if a nonspace found and false if the end has reached.
|
||||
static inline bool AdvanceToNonspace(const char** current, const char* end) {
|
||||
while (*current != end) {
|
||||
if (**current != ' ') return true;
|
||||
++*current;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool isDigit(int x, int radix) {
|
||||
return (x >= '0' && x <= '9' && x < '0' + radix)
|
||||
|| (radix > 10 && x >= 'a' && x < 'a' + radix - 10)
|
||||
|| (radix > 10 && x >= 'A' && x < 'A' + radix - 10);
|
||||
}
|
||||
|
||||
|
||||
static double SignedZero(bool sign) {
|
||||
return sign ? -0.0 : 0.0;
|
||||
}
|
||||
|
||||
|
||||
// Returns true if 'c' is a decimal digit that is valid for the given radix.
|
||||
//
|
||||
// The function is small and could be inlined, but VS2012 emitted a warning
|
||||
// because it constant-propagated the radix and concluded that the last
|
||||
// condition was always true. By moving it into a separate function the
|
||||
// compiler wouldn't warn anymore.
|
||||
static bool IsDecimalDigitForRadix(int c, int radix) {
|
||||
return '0' <= c && c <= '9' && (c - '0') < radix;
|
||||
}
|
||||
|
||||
// Returns true if 'c' is a character digit that is valid for the given radix.
|
||||
// The 'a_character' should be 'a' or 'A'.
|
||||
//
|
||||
// The function is small and could be inlined, but VS2012 emitted a warning
|
||||
// because it constant-propagated the radix and concluded that the first
|
||||
// condition was always false. By moving it into a separate function the
|
||||
// compiler wouldn't warn anymore.
|
||||
static bool IsCharacterDigitForRadix(int c, int radix, char a_character) {
|
||||
return radix > 10 && c >= a_character && c < a_character + radix - 10;
|
||||
}
|
||||
|
||||
|
||||
// Parsing integers with radix 2, 4, 8, 16, 32. Assumes current != end.
|
||||
template <int radix_log_2>
|
||||
static double RadixStringToIeee(const char* current,
|
||||
const char* end,
|
||||
bool sign,
|
||||
bool allow_trailing_junk,
|
||||
double junk_string_value,
|
||||
bool read_as_double,
|
||||
const char** trailing_pointer) {
|
||||
ASSERT(current != end);
|
||||
|
||||
const int kDoubleSize = Double::kSignificandSize;
|
||||
const int kSingleSize = Single::kSignificandSize;
|
||||
const int kSignificandSize = read_as_double? kDoubleSize: kSingleSize;
|
||||
|
||||
// Skip leading 0s.
|
||||
while (*current == '0') {
|
||||
++current;
|
||||
if (current == end) {
|
||||
*trailing_pointer = end;
|
||||
return SignedZero(sign);
|
||||
}
|
||||
}
|
||||
|
||||
int64_t number = 0;
|
||||
int exponent = 0;
|
||||
const int radix = (1 << radix_log_2);
|
||||
|
||||
do {
|
||||
int digit;
|
||||
if (IsDecimalDigitForRadix(*current, radix)) {
|
||||
digit = static_cast<char>(*current) - '0';
|
||||
} else if (IsCharacterDigitForRadix(*current, radix, 'a')) {
|
||||
digit = static_cast<char>(*current) - 'a' + 10;
|
||||
} else if (IsCharacterDigitForRadix(*current, radix, 'A')) {
|
||||
digit = static_cast<char>(*current) - 'A' + 10;
|
||||
} else {
|
||||
if (allow_trailing_junk || !AdvanceToNonspace(¤t, end)) {
|
||||
break;
|
||||
} else {
|
||||
return junk_string_value;
|
||||
}
|
||||
}
|
||||
|
||||
number = number * radix + digit;
|
||||
int overflow = static_cast<int>(number >> kSignificandSize);
|
||||
if (overflow != 0) {
|
||||
// Overflow occurred. Need to determine which direction to round the
|
||||
// result.
|
||||
int overflow_bits_count = 1;
|
||||
while (overflow > 1) {
|
||||
overflow_bits_count++;
|
||||
overflow >>= 1;
|
||||
}
|
||||
|
||||
int dropped_bits_mask = ((1 << overflow_bits_count) - 1);
|
||||
int dropped_bits = static_cast<int>(number) & dropped_bits_mask;
|
||||
number >>= overflow_bits_count;
|
||||
exponent = overflow_bits_count;
|
||||
|
||||
bool zero_tail = true;
|
||||
for (;;) {
|
||||
++current;
|
||||
if (current == end || !isDigit(*current, radix)) break;
|
||||
zero_tail = zero_tail && *current == '0';
|
||||
exponent += radix_log_2;
|
||||
}
|
||||
|
||||
if (!allow_trailing_junk && AdvanceToNonspace(¤t, end)) {
|
||||
return junk_string_value;
|
||||
}
|
||||
|
||||
int middle_value = (1 << (overflow_bits_count - 1));
|
||||
if (dropped_bits > middle_value) {
|
||||
number++; // Rounding up.
|
||||
} else if (dropped_bits == middle_value) {
|
||||
// Rounding to even to consistency with decimals: half-way case rounds
|
||||
// up if significant part is odd and down otherwise.
|
||||
if ((number & 1) != 0 || !zero_tail) {
|
||||
number++; // Rounding up.
|
||||
}
|
||||
}
|
||||
|
||||
// Rounding up may cause overflow.
|
||||
if ((number & ((int64_t)1 << kSignificandSize)) != 0) {
|
||||
exponent++;
|
||||
number >>= 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
++current;
|
||||
} while (current != end);
|
||||
|
||||
ASSERT(number < ((int64_t)1 << kSignificandSize));
|
||||
ASSERT(static_cast<int64_t>(static_cast<double>(number)) == number);
|
||||
|
||||
*trailing_pointer = current;
|
||||
|
||||
if (exponent == 0) {
|
||||
if (sign) {
|
||||
if (number == 0) return -0.0;
|
||||
number = -number;
|
||||
}
|
||||
return static_cast<double>(number);
|
||||
}
|
||||
|
||||
ASSERT(number != 0);
|
||||
return Double(DiyFp(number, exponent)).value();
|
||||
}
|
||||
|
||||
|
||||
double StringToDoubleConverter::StringToIeee(
|
||||
const char* input,
|
||||
int length,
|
||||
int* processed_characters_count,
|
||||
bool read_as_double) const {
|
||||
const char* current = input;
|
||||
const char* end = input + length;
|
||||
|
||||
*processed_characters_count = 0;
|
||||
|
||||
const bool allow_trailing_junk = (flags_ & ALLOW_TRAILING_JUNK) != 0;
|
||||
const bool allow_leading_spaces = (flags_ & ALLOW_LEADING_SPACES) != 0;
|
||||
const bool allow_trailing_spaces = (flags_ & ALLOW_TRAILING_SPACES) != 0;
|
||||
const bool allow_spaces_after_sign = (flags_ & ALLOW_SPACES_AFTER_SIGN) != 0;
|
||||
|
||||
// To make sure that iterator dereferencing is valid the following
|
||||
// convention is used:
|
||||
// 1. Each '++current' statement is followed by check for equality to 'end'.
|
||||
// 2. If AdvanceToNonspace returned false then current == end.
|
||||
// 3. If 'current' becomes equal to 'end' the function returns or goes to
|
||||
// 'parsing_done'.
|
||||
// 4. 'current' is not dereferenced after the 'parsing_done' label.
|
||||
// 5. Code before 'parsing_done' may rely on 'current != end'.
|
||||
if (current == end) return empty_string_value_;
|
||||
|
||||
if (allow_leading_spaces || allow_trailing_spaces) {
|
||||
if (!AdvanceToNonspace(¤t, end)) {
|
||||
*processed_characters_count = static_cast<int>(current - input);
|
||||
return empty_string_value_;
|
||||
}
|
||||
if (!allow_leading_spaces && (input != current)) {
|
||||
// No leading spaces allowed, but AdvanceToNonspace moved forward.
|
||||
return junk_string_value_;
|
||||
}
|
||||
}
|
||||
|
||||
// The longest form of simplified number is: "-<significant digits>.1eXXX\0".
|
||||
const int kBufferSize = kMaxSignificantDigits + 10;
|
||||
char buffer[kBufferSize]; // NOLINT: size is known at compile time.
|
||||
int buffer_pos = 0;
|
||||
|
||||
// Exponent will be adjusted if insignificant digits of the integer part
|
||||
// or insignificant leading zeros of the fractional part are dropped.
|
||||
int exponent = 0;
|
||||
int significant_digits = 0;
|
||||
int insignificant_digits = 0;
|
||||
bool nonzero_digit_dropped = false;
|
||||
|
||||
bool sign = false;
|
||||
|
||||
if (*current == '+' || *current == '-') {
|
||||
sign = (*current == '-');
|
||||
++current;
|
||||
const char* next_non_space = current;
|
||||
// Skip following spaces (if allowed).
|
||||
if (!AdvanceToNonspace(&next_non_space, end)) return junk_string_value_;
|
||||
if (!allow_spaces_after_sign && (current != next_non_space)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
current = next_non_space;
|
||||
}
|
||||
|
||||
if (infinity_symbol_ != NULL) {
|
||||
if (*current == infinity_symbol_[0]) {
|
||||
if (!ConsumeSubString(¤t, end, infinity_symbol_)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
|
||||
if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
if (!allow_trailing_junk && AdvanceToNonspace(¤t, end)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
|
||||
ASSERT(buffer_pos == 0);
|
||||
*processed_characters_count = static_cast<int>(current - input);
|
||||
return sign ? -Double::Infinity() : Double::Infinity();
|
||||
}
|
||||
}
|
||||
|
||||
if (nan_symbol_ != NULL) {
|
||||
if (*current == nan_symbol_[0]) {
|
||||
if (!ConsumeSubString(¤t, end, nan_symbol_)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
|
||||
if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
if (!allow_trailing_junk && AdvanceToNonspace(¤t, end)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
|
||||
ASSERT(buffer_pos == 0);
|
||||
*processed_characters_count = static_cast<int>(current - input);
|
||||
return sign ? -Double::NaN() : Double::NaN();
|
||||
}
|
||||
}
|
||||
|
||||
bool leading_zero = false;
|
||||
if (*current == '0') {
|
||||
++current;
|
||||
if (current == end) {
|
||||
*processed_characters_count = static_cast<int>(current - input);
|
||||
return SignedZero(sign);
|
||||
}
|
||||
|
||||
leading_zero = true;
|
||||
|
||||
// It could be hexadecimal value.
|
||||
if ((flags_ & ALLOW_HEX) && (*current == 'x' || *current == 'X')) {
|
||||
++current;
|
||||
if (current == end || !isDigit(*current, 16)) {
|
||||
return junk_string_value_; // "0x".
|
||||
}
|
||||
|
||||
const char* tail_pointer = NULL;
|
||||
double result = RadixStringToIeee<4>(current,
|
||||
end,
|
||||
sign,
|
||||
allow_trailing_junk,
|
||||
junk_string_value_,
|
||||
read_as_double,
|
||||
&tail_pointer);
|
||||
if (tail_pointer != NULL) {
|
||||
if (allow_trailing_spaces) AdvanceToNonspace(&tail_pointer, end);
|
||||
*processed_characters_count = static_cast<int>(tail_pointer - input);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Ignore leading zeros in the integer part.
|
||||
while (*current == '0') {
|
||||
++current;
|
||||
if (current == end) {
|
||||
*processed_characters_count = static_cast<int>(current - input);
|
||||
return SignedZero(sign);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool octal = leading_zero && (flags_ & ALLOW_OCTALS) != 0;
|
||||
|
||||
// Copy significant digits of the integer part (if any) to the buffer.
|
||||
while (*current >= '0' && *current <= '9') {
|
||||
if (significant_digits < kMaxSignificantDigits) {
|
||||
ASSERT(buffer_pos < kBufferSize);
|
||||
buffer[buffer_pos++] = static_cast<char>(*current);
|
||||
significant_digits++;
|
||||
// Will later check if it's an octal in the buffer.
|
||||
} else {
|
||||
insignificant_digits++; // Move the digit into the exponential part.
|
||||
nonzero_digit_dropped = nonzero_digit_dropped || *current != '0';
|
||||
}
|
||||
octal = octal && *current < '8';
|
||||
++current;
|
||||
if (current == end) goto parsing_done;
|
||||
}
|
||||
|
||||
if (significant_digits == 0) {
|
||||
octal = false;
|
||||
}
|
||||
|
||||
if (*current == '.') {
|
||||
if (octal && !allow_trailing_junk) return junk_string_value_;
|
||||
if (octal) goto parsing_done;
|
||||
|
||||
++current;
|
||||
if (current == end) {
|
||||
if (significant_digits == 0 && !leading_zero) {
|
||||
return junk_string_value_;
|
||||
} else {
|
||||
goto parsing_done;
|
||||
}
|
||||
}
|
||||
|
||||
if (significant_digits == 0) {
|
||||
// octal = false;
|
||||
// Integer part consists of 0 or is absent. Significant digits start after
|
||||
// leading zeros (if any).
|
||||
while (*current == '0') {
|
||||
++current;
|
||||
if (current == end) {
|
||||
*processed_characters_count = static_cast<int>(current - input);
|
||||
return SignedZero(sign);
|
||||
}
|
||||
exponent--; // Move this 0 into the exponent.
|
||||
}
|
||||
}
|
||||
|
||||
// There is a fractional part.
|
||||
// We don't emit a '.', but adjust the exponent instead.
|
||||
while (*current >= '0' && *current <= '9') {
|
||||
if (significant_digits < kMaxSignificantDigits) {
|
||||
ASSERT(buffer_pos < kBufferSize);
|
||||
buffer[buffer_pos++] = static_cast<char>(*current);
|
||||
significant_digits++;
|
||||
exponent--;
|
||||
} else {
|
||||
// Ignore insignificant digits in the fractional part.
|
||||
nonzero_digit_dropped = nonzero_digit_dropped || *current != '0';
|
||||
}
|
||||
++current;
|
||||
if (current == end) goto parsing_done;
|
||||
}
|
||||
}
|
||||
|
||||
if (!leading_zero && exponent == 0 && significant_digits == 0) {
|
||||
// If leading_zeros is true then the string contains zeros.
|
||||
// If exponent < 0 then string was [+-]\.0*...
|
||||
// If significant_digits != 0 the string is not equal to 0.
|
||||
// Otherwise there are no digits in the string.
|
||||
return junk_string_value_;
|
||||
}
|
||||
|
||||
// Parse exponential part.
|
||||
if (*current == 'e' || *current == 'E') {
|
||||
if (octal && !allow_trailing_junk) return junk_string_value_;
|
||||
if (octal) goto parsing_done;
|
||||
++current;
|
||||
if (current == end) {
|
||||
if (allow_trailing_junk) {
|
||||
goto parsing_done;
|
||||
} else {
|
||||
return junk_string_value_;
|
||||
}
|
||||
}
|
||||
char sign = '+';
|
||||
if (*current == '+' || *current == '-') {
|
||||
sign = static_cast<char>(*current);
|
||||
++current;
|
||||
if (current == end) {
|
||||
if (allow_trailing_junk) {
|
||||
goto parsing_done;
|
||||
} else {
|
||||
return junk_string_value_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current == end || *current < '0' || *current > '9') {
|
||||
if (allow_trailing_junk) {
|
||||
goto parsing_done;
|
||||
} else {
|
||||
return junk_string_value_;
|
||||
}
|
||||
}
|
||||
|
||||
const int max_exponent = INT_MAX / 2;
|
||||
ASSERT(-max_exponent / 2 <= exponent && exponent <= max_exponent / 2);
|
||||
int num = 0;
|
||||
do {
|
||||
// Check overflow.
|
||||
int digit = *current - '0';
|
||||
if (num >= max_exponent / 10
|
||||
&& !(num == max_exponent / 10 && digit <= max_exponent % 10)) {
|
||||
num = max_exponent;
|
||||
} else {
|
||||
num = num * 10 + digit;
|
||||
}
|
||||
++current;
|
||||
} while (current != end && *current >= '0' && *current <= '9');
|
||||
|
||||
exponent += (sign == '-' ? -num : num);
|
||||
}
|
||||
|
||||
if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
if (!allow_trailing_junk && AdvanceToNonspace(¤t, end)) {
|
||||
return junk_string_value_;
|
||||
}
|
||||
if (allow_trailing_spaces) {
|
||||
AdvanceToNonspace(¤t, end);
|
||||
}
|
||||
|
||||
parsing_done:
|
||||
exponent += insignificant_digits;
|
||||
|
||||
if (octal) {
|
||||
double result;
|
||||
const char* tail_pointer = NULL;
|
||||
result = RadixStringToIeee<3>(buffer,
|
||||
buffer + buffer_pos,
|
||||
sign,
|
||||
allow_trailing_junk,
|
||||
junk_string_value_,
|
||||
read_as_double,
|
||||
&tail_pointer);
|
||||
ASSERT(tail_pointer != NULL);
|
||||
*processed_characters_count = static_cast<int>(current - input);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (nonzero_digit_dropped) {
|
||||
buffer[buffer_pos++] = '1';
|
||||
exponent--;
|
||||
}
|
||||
|
||||
ASSERT(buffer_pos < kBufferSize);
|
||||
buffer[buffer_pos] = '\0';
|
||||
|
||||
double converted;
|
||||
if (read_as_double) {
|
||||
converted = Strtod(Vector<const char>(buffer, buffer_pos), exponent);
|
||||
} else {
|
||||
converted = Strtof(Vector<const char>(buffer, buffer_pos), exponent);
|
||||
}
|
||||
*processed_characters_count = static_cast<int>(current - input);
|
||||
return sign? -converted: converted;
|
||||
}
|
||||
|
||||
} // namespace double_conversion
|
@ -1,536 +0,0 @@
|
||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_DOUBLE_CONVERSION_H_
|
||||
#define DOUBLE_CONVERSION_DOUBLE_CONVERSION_H_
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
class DoubleToStringConverter {
|
||||
public:
|
||||
// When calling ToFixed with a double > 10^kMaxFixedDigitsBeforePoint
|
||||
// or a requested_digits parameter > kMaxFixedDigitsAfterPoint then the
|
||||
// function returns false.
|
||||
static const int kMaxFixedDigitsBeforePoint = 60;
|
||||
static const int kMaxFixedDigitsAfterPoint = 60;
|
||||
|
||||
// When calling ToExponential with a requested_digits
|
||||
// parameter > kMaxExponentialDigits then the function returns false.
|
||||
static const int kMaxExponentialDigits = 120;
|
||||
|
||||
// When calling ToPrecision with a requested_digits
|
||||
// parameter < kMinPrecisionDigits or requested_digits > kMaxPrecisionDigits
|
||||
// then the function returns false.
|
||||
static const int kMinPrecisionDigits = 1;
|
||||
static const int kMaxPrecisionDigits = 120;
|
||||
|
||||
enum Flags {
|
||||
NO_FLAGS = 0,
|
||||
EMIT_POSITIVE_EXPONENT_SIGN = 1,
|
||||
EMIT_TRAILING_DECIMAL_POINT = 2,
|
||||
EMIT_TRAILING_ZERO_AFTER_POINT = 4,
|
||||
UNIQUE_ZERO = 8
|
||||
};
|
||||
|
||||
// Flags should be a bit-or combination of the possible Flags-enum.
|
||||
// - NO_FLAGS: no special flags.
|
||||
// - EMIT_POSITIVE_EXPONENT_SIGN: when the number is converted into exponent
|
||||
// form, emits a '+' for positive exponents. Example: 1.2e+2.
|
||||
// - EMIT_TRAILING_DECIMAL_POINT: when the input number is an integer and is
|
||||
// converted into decimal format then a trailing decimal point is appended.
|
||||
// Example: 2345.0 is converted to "2345.".
|
||||
// - EMIT_TRAILING_ZERO_AFTER_POINT: in addition to a trailing decimal point
|
||||
// emits a trailing '0'-character. This flag requires the
|
||||
// EXMIT_TRAILING_DECIMAL_POINT flag.
|
||||
// Example: 2345.0 is converted to "2345.0".
|
||||
// - UNIQUE_ZERO: "-0.0" is converted to "0.0".
|
||||
//
|
||||
// Infinity symbol and nan_symbol provide the string representation for these
|
||||
// special values. If the string is NULL and the special value is encountered
|
||||
// then the conversion functions return false.
|
||||
//
|
||||
// The exponent_character is used in exponential representations. It is
|
||||
// usually 'e' or 'E'.
|
||||
//
|
||||
// When converting to the shortest representation the converter will
|
||||
// represent input numbers in decimal format if they are in the interval
|
||||
// [10^decimal_in_shortest_low; 10^decimal_in_shortest_high[
|
||||
// (lower boundary included, greater boundary excluded).
|
||||
// Example: with decimal_in_shortest_low = -6 and
|
||||
// decimal_in_shortest_high = 21:
|
||||
// ToShortest(0.000001) -> "0.000001"
|
||||
// ToShortest(0.0000001) -> "1e-7"
|
||||
// ToShortest(111111111111111111111.0) -> "111111111111111110000"
|
||||
// ToShortest(100000000000000000000.0) -> "100000000000000000000"
|
||||
// ToShortest(1111111111111111111111.0) -> "1.1111111111111111e+21"
|
||||
//
|
||||
// When converting to precision mode the converter may add
|
||||
// max_leading_padding_zeroes before returning the number in exponential
|
||||
// format.
|
||||
// Example with max_leading_padding_zeroes_in_precision_mode = 6.
|
||||
// ToPrecision(0.0000012345, 2) -> "0.0000012"
|
||||
// ToPrecision(0.00000012345, 2) -> "1.2e-7"
|
||||
// Similarily the converter may add up to
|
||||
// max_trailing_padding_zeroes_in_precision_mode in precision mode to avoid
|
||||
// returning an exponential representation. A zero added by the
|
||||
// EMIT_TRAILING_ZERO_AFTER_POINT flag is counted for this limit.
|
||||
// Examples for max_trailing_padding_zeroes_in_precision_mode = 1:
|
||||
// ToPrecision(230.0, 2) -> "230"
|
||||
// ToPrecision(230.0, 2) -> "230." with EMIT_TRAILING_DECIMAL_POINT.
|
||||
// ToPrecision(230.0, 2) -> "2.3e2" with EMIT_TRAILING_ZERO_AFTER_POINT.
|
||||
DoubleToStringConverter(int flags,
|
||||
const char* infinity_symbol,
|
||||
const char* nan_symbol,
|
||||
char exponent_character,
|
||||
int decimal_in_shortest_low,
|
||||
int decimal_in_shortest_high,
|
||||
int max_leading_padding_zeroes_in_precision_mode,
|
||||
int max_trailing_padding_zeroes_in_precision_mode)
|
||||
: flags_(flags),
|
||||
infinity_symbol_(infinity_symbol),
|
||||
nan_symbol_(nan_symbol),
|
||||
exponent_character_(exponent_character),
|
||||
decimal_in_shortest_low_(decimal_in_shortest_low),
|
||||
decimal_in_shortest_high_(decimal_in_shortest_high),
|
||||
max_leading_padding_zeroes_in_precision_mode_(
|
||||
max_leading_padding_zeroes_in_precision_mode),
|
||||
max_trailing_padding_zeroes_in_precision_mode_(
|
||||
max_trailing_padding_zeroes_in_precision_mode) {
|
||||
// When 'trailing zero after the point' is set, then 'trailing point'
|
||||
// must be set too.
|
||||
ASSERT(((flags & EMIT_TRAILING_DECIMAL_POINT) != 0) ||
|
||||
!((flags & EMIT_TRAILING_ZERO_AFTER_POINT) != 0));
|
||||
}
|
||||
|
||||
// Returns a converter following the EcmaScript specification.
|
||||
static const DoubleToStringConverter& EcmaScriptConverter();
|
||||
|
||||
// Computes the shortest string of digits that correctly represent the input
|
||||
// number. Depending on decimal_in_shortest_low and decimal_in_shortest_high
|
||||
// (see constructor) it then either returns a decimal representation, or an
|
||||
// exponential representation.
|
||||
// Example with decimal_in_shortest_low = -6,
|
||||
// decimal_in_shortest_high = 21,
|
||||
// EMIT_POSITIVE_EXPONENT_SIGN activated, and
|
||||
// EMIT_TRAILING_DECIMAL_POINT deactived:
|
||||
// ToShortest(0.000001) -> "0.000001"
|
||||
// ToShortest(0.0000001) -> "1e-7"
|
||||
// ToShortest(111111111111111111111.0) -> "111111111111111110000"
|
||||
// ToShortest(100000000000000000000.0) -> "100000000000000000000"
|
||||
// ToShortest(1111111111111111111111.0) -> "1.1111111111111111e+21"
|
||||
//
|
||||
// Note: the conversion may round the output if the returned string
|
||||
// is accurate enough to uniquely identify the input-number.
|
||||
// For example the most precise representation of the double 9e59 equals
|
||||
// "899999999999999918767229449717619953810131273674690656206848", but
|
||||
// the converter will return the shorter (but still correct) "9e59".
|
||||
//
|
||||
// Returns true if the conversion succeeds. The conversion always succeeds
|
||||
// except when the input value is special and no infinity_symbol or
|
||||
// nan_symbol has been given to the constructor.
|
||||
bool ToShortest(double value, StringBuilder* result_builder) const {
|
||||
return ToShortestIeeeNumber(value, result_builder, SHORTEST);
|
||||
}
|
||||
|
||||
// Same as ToShortest, but for single-precision floats.
|
||||
bool ToShortestSingle(float value, StringBuilder* result_builder) const {
|
||||
return ToShortestIeeeNumber(value, result_builder, SHORTEST_SINGLE);
|
||||
}
|
||||
|
||||
|
||||
// Computes a decimal representation with a fixed number of digits after the
|
||||
// decimal point. The last emitted digit is rounded.
|
||||
//
|
||||
// Examples:
|
||||
// ToFixed(3.12, 1) -> "3.1"
|
||||
// ToFixed(3.1415, 3) -> "3.142"
|
||||
// ToFixed(1234.56789, 4) -> "1234.5679"
|
||||
// ToFixed(1.23, 5) -> "1.23000"
|
||||
// ToFixed(0.1, 4) -> "0.1000"
|
||||
// ToFixed(1e30, 2) -> "1000000000000000019884624838656.00"
|
||||
// ToFixed(0.1, 30) -> "0.100000000000000005551115123126"
|
||||
// ToFixed(0.1, 17) -> "0.10000000000000001"
|
||||
//
|
||||
// If requested_digits equals 0, then the tail of the result depends on
|
||||
// the EMIT_TRAILING_DECIMAL_POINT and EMIT_TRAILING_ZERO_AFTER_POINT.
|
||||
// Examples, for requested_digits == 0,
|
||||
// let EMIT_TRAILING_DECIMAL_POINT and EMIT_TRAILING_ZERO_AFTER_POINT be
|
||||
// - false and false: then 123.45 -> 123
|
||||
// 0.678 -> 1
|
||||
// - true and false: then 123.45 -> 123.
|
||||
// 0.678 -> 1.
|
||||
// - true and true: then 123.45 -> 123.0
|
||||
// 0.678 -> 1.0
|
||||
//
|
||||
// Returns true if the conversion succeeds. The conversion always succeeds
|
||||
// except for the following cases:
|
||||
// - the input value is special and no infinity_symbol or nan_symbol has
|
||||
// been provided to the constructor,
|
||||
// - 'value' > 10^kMaxFixedDigitsBeforePoint, or
|
||||
// - 'requested_digits' > kMaxFixedDigitsAfterPoint.
|
||||
// The last two conditions imply that the result will never contain more than
|
||||
// 1 + kMaxFixedDigitsBeforePoint + 1 + kMaxFixedDigitsAfterPoint characters
|
||||
// (one additional character for the sign, and one for the decimal point).
|
||||
bool ToFixed(double value,
|
||||
int requested_digits,
|
||||
StringBuilder* result_builder) const;
|
||||
|
||||
// Computes a representation in exponential format with requested_digits
|
||||
// after the decimal point. The last emitted digit is rounded.
|
||||
// If requested_digits equals -1, then the shortest exponential representation
|
||||
// is computed.
|
||||
//
|
||||
// Examples with EMIT_POSITIVE_EXPONENT_SIGN deactivated, and
|
||||
// exponent_character set to 'e'.
|
||||
// ToExponential(3.12, 1) -> "3.1e0"
|
||||
// ToExponential(5.0, 3) -> "5.000e0"
|
||||
// ToExponential(0.001, 2) -> "1.00e-3"
|
||||
// ToExponential(3.1415, -1) -> "3.1415e0"
|
||||
// ToExponential(3.1415, 4) -> "3.1415e0"
|
||||
// ToExponential(3.1415, 3) -> "3.142e0"
|
||||
// ToExponential(123456789000000, 3) -> "1.235e14"
|
||||
// ToExponential(1000000000000000019884624838656.0, -1) -> "1e30"
|
||||
// ToExponential(1000000000000000019884624838656.0, 32) ->
|
||||
// "1.00000000000000001988462483865600e30"
|
||||
// ToExponential(1234, 0) -> "1e3"
|
||||
//
|
||||
// Returns true if the conversion succeeds. The conversion always succeeds
|
||||
// except for the following cases:
|
||||
// - the input value is special and no infinity_symbol or nan_symbol has
|
||||
// been provided to the constructor,
|
||||
// - 'requested_digits' > kMaxExponentialDigits.
|
||||
// The last condition implies that the result will never contain more than
|
||||
// kMaxExponentialDigits + 8 characters (the sign, the digit before the
|
||||
// decimal point, the decimal point, the exponent character, the
|
||||
// exponent's sign, and at most 3 exponent digits).
|
||||
bool ToExponential(double value,
|
||||
int requested_digits,
|
||||
StringBuilder* result_builder) const;
|
||||
|
||||
// Computes 'precision' leading digits of the given 'value' and returns them
|
||||
// either in exponential or decimal format, depending on
|
||||
// max_{leading|trailing}_padding_zeroes_in_precision_mode (given to the
|
||||
// constructor).
|
||||
// The last computed digit is rounded.
|
||||
//
|
||||
// Example with max_leading_padding_zeroes_in_precision_mode = 6.
|
||||
// ToPrecision(0.0000012345, 2) -> "0.0000012"
|
||||
// ToPrecision(0.00000012345, 2) -> "1.2e-7"
|
||||
// Similarily the converter may add up to
|
||||
// max_trailing_padding_zeroes_in_precision_mode in precision mode to avoid
|
||||
// returning an exponential representation. A zero added by the
|
||||
// EMIT_TRAILING_ZERO_AFTER_POINT flag is counted for this limit.
|
||||
// Examples for max_trailing_padding_zeroes_in_precision_mode = 1:
|
||||
// ToPrecision(230.0, 2) -> "230"
|
||||
// ToPrecision(230.0, 2) -> "230." with EMIT_TRAILING_DECIMAL_POINT.
|
||||
// ToPrecision(230.0, 2) -> "2.3e2" with EMIT_TRAILING_ZERO_AFTER_POINT.
|
||||
// Examples for max_trailing_padding_zeroes_in_precision_mode = 3, and no
|
||||
// EMIT_TRAILING_ZERO_AFTER_POINT:
|
||||
// ToPrecision(123450.0, 6) -> "123450"
|
||||
// ToPrecision(123450.0, 5) -> "123450"
|
||||
// ToPrecision(123450.0, 4) -> "123500"
|
||||
// ToPrecision(123450.0, 3) -> "123000"
|
||||
// ToPrecision(123450.0, 2) -> "1.2e5"
|
||||
//
|
||||
// Returns true if the conversion succeeds. The conversion always succeeds
|
||||
// except for the following cases:
|
||||
// - the input value is special and no infinity_symbol or nan_symbol has
|
||||
// been provided to the constructor,
|
||||
// - precision < kMinPericisionDigits
|
||||
// - precision > kMaxPrecisionDigits
|
||||
// The last condition implies that the result will never contain more than
|
||||
// kMaxPrecisionDigits + 7 characters (the sign, the decimal point, the
|
||||
// exponent character, the exponent's sign, and at most 3 exponent digits).
|
||||
bool ToPrecision(double value,
|
||||
int precision,
|
||||
StringBuilder* result_builder) const;
|
||||
|
||||
enum DtoaMode {
|
||||
// Produce the shortest correct representation.
|
||||
// For example the output of 0.299999999999999988897 is (the less accurate
|
||||
// but correct) 0.3.
|
||||
SHORTEST,
|
||||
// Same as SHORTEST, but for single-precision floats.
|
||||
SHORTEST_SINGLE,
|
||||
// Produce a fixed number of digits after the decimal point.
|
||||
// For instance fixed(0.1, 4) becomes 0.1000
|
||||
// If the input number is big, the output will be big.
|
||||
FIXED,
|
||||
// Fixed number of digits (independent of the decimal point).
|
||||
PRECISION
|
||||
};
|
||||
|
||||
// The maximal number of digits that are needed to emit a double in base 10.
|
||||
// A higher precision can be achieved by using more digits, but the shortest
|
||||
// accurate representation of any double will never use more digits than
|
||||
// kBase10MaximalLength.
|
||||
// Note that DoubleToAscii null-terminates its input. So the given buffer
|
||||
// should be at least kBase10MaximalLength + 1 characters long.
|
||||
static const int kBase10MaximalLength = 17;
|
||||
|
||||
// Converts the given double 'v' to ascii. 'v' must not be NaN, +Infinity, or
|
||||
// -Infinity. In SHORTEST_SINGLE-mode this restriction also applies to 'v'
|
||||
// after it has been casted to a single-precision float. That is, in this
|
||||
// mode static_cast<float>(v) must not be NaN, +Infinity or -Infinity.
|
||||
//
|
||||
// The result should be interpreted as buffer * 10^(point-length).
|
||||
//
|
||||
// The output depends on the given mode:
|
||||
// - SHORTEST: produce the least amount of digits for which the internal
|
||||
// identity requirement is still satisfied. If the digits are printed
|
||||
// (together with the correct exponent) then reading this number will give
|
||||
// 'v' again. The buffer will choose the representation that is closest to
|
||||
// 'v'. If there are two at the same distance, than the one farther away
|
||||
// from 0 is chosen (halfway cases - ending with 5 - are rounded up).
|
||||
// In this mode the 'requested_digits' parameter is ignored.
|
||||
// - SHORTEST_SINGLE: same as SHORTEST but with single-precision.
|
||||
// - FIXED: produces digits necessary to print a given number with
|
||||
// 'requested_digits' digits after the decimal point. The produced digits
|
||||
// might be too short in which case the caller has to fill the remainder
|
||||
// with '0's.
|
||||
// Example: toFixed(0.001, 5) is allowed to return buffer="1", point=-2.
|
||||
// Halfway cases are rounded towards +/-Infinity (away from 0). The call
|
||||
// toFixed(0.15, 2) thus returns buffer="2", point=0.
|
||||
// The returned buffer may contain digits that would be truncated from the
|
||||
// shortest representation of the input.
|
||||
// - PRECISION: produces 'requested_digits' where the first digit is not '0'.
|
||||
// Even though the length of produced digits usually equals
|
||||
// 'requested_digits', the function is allowed to return fewer digits, in
|
||||
// which case the caller has to fill the missing digits with '0's.
|
||||
// Halfway cases are again rounded away from 0.
|
||||
// DoubleToAscii expects the given buffer to be big enough to hold all
|
||||
// digits and a terminating null-character. In SHORTEST-mode it expects a
|
||||
// buffer of at least kBase10MaximalLength + 1. In all other modes the
|
||||
// requested_digits parameter and the padding-zeroes limit the size of the
|
||||
// output. Don't forget the decimal point, the exponent character and the
|
||||
// terminating null-character when computing the maximal output size.
|
||||
// The given length is only used in debug mode to ensure the buffer is big
|
||||
// enough.
|
||||
static void DoubleToAscii(double v,
|
||||
DtoaMode mode,
|
||||
int requested_digits,
|
||||
char* buffer,
|
||||
int buffer_length,
|
||||
bool* sign,
|
||||
int* length,
|
||||
int* point);
|
||||
|
||||
private:
|
||||
// Implementation for ToShortest and ToShortestSingle.
|
||||
bool ToShortestIeeeNumber(double value,
|
||||
StringBuilder* result_builder,
|
||||
DtoaMode mode) const;
|
||||
|
||||
// If the value is a special value (NaN or Infinity) constructs the
|
||||
// corresponding string using the configured infinity/nan-symbol.
|
||||
// If either of them is NULL or the value is not special then the
|
||||
// function returns false.
|
||||
bool HandleSpecialValues(double value, StringBuilder* result_builder) const;
|
||||
// Constructs an exponential representation (i.e. 1.234e56).
|
||||
// The given exponent assumes a decimal point after the first decimal digit.
|
||||
void CreateExponentialRepresentation(const char* decimal_digits,
|
||||
int length,
|
||||
int exponent,
|
||||
StringBuilder* result_builder) const;
|
||||
// Creates a decimal representation (i.e 1234.5678).
|
||||
void CreateDecimalRepresentation(const char* decimal_digits,
|
||||
int length,
|
||||
int decimal_point,
|
||||
int digits_after_point,
|
||||
StringBuilder* result_builder) const;
|
||||
|
||||
const int flags_;
|
||||
const char* const infinity_symbol_;
|
||||
const char* const nan_symbol_;
|
||||
const char exponent_character_;
|
||||
const int decimal_in_shortest_low_;
|
||||
const int decimal_in_shortest_high_;
|
||||
const int max_leading_padding_zeroes_in_precision_mode_;
|
||||
const int max_trailing_padding_zeroes_in_precision_mode_;
|
||||
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(DoubleToStringConverter);
|
||||
};
|
||||
|
||||
|
||||
class StringToDoubleConverter {
|
||||
public:
|
||||
// Enumeration for allowing octals and ignoring junk when converting
|
||||
// strings to numbers.
|
||||
enum Flags {
|
||||
NO_FLAGS = 0,
|
||||
ALLOW_HEX = 1,
|
||||
ALLOW_OCTALS = 2,
|
||||
ALLOW_TRAILING_JUNK = 4,
|
||||
ALLOW_LEADING_SPACES = 8,
|
||||
ALLOW_TRAILING_SPACES = 16,
|
||||
ALLOW_SPACES_AFTER_SIGN = 32
|
||||
};
|
||||
|
||||
// Flags should be a bit-or combination of the possible Flags-enum.
|
||||
// - NO_FLAGS: no special flags.
|
||||
// - ALLOW_HEX: recognizes the prefix "0x". Hex numbers may only be integers.
|
||||
// Ex: StringToDouble("0x1234") -> 4660.0
|
||||
// In StringToDouble("0x1234.56") the characters ".56" are trailing
|
||||
// junk. The result of the call is hence dependent on
|
||||
// the ALLOW_TRAILING_JUNK flag and/or the junk value.
|
||||
// With this flag "0x" is a junk-string. Even with ALLOW_TRAILING_JUNK,
|
||||
// the string will not be parsed as "0" followed by junk.
|
||||
//
|
||||
// - ALLOW_OCTALS: recognizes the prefix "0" for octals:
|
||||
// If a sequence of octal digits starts with '0', then the number is
|
||||
// read as octal integer. Octal numbers may only be integers.
|
||||
// Ex: StringToDouble("01234") -> 668.0
|
||||
// StringToDouble("012349") -> 12349.0 // Not a sequence of octal
|
||||
// // digits.
|
||||
// In StringToDouble("01234.56") the characters ".56" are trailing
|
||||
// junk. The result of the call is hence dependent on
|
||||
// the ALLOW_TRAILING_JUNK flag and/or the junk value.
|
||||
// In StringToDouble("01234e56") the characters "e56" are trailing
|
||||
// junk, too.
|
||||
// - ALLOW_TRAILING_JUNK: ignore trailing characters that are not part of
|
||||
// a double literal.
|
||||
// - ALLOW_LEADING_SPACES: skip over leading spaces.
|
||||
// - ALLOW_TRAILING_SPACES: ignore trailing spaces.
|
||||
// - ALLOW_SPACES_AFTER_SIGN: ignore spaces after the sign.
|
||||
// Ex: StringToDouble("- 123.2") -> -123.2.
|
||||
// StringToDouble("+ 123.2") -> 123.2
|
||||
//
|
||||
// empty_string_value is returned when an empty string is given as input.
|
||||
// If ALLOW_LEADING_SPACES or ALLOW_TRAILING_SPACES are set, then a string
|
||||
// containing only spaces is converted to the 'empty_string_value', too.
|
||||
//
|
||||
// junk_string_value is returned when
|
||||
// a) ALLOW_TRAILING_JUNK is not set, and a junk character (a character not
|
||||
// part of a double-literal) is found.
|
||||
// b) ALLOW_TRAILING_JUNK is set, but the string does not start with a
|
||||
// double literal.
|
||||
//
|
||||
// infinity_symbol and nan_symbol are strings that are used to detect
|
||||
// inputs that represent infinity and NaN. They can be null, in which case
|
||||
// they are ignored.
|
||||
// The conversion routine first reads any possible signs. Then it compares the
|
||||
// following character of the input-string with the first character of
|
||||
// the infinity, and nan-symbol. If either matches, the function assumes, that
|
||||
// a match has been found, and expects the following input characters to match
|
||||
// the remaining characters of the special-value symbol.
|
||||
// This means that the following restrictions apply to special-value symbols:
|
||||
// - they must not start with signs ('+', or '-'),
|
||||
// - they must not have the same first character.
|
||||
// - they must not start with digits.
|
||||
//
|
||||
// Examples:
|
||||
// flags = ALLOW_HEX | ALLOW_TRAILING_JUNK,
|
||||
// empty_string_value = 0.0,
|
||||
// junk_string_value = NaN,
|
||||
// infinity_symbol = "infinity",
|
||||
// nan_symbol = "nan":
|
||||
// StringToDouble("0x1234") -> 4660.0.
|
||||
// StringToDouble("0x1234K") -> 4660.0.
|
||||
// StringToDouble("") -> 0.0 // empty_string_value.
|
||||
// StringToDouble(" ") -> NaN // junk_string_value.
|
||||
// StringToDouble(" 1") -> NaN // junk_string_value.
|
||||
// StringToDouble("0x") -> NaN // junk_string_value.
|
||||
// StringToDouble("-123.45") -> -123.45.
|
||||
// StringToDouble("--123.45") -> NaN // junk_string_value.
|
||||
// StringToDouble("123e45") -> 123e45.
|
||||
// StringToDouble("123E45") -> 123e45.
|
||||
// StringToDouble("123e+45") -> 123e45.
|
||||
// StringToDouble("123E-45") -> 123e-45.
|
||||
// StringToDouble("123e") -> 123.0 // trailing junk ignored.
|
||||
// StringToDouble("123e-") -> 123.0 // trailing junk ignored.
|
||||
// StringToDouble("+NaN") -> NaN // NaN string literal.
|
||||
// StringToDouble("-infinity") -> -inf. // infinity literal.
|
||||
// StringToDouble("Infinity") -> NaN // junk_string_value.
|
||||
//
|
||||
// flags = ALLOW_OCTAL | ALLOW_LEADING_SPACES,
|
||||
// empty_string_value = 0.0,
|
||||
// junk_string_value = NaN,
|
||||
// infinity_symbol = NULL,
|
||||
// nan_symbol = NULL:
|
||||
// StringToDouble("0x1234") -> NaN // junk_string_value.
|
||||
// StringToDouble("01234") -> 668.0.
|
||||
// StringToDouble("") -> 0.0 // empty_string_value.
|
||||
// StringToDouble(" ") -> 0.0 // empty_string_value.
|
||||
// StringToDouble(" 1") -> 1.0
|
||||
// StringToDouble("0x") -> NaN // junk_string_value.
|
||||
// StringToDouble("0123e45") -> NaN // junk_string_value.
|
||||
// StringToDouble("01239E45") -> 1239e45.
|
||||
// StringToDouble("-infinity") -> NaN // junk_string_value.
|
||||
// StringToDouble("NaN") -> NaN // junk_string_value.
|
||||
StringToDoubleConverter(int flags,
|
||||
double empty_string_value,
|
||||
double junk_string_value,
|
||||
const char* infinity_symbol,
|
||||
const char* nan_symbol)
|
||||
: flags_(flags),
|
||||
empty_string_value_(empty_string_value),
|
||||
junk_string_value_(junk_string_value),
|
||||
infinity_symbol_(infinity_symbol),
|
||||
nan_symbol_(nan_symbol) {
|
||||
}
|
||||
|
||||
// Performs the conversion.
|
||||
// The output parameter 'processed_characters_count' is set to the number
|
||||
// of characters that have been processed to read the number.
|
||||
// Spaces than are processed with ALLOW_{LEADING|TRAILING}_SPACES are included
|
||||
// in the 'processed_characters_count'. Trailing junk is never included.
|
||||
double StringToDouble(const char* buffer,
|
||||
int length,
|
||||
int* processed_characters_count) const {
|
||||
return StringToIeee(buffer, length, processed_characters_count, true);
|
||||
}
|
||||
|
||||
// Same as StringToDouble but reads a float.
|
||||
// Note that this is not equivalent to static_cast<float>(StringToDouble(...))
|
||||
// due to potential double-rounding.
|
||||
float StringToFloat(const char* buffer,
|
||||
int length,
|
||||
int* processed_characters_count) const {
|
||||
return static_cast<float>(StringToIeee(buffer, length,
|
||||
processed_characters_count, false));
|
||||
}
|
||||
|
||||
private:
|
||||
const int flags_;
|
||||
const double empty_string_value_;
|
||||
const double junk_string_value_;
|
||||
const char* const infinity_symbol_;
|
||||
const char* const nan_symbol_;
|
||||
|
||||
double StringToIeee(const char* buffer,
|
||||
int length,
|
||||
int* processed_characters_count,
|
||||
bool read_as_double) const;
|
||||
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(StringToDoubleConverter);
|
||||
};
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_DOUBLE_CONVERSION_H_
|
665
ios/Pods/DoubleConversion/double-conversion/fast-dtoa.cc
generated
665
ios/Pods/DoubleConversion/double-conversion/fast-dtoa.cc
generated
@ -1,665 +0,0 @@
|
||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include "fast-dtoa.h"
|
||||
|
||||
#include "cached-powers.h"
|
||||
#include "diy-fp.h"
|
||||
#include "ieee.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
// The minimal and maximal target exponent define the range of w's binary
|
||||
// exponent, where 'w' is the result of multiplying the input by a cached power
|
||||
// of ten.
|
||||
//
|
||||
// A different range might be chosen on a different platform, to optimize digit
|
||||
// generation, but a smaller range requires more powers of ten to be cached.
|
||||
static const int kMinimalTargetExponent = -60;
|
||||
static const int kMaximalTargetExponent = -32;
|
||||
|
||||
|
||||
// Adjusts the last digit of the generated number, and screens out generated
|
||||
// solutions that may be inaccurate. A solution may be inaccurate if it is
|
||||
// outside the safe interval, or if we cannot prove that it is closer to the
|
||||
// input than a neighboring representation of the same length.
|
||||
//
|
||||
// Input: * buffer containing the digits of too_high / 10^kappa
|
||||
// * the buffer's length
|
||||
// * distance_too_high_w == (too_high - w).f() * unit
|
||||
// * unsafe_interval == (too_high - too_low).f() * unit
|
||||
// * rest = (too_high - buffer * 10^kappa).f() * unit
|
||||
// * ten_kappa = 10^kappa * unit
|
||||
// * unit = the common multiplier
|
||||
// Output: returns true if the buffer is guaranteed to contain the closest
|
||||
// representable number to the input.
|
||||
// Modifies the generated digits in the buffer to approach (round towards) w.
|
||||
static bool RoundWeed(Vector<char> buffer,
|
||||
int length,
|
||||
uint64_t distance_too_high_w,
|
||||
uint64_t unsafe_interval,
|
||||
uint64_t rest,
|
||||
uint64_t ten_kappa,
|
||||
uint64_t unit) {
|
||||
uint64_t small_distance = distance_too_high_w - unit;
|
||||
uint64_t big_distance = distance_too_high_w + unit;
|
||||
// Let w_low = too_high - big_distance, and
|
||||
// w_high = too_high - small_distance.
|
||||
// Note: w_low < w < w_high
|
||||
//
|
||||
// The real w (* unit) must lie somewhere inside the interval
|
||||
// ]w_low; w_high[ (often written as "(w_low; w_high)")
|
||||
|
||||
// Basically the buffer currently contains a number in the unsafe interval
|
||||
// ]too_low; too_high[ with too_low < w < too_high
|
||||
//
|
||||
// too_high - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// ^v 1 unit ^ ^ ^ ^
|
||||
// boundary_high --------------------- . . . .
|
||||
// ^v 1 unit . . . .
|
||||
// - - - - - - - - - - - - - - - - - - - + - - + - - - - - - . .
|
||||
// . . ^ . .
|
||||
// . big_distance . . .
|
||||
// . . . . rest
|
||||
// small_distance . . . .
|
||||
// v . . . .
|
||||
// w_high - - - - - - - - - - - - - - - - - - . . . .
|
||||
// ^v 1 unit . . . .
|
||||
// w ---------------------------------------- . . . .
|
||||
// ^v 1 unit v . . .
|
||||
// w_low - - - - - - - - - - - - - - - - - - - - - . . .
|
||||
// . . v
|
||||
// buffer --------------------------------------------------+-------+--------
|
||||
// . .
|
||||
// safe_interval .
|
||||
// v .
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .
|
||||
// ^v 1 unit .
|
||||
// boundary_low ------------------------- unsafe_interval
|
||||
// ^v 1 unit v
|
||||
// too_low - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
//
|
||||
//
|
||||
// Note that the value of buffer could lie anywhere inside the range too_low
|
||||
// to too_high.
|
||||
//
|
||||
// boundary_low, boundary_high and w are approximations of the real boundaries
|
||||
// and v (the input number). They are guaranteed to be precise up to one unit.
|
||||
// In fact the error is guaranteed to be strictly less than one unit.
|
||||
//
|
||||
// Anything that lies outside the unsafe interval is guaranteed not to round
|
||||
// to v when read again.
|
||||
// Anything that lies inside the safe interval is guaranteed to round to v
|
||||
// when read again.
|
||||
// If the number inside the buffer lies inside the unsafe interval but not
|
||||
// inside the safe interval then we simply do not know and bail out (returning
|
||||
// false).
|
||||
//
|
||||
// Similarly we have to take into account the imprecision of 'w' when finding
|
||||
// the closest representation of 'w'. If we have two potential
|
||||
// representations, and one is closer to both w_low and w_high, then we know
|
||||
// it is closer to the actual value v.
|
||||
//
|
||||
// By generating the digits of too_high we got the largest (closest to
|
||||
// too_high) buffer that is still in the unsafe interval. In the case where
|
||||
// w_high < buffer < too_high we try to decrement the buffer.
|
||||
// This way the buffer approaches (rounds towards) w.
|
||||
// There are 3 conditions that stop the decrementation process:
|
||||
// 1) the buffer is already below w_high
|
||||
// 2) decrementing the buffer would make it leave the unsafe interval
|
||||
// 3) decrementing the buffer would yield a number below w_high and farther
|
||||
// away than the current number. In other words:
|
||||
// (buffer{-1} < w_high) && w_high - buffer{-1} > buffer - w_high
|
||||
// Instead of using the buffer directly we use its distance to too_high.
|
||||
// Conceptually rest ~= too_high - buffer
|
||||
// We need to do the following tests in this order to avoid over- and
|
||||
// underflows.
|
||||
ASSERT(rest <= unsafe_interval);
|
||||
while (rest < small_distance && // Negated condition 1
|
||||
unsafe_interval - rest >= ten_kappa && // Negated condition 2
|
||||
(rest + ten_kappa < small_distance || // buffer{-1} > w_high
|
||||
small_distance - rest >= rest + ten_kappa - small_distance)) {
|
||||
buffer[length - 1]--;
|
||||
rest += ten_kappa;
|
||||
}
|
||||
|
||||
// We have approached w+ as much as possible. We now test if approaching w-
|
||||
// would require changing the buffer. If yes, then we have two possible
|
||||
// representations close to w, but we cannot decide which one is closer.
|
||||
if (rest < big_distance &&
|
||||
unsafe_interval - rest >= ten_kappa &&
|
||||
(rest + ten_kappa < big_distance ||
|
||||
big_distance - rest > rest + ten_kappa - big_distance)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Weeding test.
|
||||
// The safe interval is [too_low + 2 ulp; too_high - 2 ulp]
|
||||
// Since too_low = too_high - unsafe_interval this is equivalent to
|
||||
// [too_high - unsafe_interval + 4 ulp; too_high - 2 ulp]
|
||||
// Conceptually we have: rest ~= too_high - buffer
|
||||
return (2 * unit <= rest) && (rest <= unsafe_interval - 4 * unit);
|
||||
}
|
||||
|
||||
|
||||
// Rounds the buffer upwards if the result is closer to v by possibly adding
|
||||
// 1 to the buffer. If the precision of the calculation is not sufficient to
|
||||
// round correctly, return false.
|
||||
// The rounding might shift the whole buffer in which case the kappa is
|
||||
// adjusted. For example "99", kappa = 3 might become "10", kappa = 4.
|
||||
//
|
||||
// If 2*rest > ten_kappa then the buffer needs to be round up.
|
||||
// rest can have an error of +/- 1 unit. This function accounts for the
|
||||
// imprecision and returns false, if the rounding direction cannot be
|
||||
// unambiguously determined.
|
||||
//
|
||||
// Precondition: rest < ten_kappa.
|
||||
static bool RoundWeedCounted(Vector<char> buffer,
|
||||
int length,
|
||||
uint64_t rest,
|
||||
uint64_t ten_kappa,
|
||||
uint64_t unit,
|
||||
int* kappa) {
|
||||
ASSERT(rest < ten_kappa);
|
||||
// The following tests are done in a specific order to avoid overflows. They
|
||||
// will work correctly with any uint64 values of rest < ten_kappa and unit.
|
||||
//
|
||||
// If the unit is too big, then we don't know which way to round. For example
|
||||
// a unit of 50 means that the real number lies within rest +/- 50. If
|
||||
// 10^kappa == 40 then there is no way to tell which way to round.
|
||||
if (unit >= ten_kappa) return false;
|
||||
// Even if unit is just half the size of 10^kappa we are already completely
|
||||
// lost. (And after the previous test we know that the expression will not
|
||||
// over/underflow.)
|
||||
if (ten_kappa - unit <= unit) return false;
|
||||
// If 2 * (rest + unit) <= 10^kappa we can safely round down.
|
||||
if ((ten_kappa - rest > rest) && (ten_kappa - 2 * rest >= 2 * unit)) {
|
||||
return true;
|
||||
}
|
||||
// If 2 * (rest - unit) >= 10^kappa, then we can safely round up.
|
||||
if ((rest > unit) && (ten_kappa - (rest - unit) <= (rest - unit))) {
|
||||
// Increment the last digit recursively until we find a non '9' digit.
|
||||
buffer[length - 1]++;
|
||||
for (int i = length - 1; i > 0; --i) {
|
||||
if (buffer[i] != '0' + 10) break;
|
||||
buffer[i] = '0';
|
||||
buffer[i - 1]++;
|
||||
}
|
||||
// If the first digit is now '0'+ 10 we had a buffer with all '9's. With the
|
||||
// exception of the first digit all digits are now '0'. Simply switch the
|
||||
// first digit to '1' and adjust the kappa. Example: "99" becomes "10" and
|
||||
// the power (the kappa) is increased.
|
||||
if (buffer[0] == '0' + 10) {
|
||||
buffer[0] = '1';
|
||||
(*kappa) += 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the biggest power of ten that is less than or equal to the given
|
||||
// number. We furthermore receive the maximum number of bits 'number' has.
|
||||
//
|
||||
// Returns power == 10^(exponent_plus_one-1) such that
|
||||
// power <= number < power * 10.
|
||||
// If number_bits == 0 then 0^(0-1) is returned.
|
||||
// The number of bits must be <= 32.
|
||||
// Precondition: number < (1 << (number_bits + 1)).
|
||||
|
||||
// Inspired by the method for finding an integer log base 10 from here:
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10
|
||||
static unsigned int const kSmallPowersOfTen[] =
|
||||
{0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
|
||||
1000000000};
|
||||
|
||||
static void BiggestPowerTen(uint32_t number,
|
||||
int number_bits,
|
||||
uint32_t* power,
|
||||
int* exponent_plus_one) {
|
||||
ASSERT(number < (1u << (number_bits + 1)));
|
||||
// 1233/4096 is approximately 1/lg(10).
|
||||
int exponent_plus_one_guess = ((number_bits + 1) * 1233 >> 12);
|
||||
// We increment to skip over the first entry in the kPowersOf10 table.
|
||||
// Note: kPowersOf10[i] == 10^(i-1).
|
||||
exponent_plus_one_guess++;
|
||||
// We don't have any guarantees that 2^number_bits <= number.
|
||||
if (number < kSmallPowersOfTen[exponent_plus_one_guess]) {
|
||||
exponent_plus_one_guess--;
|
||||
}
|
||||
*power = kSmallPowersOfTen[exponent_plus_one_guess];
|
||||
*exponent_plus_one = exponent_plus_one_guess;
|
||||
}
|
||||
|
||||
// Generates the digits of input number w.
|
||||
// w is a floating-point number (DiyFp), consisting of a significand and an
|
||||
// exponent. Its exponent is bounded by kMinimalTargetExponent and
|
||||
// kMaximalTargetExponent.
|
||||
// Hence -60 <= w.e() <= -32.
|
||||
//
|
||||
// Returns false if it fails, in which case the generated digits in the buffer
|
||||
// should not be used.
|
||||
// Preconditions:
|
||||
// * low, w and high are correct up to 1 ulp (unit in the last place). That
|
||||
// is, their error must be less than a unit of their last digits.
|
||||
// * low.e() == w.e() == high.e()
|
||||
// * low < w < high, and taking into account their error: low~ <= high~
|
||||
// * kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
|
||||
// Postconditions: returns false if procedure fails.
|
||||
// otherwise:
|
||||
// * buffer is not null-terminated, but len contains the number of digits.
|
||||
// * buffer contains the shortest possible decimal digit-sequence
|
||||
// such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the
|
||||
// correct values of low and high (without their error).
|
||||
// * if more than one decimal representation gives the minimal number of
|
||||
// decimal digits then the one closest to W (where W is the correct value
|
||||
// of w) is chosen.
|
||||
// Remark: this procedure takes into account the imprecision of its input
|
||||
// numbers. If the precision is not enough to guarantee all the postconditions
|
||||
// then false is returned. This usually happens rarely (~0.5%).
|
||||
//
|
||||
// Say, for the sake of example, that
|
||||
// w.e() == -48, and w.f() == 0x1234567890abcdef
|
||||
// w's value can be computed by w.f() * 2^w.e()
|
||||
// We can obtain w's integral digits by simply shifting w.f() by -w.e().
|
||||
// -> w's integral part is 0x1234
|
||||
// w's fractional part is therefore 0x567890abcdef.
|
||||
// Printing w's integral part is easy (simply print 0x1234 in decimal).
|
||||
// In order to print its fraction we repeatedly multiply the fraction by 10 and
|
||||
// get each digit. Example the first digit after the point would be computed by
|
||||
// (0x567890abcdef * 10) >> 48. -> 3
|
||||
// The whole thing becomes slightly more complicated because we want to stop
|
||||
// once we have enough digits. That is, once the digits inside the buffer
|
||||
// represent 'w' we can stop. Everything inside the interval low - high
|
||||
// represents w. However we have to pay attention to low, high and w's
|
||||
// imprecision.
|
||||
static bool DigitGen(DiyFp low,
|
||||
DiyFp w,
|
||||
DiyFp high,
|
||||
Vector<char> buffer,
|
||||
int* length,
|
||||
int* kappa) {
|
||||
ASSERT(low.e() == w.e() && w.e() == high.e());
|
||||
ASSERT(low.f() + 1 <= high.f() - 1);
|
||||
ASSERT(kMinimalTargetExponent <= w.e() && w.e() <= kMaximalTargetExponent);
|
||||
// low, w and high are imprecise, but by less than one ulp (unit in the last
|
||||
// place).
|
||||
// If we remove (resp. add) 1 ulp from low (resp. high) we are certain that
|
||||
// the new numbers are outside of the interval we want the final
|
||||
// representation to lie in.
|
||||
// Inversely adding (resp. removing) 1 ulp from low (resp. high) would yield
|
||||
// numbers that are certain to lie in the interval. We will use this fact
|
||||
// later on.
|
||||
// We will now start by generating the digits within the uncertain
|
||||
// interval. Later we will weed out representations that lie outside the safe
|
||||
// interval and thus _might_ lie outside the correct interval.
|
||||
uint64_t unit = 1;
|
||||
DiyFp too_low = DiyFp(low.f() - unit, low.e());
|
||||
DiyFp too_high = DiyFp(high.f() + unit, high.e());
|
||||
// too_low and too_high are guaranteed to lie outside the interval we want the
|
||||
// generated number in.
|
||||
DiyFp unsafe_interval = DiyFp::Minus(too_high, too_low);
|
||||
// We now cut the input number into two parts: the integral digits and the
|
||||
// fractionals. We will not write any decimal separator though, but adapt
|
||||
// kappa instead.
|
||||
// Reminder: we are currently computing the digits (stored inside the buffer)
|
||||
// such that: too_low < buffer * 10^kappa < too_high
|
||||
// We use too_high for the digit_generation and stop as soon as possible.
|
||||
// If we stop early we effectively round down.
|
||||
DiyFp one = DiyFp(static_cast<uint64_t>(1) << -w.e(), w.e());
|
||||
// Division by one is a shift.
|
||||
uint32_t integrals = static_cast<uint32_t>(too_high.f() >> -one.e());
|
||||
// Modulo by one is an and.
|
||||
uint64_t fractionals = too_high.f() & (one.f() - 1);
|
||||
uint32_t divisor;
|
||||
int divisor_exponent_plus_one;
|
||||
BiggestPowerTen(integrals, DiyFp::kSignificandSize - (-one.e()),
|
||||
&divisor, &divisor_exponent_plus_one);
|
||||
*kappa = divisor_exponent_plus_one;
|
||||
*length = 0;
|
||||
// Loop invariant: buffer = too_high / 10^kappa (integer division)
|
||||
// The invariant holds for the first iteration: kappa has been initialized
|
||||
// with the divisor exponent + 1. And the divisor is the biggest power of ten
|
||||
// that is smaller than integrals.
|
||||
while (*kappa > 0) {
|
||||
int digit = integrals / divisor;
|
||||
ASSERT(digit <= 9);
|
||||
buffer[*length] = static_cast<char>('0' + digit);
|
||||
(*length)++;
|
||||
integrals %= divisor;
|
||||
(*kappa)--;
|
||||
// Note that kappa now equals the exponent of the divisor and that the
|
||||
// invariant thus holds again.
|
||||
uint64_t rest =
|
||||
(static_cast<uint64_t>(integrals) << -one.e()) + fractionals;
|
||||
// Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e())
|
||||
// Reminder: unsafe_interval.e() == one.e()
|
||||
if (rest < unsafe_interval.f()) {
|
||||
// Rounding down (by not emitting the remaining digits) yields a number
|
||||
// that lies within the unsafe interval.
|
||||
return RoundWeed(buffer, *length, DiyFp::Minus(too_high, w).f(),
|
||||
unsafe_interval.f(), rest,
|
||||
static_cast<uint64_t>(divisor) << -one.e(), unit);
|
||||
}
|
||||
divisor /= 10;
|
||||
}
|
||||
|
||||
// The integrals have been generated. We are at the point of the decimal
|
||||
// separator. In the following loop we simply multiply the remaining digits by
|
||||
// 10 and divide by one. We just need to pay attention to multiply associated
|
||||
// data (like the interval or 'unit'), too.
|
||||
// Note that the multiplication by 10 does not overflow, because w.e >= -60
|
||||
// and thus one.e >= -60.
|
||||
ASSERT(one.e() >= -60);
|
||||
ASSERT(fractionals < one.f());
|
||||
ASSERT(UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF) / 10 >= one.f());
|
||||
for (;;) {
|
||||
fractionals *= 10;
|
||||
unit *= 10;
|
||||
unsafe_interval.set_f(unsafe_interval.f() * 10);
|
||||
// Integer division by one.
|
||||
int digit = static_cast<int>(fractionals >> -one.e());
|
||||
ASSERT(digit <= 9);
|
||||
buffer[*length] = static_cast<char>('0' + digit);
|
||||
(*length)++;
|
||||
fractionals &= one.f() - 1; // Modulo by one.
|
||||
(*kappa)--;
|
||||
if (fractionals < unsafe_interval.f()) {
|
||||
return RoundWeed(buffer, *length, DiyFp::Minus(too_high, w).f() * unit,
|
||||
unsafe_interval.f(), fractionals, one.f(), unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Generates (at most) requested_digits digits of input number w.
|
||||
// w is a floating-point number (DiyFp), consisting of a significand and an
|
||||
// exponent. Its exponent is bounded by kMinimalTargetExponent and
|
||||
// kMaximalTargetExponent.
|
||||
// Hence -60 <= w.e() <= -32.
|
||||
//
|
||||
// Returns false if it fails, in which case the generated digits in the buffer
|
||||
// should not be used.
|
||||
// Preconditions:
|
||||
// * w is correct up to 1 ulp (unit in the last place). That
|
||||
// is, its error must be strictly less than a unit of its last digit.
|
||||
// * kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
|
||||
//
|
||||
// Postconditions: returns false if procedure fails.
|
||||
// otherwise:
|
||||
// * buffer is not null-terminated, but length contains the number of
|
||||
// digits.
|
||||
// * the representation in buffer is the most precise representation of
|
||||
// requested_digits digits.
|
||||
// * buffer contains at most requested_digits digits of w. If there are less
|
||||
// than requested_digits digits then some trailing '0's have been removed.
|
||||
// * kappa is such that
|
||||
// w = buffer * 10^kappa + eps with |eps| < 10^kappa / 2.
|
||||
//
|
||||
// Remark: This procedure takes into account the imprecision of its input
|
||||
// numbers. If the precision is not enough to guarantee all the postconditions
|
||||
// then false is returned. This usually happens rarely, but the failure-rate
|
||||
// increases with higher requested_digits.
|
||||
static bool DigitGenCounted(DiyFp w,
|
||||
int requested_digits,
|
||||
Vector<char> buffer,
|
||||
int* length,
|
||||
int* kappa) {
|
||||
ASSERT(kMinimalTargetExponent <= w.e() && w.e() <= kMaximalTargetExponent);
|
||||
ASSERT(kMinimalTargetExponent >= -60);
|
||||
ASSERT(kMaximalTargetExponent <= -32);
|
||||
// w is assumed to have an error less than 1 unit. Whenever w is scaled we
|
||||
// also scale its error.
|
||||
uint64_t w_error = 1;
|
||||
// We cut the input number into two parts: the integral digits and the
|
||||
// fractional digits. We don't emit any decimal separator, but adapt kappa
|
||||
// instead. Example: instead of writing "1.2" we put "12" into the buffer and
|
||||
// increase kappa by 1.
|
||||
DiyFp one = DiyFp(static_cast<uint64_t>(1) << -w.e(), w.e());
|
||||
// Division by one is a shift.
|
||||
uint32_t integrals = static_cast<uint32_t>(w.f() >> -one.e());
|
||||
// Modulo by one is an and.
|
||||
uint64_t fractionals = w.f() & (one.f() - 1);
|
||||
uint32_t divisor;
|
||||
int divisor_exponent_plus_one;
|
||||
BiggestPowerTen(integrals, DiyFp::kSignificandSize - (-one.e()),
|
||||
&divisor, &divisor_exponent_plus_one);
|
||||
*kappa = divisor_exponent_plus_one;
|
||||
*length = 0;
|
||||
|
||||
// Loop invariant: buffer = w / 10^kappa (integer division)
|
||||
// The invariant holds for the first iteration: kappa has been initialized
|
||||
// with the divisor exponent + 1. And the divisor is the biggest power of ten
|
||||
// that is smaller than 'integrals'.
|
||||
while (*kappa > 0) {
|
||||
int digit = integrals / divisor;
|
||||
ASSERT(digit <= 9);
|
||||
buffer[*length] = static_cast<char>('0' + digit);
|
||||
(*length)++;
|
||||
requested_digits--;
|
||||
integrals %= divisor;
|
||||
(*kappa)--;
|
||||
// Note that kappa now equals the exponent of the divisor and that the
|
||||
// invariant thus holds again.
|
||||
if (requested_digits == 0) break;
|
||||
divisor /= 10;
|
||||
}
|
||||
|
||||
if (requested_digits == 0) {
|
||||
uint64_t rest =
|
||||
(static_cast<uint64_t>(integrals) << -one.e()) + fractionals;
|
||||
return RoundWeedCounted(buffer, *length, rest,
|
||||
static_cast<uint64_t>(divisor) << -one.e(), w_error,
|
||||
kappa);
|
||||
}
|
||||
|
||||
// The integrals have been generated. We are at the point of the decimal
|
||||
// separator. In the following loop we simply multiply the remaining digits by
|
||||
// 10 and divide by one. We just need to pay attention to multiply associated
|
||||
// data (the 'unit'), too.
|
||||
// Note that the multiplication by 10 does not overflow, because w.e >= -60
|
||||
// and thus one.e >= -60.
|
||||
ASSERT(one.e() >= -60);
|
||||
ASSERT(fractionals < one.f());
|
||||
ASSERT(UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF) / 10 >= one.f());
|
||||
while (requested_digits > 0 && fractionals > w_error) {
|
||||
fractionals *= 10;
|
||||
w_error *= 10;
|
||||
// Integer division by one.
|
||||
int digit = static_cast<int>(fractionals >> -one.e());
|
||||
ASSERT(digit <= 9);
|
||||
buffer[*length] = static_cast<char>('0' + digit);
|
||||
(*length)++;
|
||||
requested_digits--;
|
||||
fractionals &= one.f() - 1; // Modulo by one.
|
||||
(*kappa)--;
|
||||
}
|
||||
if (requested_digits != 0) return false;
|
||||
return RoundWeedCounted(buffer, *length, fractionals, one.f(), w_error,
|
||||
kappa);
|
||||
}
|
||||
|
||||
|
||||
// Provides a decimal representation of v.
|
||||
// Returns true if it succeeds, otherwise the result cannot be trusted.
|
||||
// There will be *length digits inside the buffer (not null-terminated).
|
||||
// If the function returns true then
|
||||
// v == (double) (buffer * 10^decimal_exponent).
|
||||
// The digits in the buffer are the shortest representation possible: no
|
||||
// 0.09999999999999999 instead of 0.1. The shorter representation will even be
|
||||
// chosen even if the longer one would be closer to v.
|
||||
// The last digit will be closest to the actual v. That is, even if several
|
||||
// digits might correctly yield 'v' when read again, the closest will be
|
||||
// computed.
|
||||
static bool Grisu3(double v,
|
||||
FastDtoaMode mode,
|
||||
Vector<char> buffer,
|
||||
int* length,
|
||||
int* decimal_exponent) {
|
||||
DiyFp w = Double(v).AsNormalizedDiyFp();
|
||||
// boundary_minus and boundary_plus are the boundaries between v and its
|
||||
// closest floating-point neighbors. Any number strictly between
|
||||
// boundary_minus and boundary_plus will round to v when convert to a double.
|
||||
// Grisu3 will never output representations that lie exactly on a boundary.
|
||||
DiyFp boundary_minus, boundary_plus;
|
||||
if (mode == FAST_DTOA_SHORTEST) {
|
||||
Double(v).NormalizedBoundaries(&boundary_minus, &boundary_plus);
|
||||
} else {
|
||||
ASSERT(mode == FAST_DTOA_SHORTEST_SINGLE);
|
||||
float single_v = static_cast<float>(v);
|
||||
Single(single_v).NormalizedBoundaries(&boundary_minus, &boundary_plus);
|
||||
}
|
||||
ASSERT(boundary_plus.e() == w.e());
|
||||
DiyFp ten_mk; // Cached power of ten: 10^-k
|
||||
int mk; // -k
|
||||
int ten_mk_minimal_binary_exponent =
|
||||
kMinimalTargetExponent - (w.e() + DiyFp::kSignificandSize);
|
||||
int ten_mk_maximal_binary_exponent =
|
||||
kMaximalTargetExponent - (w.e() + DiyFp::kSignificandSize);
|
||||
PowersOfTenCache::GetCachedPowerForBinaryExponentRange(
|
||||
ten_mk_minimal_binary_exponent,
|
||||
ten_mk_maximal_binary_exponent,
|
||||
&ten_mk, &mk);
|
||||
ASSERT((kMinimalTargetExponent <= w.e() + ten_mk.e() +
|
||||
DiyFp::kSignificandSize) &&
|
||||
(kMaximalTargetExponent >= w.e() + ten_mk.e() +
|
||||
DiyFp::kSignificandSize));
|
||||
// Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
|
||||
// 64 bit significand and ten_mk is thus only precise up to 64 bits.
|
||||
|
||||
// The DiyFp::Times procedure rounds its result, and ten_mk is approximated
|
||||
// too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
|
||||
// off by a small amount.
|
||||
// In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
|
||||
// In other words: let f = scaled_w.f() and e = scaled_w.e(), then
|
||||
// (f-1) * 2^e < w*10^k < (f+1) * 2^e
|
||||
DiyFp scaled_w = DiyFp::Times(w, ten_mk);
|
||||
ASSERT(scaled_w.e() ==
|
||||
boundary_plus.e() + ten_mk.e() + DiyFp::kSignificandSize);
|
||||
// In theory it would be possible to avoid some recomputations by computing
|
||||
// the difference between w and boundary_minus/plus (a power of 2) and to
|
||||
// compute scaled_boundary_minus/plus by subtracting/adding from
|
||||
// scaled_w. However the code becomes much less readable and the speed
|
||||
// enhancements are not terriffic.
|
||||
DiyFp scaled_boundary_minus = DiyFp::Times(boundary_minus, ten_mk);
|
||||
DiyFp scaled_boundary_plus = DiyFp::Times(boundary_plus, ten_mk);
|
||||
|
||||
// DigitGen will generate the digits of scaled_w. Therefore we have
|
||||
// v == (double) (scaled_w * 10^-mk).
|
||||
// Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an
|
||||
// integer than it will be updated. For instance if scaled_w == 1.23 then
|
||||
// the buffer will be filled with "123" und the decimal_exponent will be
|
||||
// decreased by 2.
|
||||
int kappa;
|
||||
bool result = DigitGen(scaled_boundary_minus, scaled_w, scaled_boundary_plus,
|
||||
buffer, length, &kappa);
|
||||
*decimal_exponent = -mk + kappa;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// The "counted" version of grisu3 (see above) only generates requested_digits
|
||||
// number of digits. This version does not generate the shortest representation,
|
||||
// and with enough requested digits 0.1 will at some point print as 0.9999999...
|
||||
// Grisu3 is too imprecise for real halfway cases (1.5 will not work) and
|
||||
// therefore the rounding strategy for halfway cases is irrelevant.
|
||||
static bool Grisu3Counted(double v,
|
||||
int requested_digits,
|
||||
Vector<char> buffer,
|
||||
int* length,
|
||||
int* decimal_exponent) {
|
||||
DiyFp w = Double(v).AsNormalizedDiyFp();
|
||||
DiyFp ten_mk; // Cached power of ten: 10^-k
|
||||
int mk; // -k
|
||||
int ten_mk_minimal_binary_exponent =
|
||||
kMinimalTargetExponent - (w.e() + DiyFp::kSignificandSize);
|
||||
int ten_mk_maximal_binary_exponent =
|
||||
kMaximalTargetExponent - (w.e() + DiyFp::kSignificandSize);
|
||||
PowersOfTenCache::GetCachedPowerForBinaryExponentRange(
|
||||
ten_mk_minimal_binary_exponent,
|
||||
ten_mk_maximal_binary_exponent,
|
||||
&ten_mk, &mk);
|
||||
ASSERT((kMinimalTargetExponent <= w.e() + ten_mk.e() +
|
||||
DiyFp::kSignificandSize) &&
|
||||
(kMaximalTargetExponent >= w.e() + ten_mk.e() +
|
||||
DiyFp::kSignificandSize));
|
||||
// Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
|
||||
// 64 bit significand and ten_mk is thus only precise up to 64 bits.
|
||||
|
||||
// The DiyFp::Times procedure rounds its result, and ten_mk is approximated
|
||||
// too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
|
||||
// off by a small amount.
|
||||
// In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
|
||||
// In other words: let f = scaled_w.f() and e = scaled_w.e(), then
|
||||
// (f-1) * 2^e < w*10^k < (f+1) * 2^e
|
||||
DiyFp scaled_w = DiyFp::Times(w, ten_mk);
|
||||
|
||||
// We now have (double) (scaled_w * 10^-mk).
|
||||
// DigitGen will generate the first requested_digits digits of scaled_w and
|
||||
// return together with a kappa such that scaled_w ~= buffer * 10^kappa. (It
|
||||
// will not always be exactly the same since DigitGenCounted only produces a
|
||||
// limited number of digits.)
|
||||
int kappa;
|
||||
bool result = DigitGenCounted(scaled_w, requested_digits,
|
||||
buffer, length, &kappa);
|
||||
*decimal_exponent = -mk + kappa;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool FastDtoa(double v,
|
||||
FastDtoaMode mode,
|
||||
int requested_digits,
|
||||
Vector<char> buffer,
|
||||
int* length,
|
||||
int* decimal_point) {
|
||||
ASSERT(v > 0);
|
||||
ASSERT(!Double(v).IsSpecial());
|
||||
|
||||
bool result = false;
|
||||
int decimal_exponent = 0;
|
||||
switch (mode) {
|
||||
case FAST_DTOA_SHORTEST:
|
||||
case FAST_DTOA_SHORTEST_SINGLE:
|
||||
result = Grisu3(v, mode, buffer, length, &decimal_exponent);
|
||||
break;
|
||||
case FAST_DTOA_PRECISION:
|
||||
result = Grisu3Counted(v, requested_digits,
|
||||
buffer, length, &decimal_exponent);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
if (result) {
|
||||
*decimal_point = *length + decimal_exponent;
|
||||
buffer[*length] = '\0';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace double_conversion
|
@ -1,88 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_FAST_DTOA_H_
|
||||
#define DOUBLE_CONVERSION_FAST_DTOA_H_
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
enum FastDtoaMode {
|
||||
// Computes the shortest representation of the given input. The returned
|
||||
// result will be the most accurate number of this length. Longer
|
||||
// representations might be more accurate.
|
||||
FAST_DTOA_SHORTEST,
|
||||
// Same as FAST_DTOA_SHORTEST but for single-precision floats.
|
||||
FAST_DTOA_SHORTEST_SINGLE,
|
||||
// Computes a representation where the precision (number of digits) is
|
||||
// given as input. The precision is independent of the decimal point.
|
||||
FAST_DTOA_PRECISION
|
||||
};
|
||||
|
||||
// FastDtoa will produce at most kFastDtoaMaximalLength digits. This does not
|
||||
// include the terminating '\0' character.
|
||||
static const int kFastDtoaMaximalLength = 17;
|
||||
// Same for single-precision numbers.
|
||||
static const int kFastDtoaMaximalSingleLength = 9;
|
||||
|
||||
// Provides a decimal representation of v.
|
||||
// The result should be interpreted as buffer * 10^(point - length).
|
||||
//
|
||||
// Precondition:
|
||||
// * v must be a strictly positive finite double.
|
||||
//
|
||||
// Returns true if it succeeds, otherwise the result can not be trusted.
|
||||
// There will be *length digits inside the buffer followed by a null terminator.
|
||||
// If the function returns true and mode equals
|
||||
// - FAST_DTOA_SHORTEST, then
|
||||
// the parameter requested_digits is ignored.
|
||||
// The result satisfies
|
||||
// v == (double) (buffer * 10^(point - length)).
|
||||
// The digits in the buffer are the shortest representation possible. E.g.
|
||||
// if 0.099999999999 and 0.1 represent the same double then "1" is returned
|
||||
// with point = 0.
|
||||
// The last digit will be closest to the actual v. That is, even if several
|
||||
// digits might correctly yield 'v' when read again, the buffer will contain
|
||||
// the one closest to v.
|
||||
// - FAST_DTOA_PRECISION, then
|
||||
// the buffer contains requested_digits digits.
|
||||
// the difference v - (buffer * 10^(point-length)) is closest to zero for
|
||||
// all possible representations of requested_digits digits.
|
||||
// If there are two values that are equally close, then FastDtoa returns
|
||||
// false.
|
||||
// For both modes the buffer must be large enough to hold the result.
|
||||
bool FastDtoa(double d,
|
||||
FastDtoaMode mode,
|
||||
int requested_digits,
|
||||
Vector<char> buffer,
|
||||
int* length,
|
||||
int* decimal_point);
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_FAST_DTOA_H_
|
@ -1,404 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "fixed-dtoa.h"
|
||||
#include "ieee.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
// Represents a 128bit type. This class should be replaced by a native type on
|
||||
// platforms that support 128bit integers.
|
||||
class UInt128 {
|
||||
public:
|
||||
UInt128() : high_bits_(0), low_bits_(0) { }
|
||||
UInt128(uint64_t high, uint64_t low) : high_bits_(high), low_bits_(low) { }
|
||||
|
||||
void Multiply(uint32_t multiplicand) {
|
||||
uint64_t accumulator;
|
||||
|
||||
accumulator = (low_bits_ & kMask32) * multiplicand;
|
||||
uint32_t part = static_cast<uint32_t>(accumulator & kMask32);
|
||||
accumulator >>= 32;
|
||||
accumulator = accumulator + (low_bits_ >> 32) * multiplicand;
|
||||
low_bits_ = (accumulator << 32) + part;
|
||||
accumulator >>= 32;
|
||||
accumulator = accumulator + (high_bits_ & kMask32) * multiplicand;
|
||||
part = static_cast<uint32_t>(accumulator & kMask32);
|
||||
accumulator >>= 32;
|
||||
accumulator = accumulator + (high_bits_ >> 32) * multiplicand;
|
||||
high_bits_ = (accumulator << 32) + part;
|
||||
ASSERT((accumulator >> 32) == 0);
|
||||
}
|
||||
|
||||
void Shift(int shift_amount) {
|
||||
ASSERT(-64 <= shift_amount && shift_amount <= 64);
|
||||
if (shift_amount == 0) {
|
||||
return;
|
||||
} else if (shift_amount == -64) {
|
||||
high_bits_ = low_bits_;
|
||||
low_bits_ = 0;
|
||||
} else if (shift_amount == 64) {
|
||||
low_bits_ = high_bits_;
|
||||
high_bits_ = 0;
|
||||
} else if (shift_amount <= 0) {
|
||||
high_bits_ <<= -shift_amount;
|
||||
high_bits_ += low_bits_ >> (64 + shift_amount);
|
||||
low_bits_ <<= -shift_amount;
|
||||
} else {
|
||||
low_bits_ >>= shift_amount;
|
||||
low_bits_ += high_bits_ << (64 - shift_amount);
|
||||
high_bits_ >>= shift_amount;
|
||||
}
|
||||
}
|
||||
|
||||
// Modifies *this to *this MOD (2^power).
|
||||
// Returns *this DIV (2^power).
|
||||
int DivModPowerOf2(int power) {
|
||||
if (power >= 64) {
|
||||
int result = static_cast<int>(high_bits_ >> (power - 64));
|
||||
high_bits_ -= static_cast<uint64_t>(result) << (power - 64);
|
||||
return result;
|
||||
} else {
|
||||
uint64_t part_low = low_bits_ >> power;
|
||||
uint64_t part_high = high_bits_ << (64 - power);
|
||||
int result = static_cast<int>(part_low + part_high);
|
||||
high_bits_ = 0;
|
||||
low_bits_ -= part_low << power;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsZero() const {
|
||||
return high_bits_ == 0 && low_bits_ == 0;
|
||||
}
|
||||
|
||||
int BitAt(int position) {
|
||||
if (position >= 64) {
|
||||
return static_cast<int>(high_bits_ >> (position - 64)) & 1;
|
||||
} else {
|
||||
return static_cast<int>(low_bits_ >> position) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static const uint64_t kMask32 = 0xFFFFFFFF;
|
||||
// Value == (high_bits_ << 64) + low_bits_
|
||||
uint64_t high_bits_;
|
||||
uint64_t low_bits_;
|
||||
};
|
||||
|
||||
|
||||
static const int kDoubleSignificandSize = 53; // Includes the hidden bit.
|
||||
|
||||
|
||||
static void FillDigits32FixedLength(uint32_t number, int requested_length,
|
||||
Vector<char> buffer, int* length) {
|
||||
for (int i = requested_length - 1; i >= 0; --i) {
|
||||
buffer[(*length) + i] = '0' + number % 10;
|
||||
number /= 10;
|
||||
}
|
||||
*length += requested_length;
|
||||
}
|
||||
|
||||
|
||||
static void FillDigits32(uint32_t number, Vector<char> buffer, int* length) {
|
||||
int number_length = 0;
|
||||
// We fill the digits in reverse order and exchange them afterwards.
|
||||
while (number != 0) {
|
||||
int digit = number % 10;
|
||||
number /= 10;
|
||||
buffer[(*length) + number_length] = static_cast<char>('0' + digit);
|
||||
number_length++;
|
||||
}
|
||||
// Exchange the digits.
|
||||
int i = *length;
|
||||
int j = *length + number_length - 1;
|
||||
while (i < j) {
|
||||
char tmp = buffer[i];
|
||||
buffer[i] = buffer[j];
|
||||
buffer[j] = tmp;
|
||||
i++;
|
||||
j--;
|
||||
}
|
||||
*length += number_length;
|
||||
}
|
||||
|
||||
|
||||
static void FillDigits64FixedLength(uint64_t number,
|
||||
Vector<char> buffer, int* length) {
|
||||
const uint32_t kTen7 = 10000000;
|
||||
// For efficiency cut the number into 3 uint32_t parts, and print those.
|
||||
uint32_t part2 = static_cast<uint32_t>(number % kTen7);
|
||||
number /= kTen7;
|
||||
uint32_t part1 = static_cast<uint32_t>(number % kTen7);
|
||||
uint32_t part0 = static_cast<uint32_t>(number / kTen7);
|
||||
|
||||
FillDigits32FixedLength(part0, 3, buffer, length);
|
||||
FillDigits32FixedLength(part1, 7, buffer, length);
|
||||
FillDigits32FixedLength(part2, 7, buffer, length);
|
||||
}
|
||||
|
||||
|
||||
static void FillDigits64(uint64_t number, Vector<char> buffer, int* length) {
|
||||
const uint32_t kTen7 = 10000000;
|
||||
// For efficiency cut the number into 3 uint32_t parts, and print those.
|
||||
uint32_t part2 = static_cast<uint32_t>(number % kTen7);
|
||||
number /= kTen7;
|
||||
uint32_t part1 = static_cast<uint32_t>(number % kTen7);
|
||||
uint32_t part0 = static_cast<uint32_t>(number / kTen7);
|
||||
|
||||
if (part0 != 0) {
|
||||
FillDigits32(part0, buffer, length);
|
||||
FillDigits32FixedLength(part1, 7, buffer, length);
|
||||
FillDigits32FixedLength(part2, 7, buffer, length);
|
||||
} else if (part1 != 0) {
|
||||
FillDigits32(part1, buffer, length);
|
||||
FillDigits32FixedLength(part2, 7, buffer, length);
|
||||
} else {
|
||||
FillDigits32(part2, buffer, length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void RoundUp(Vector<char> buffer, int* length, int* decimal_point) {
|
||||
// An empty buffer represents 0.
|
||||
if (*length == 0) {
|
||||
buffer[0] = '1';
|
||||
*decimal_point = 1;
|
||||
*length = 1;
|
||||
return;
|
||||
}
|
||||
// Round the last digit until we either have a digit that was not '9' or until
|
||||
// we reached the first digit.
|
||||
buffer[(*length) - 1]++;
|
||||
for (int i = (*length) - 1; i > 0; --i) {
|
||||
if (buffer[i] != '0' + 10) {
|
||||
return;
|
||||
}
|
||||
buffer[i] = '0';
|
||||
buffer[i - 1]++;
|
||||
}
|
||||
// If the first digit is now '0' + 10, we would need to set it to '0' and add
|
||||
// a '1' in front. However we reach the first digit only if all following
|
||||
// digits had been '9' before rounding up. Now all trailing digits are '0' and
|
||||
// we simply switch the first digit to '1' and update the decimal-point
|
||||
// (indicating that the point is now one digit to the right).
|
||||
if (buffer[0] == '0' + 10) {
|
||||
buffer[0] = '1';
|
||||
(*decimal_point)++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The given fractionals number represents a fixed-point number with binary
|
||||
// point at bit (-exponent).
|
||||
// Preconditions:
|
||||
// -128 <= exponent <= 0.
|
||||
// 0 <= fractionals * 2^exponent < 1
|
||||
// The buffer holds the result.
|
||||
// The function will round its result. During the rounding-process digits not
|
||||
// generated by this function might be updated, and the decimal-point variable
|
||||
// might be updated. If this function generates the digits 99 and the buffer
|
||||
// already contained "199" (thus yielding a buffer of "19999") then a
|
||||
// rounding-up will change the contents of the buffer to "20000".
|
||||
static void FillFractionals(uint64_t fractionals, int exponent,
|
||||
int fractional_count, Vector<char> buffer,
|
||||
int* length, int* decimal_point) {
|
||||
ASSERT(-128 <= exponent && exponent <= 0);
|
||||
// 'fractionals' is a fixed-point number, with binary point at bit
|
||||
// (-exponent). Inside the function the non-converted remainder of fractionals
|
||||
// is a fixed-point number, with binary point at bit 'point'.
|
||||
if (-exponent <= 64) {
|
||||
// One 64 bit number is sufficient.
|
||||
ASSERT(fractionals >> 56 == 0);
|
||||
int point = -exponent;
|
||||
for (int i = 0; i < fractional_count; ++i) {
|
||||
if (fractionals == 0) break;
|
||||
// Instead of multiplying by 10 we multiply by 5 and adjust the point
|
||||
// location. This way the fractionals variable will not overflow.
|
||||
// Invariant at the beginning of the loop: fractionals < 2^point.
|
||||
// Initially we have: point <= 64 and fractionals < 2^56
|
||||
// After each iteration the point is decremented by one.
|
||||
// Note that 5^3 = 125 < 128 = 2^7.
|
||||
// Therefore three iterations of this loop will not overflow fractionals
|
||||
// (even without the subtraction at the end of the loop body). At this
|
||||
// time point will satisfy point <= 61 and therefore fractionals < 2^point
|
||||
// and any further multiplication of fractionals by 5 will not overflow.
|
||||
fractionals *= 5;
|
||||
point--;
|
||||
int digit = static_cast<int>(fractionals >> point);
|
||||
ASSERT(digit <= 9);
|
||||
buffer[*length] = static_cast<char>('0' + digit);
|
||||
(*length)++;
|
||||
fractionals -= static_cast<uint64_t>(digit) << point;
|
||||
}
|
||||
// If the first bit after the point is set we have to round up.
|
||||
if (((fractionals >> (point - 1)) & 1) == 1) {
|
||||
RoundUp(buffer, length, decimal_point);
|
||||
}
|
||||
} else { // We need 128 bits.
|
||||
ASSERT(64 < -exponent && -exponent <= 128);
|
||||
UInt128 fractionals128 = UInt128(fractionals, 0);
|
||||
fractionals128.Shift(-exponent - 64);
|
||||
int point = 128;
|
||||
for (int i = 0; i < fractional_count; ++i) {
|
||||
if (fractionals128.IsZero()) break;
|
||||
// As before: instead of multiplying by 10 we multiply by 5 and adjust the
|
||||
// point location.
|
||||
// This multiplication will not overflow for the same reasons as before.
|
||||
fractionals128.Multiply(5);
|
||||
point--;
|
||||
int digit = fractionals128.DivModPowerOf2(point);
|
||||
ASSERT(digit <= 9);
|
||||
buffer[*length] = static_cast<char>('0' + digit);
|
||||
(*length)++;
|
||||
}
|
||||
if (fractionals128.BitAt(point - 1) == 1) {
|
||||
RoundUp(buffer, length, decimal_point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Removes leading and trailing zeros.
|
||||
// If leading zeros are removed then the decimal point position is adjusted.
|
||||
static void TrimZeros(Vector<char> buffer, int* length, int* decimal_point) {
|
||||
while (*length > 0 && buffer[(*length) - 1] == '0') {
|
||||
(*length)--;
|
||||
}
|
||||
int first_non_zero = 0;
|
||||
while (first_non_zero < *length && buffer[first_non_zero] == '0') {
|
||||
first_non_zero++;
|
||||
}
|
||||
if (first_non_zero != 0) {
|
||||
for (int i = first_non_zero; i < *length; ++i) {
|
||||
buffer[i - first_non_zero] = buffer[i];
|
||||
}
|
||||
*length -= first_non_zero;
|
||||
*decimal_point -= first_non_zero;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool FastFixedDtoa(double v,
|
||||
int fractional_count,
|
||||
Vector<char> buffer,
|
||||
int* length,
|
||||
int* decimal_point) {
|
||||
const uint32_t kMaxUInt32 = 0xFFFFFFFF;
|
||||
uint64_t significand = Double(v).Significand();
|
||||
int exponent = Double(v).Exponent();
|
||||
// v = significand * 2^exponent (with significand a 53bit integer).
|
||||
// If the exponent is larger than 20 (i.e. we may have a 73bit number) then we
|
||||
// don't know how to compute the representation. 2^73 ~= 9.5*10^21.
|
||||
// If necessary this limit could probably be increased, but we don't need
|
||||
// more.
|
||||
if (exponent > 20) return false;
|
||||
if (fractional_count > 20) return false;
|
||||
*length = 0;
|
||||
// At most kDoubleSignificandSize bits of the significand are non-zero.
|
||||
// Given a 64 bit integer we have 11 0s followed by 53 potentially non-zero
|
||||
// bits: 0..11*..0xxx..53*..xx
|
||||
if (exponent + kDoubleSignificandSize > 64) {
|
||||
// The exponent must be > 11.
|
||||
//
|
||||
// We know that v = significand * 2^exponent.
|
||||
// And the exponent > 11.
|
||||
// We simplify the task by dividing v by 10^17.
|
||||
// The quotient delivers the first digits, and the remainder fits into a 64
|
||||
// bit number.
|
||||
// Dividing by 10^17 is equivalent to dividing by 5^17*2^17.
|
||||
const uint64_t kFive17 = UINT64_2PART_C(0xB1, A2BC2EC5); // 5^17
|
||||
uint64_t divisor = kFive17;
|
||||
int divisor_power = 17;
|
||||
uint64_t dividend = significand;
|
||||
uint32_t quotient;
|
||||
uint64_t remainder;
|
||||
// Let v = f * 2^e with f == significand and e == exponent.
|
||||
// Then need q (quotient) and r (remainder) as follows:
|
||||
// v = q * 10^17 + r
|
||||
// f * 2^e = q * 10^17 + r
|
||||
// f * 2^e = q * 5^17 * 2^17 + r
|
||||
// If e > 17 then
|
||||
// f * 2^(e-17) = q * 5^17 + r/2^17
|
||||
// else
|
||||
// f = q * 5^17 * 2^(17-e) + r/2^e
|
||||
if (exponent > divisor_power) {
|
||||
// We only allow exponents of up to 20 and therefore (17 - e) <= 3
|
||||
dividend <<= exponent - divisor_power;
|
||||
quotient = static_cast<uint32_t>(dividend / divisor);
|
||||
remainder = (dividend % divisor) << divisor_power;
|
||||
} else {
|
||||
divisor <<= divisor_power - exponent;
|
||||
quotient = static_cast<uint32_t>(dividend / divisor);
|
||||
remainder = (dividend % divisor) << exponent;
|
||||
}
|
||||
FillDigits32(quotient, buffer, length);
|
||||
FillDigits64FixedLength(remainder, buffer, length);
|
||||
*decimal_point = *length;
|
||||
} else if (exponent >= 0) {
|
||||
// 0 <= exponent <= 11
|
||||
significand <<= exponent;
|
||||
FillDigits64(significand, buffer, length);
|
||||
*decimal_point = *length;
|
||||
} else if (exponent > -kDoubleSignificandSize) {
|
||||
// We have to cut the number.
|
||||
uint64_t integrals = significand >> -exponent;
|
||||
uint64_t fractionals = significand - (integrals << -exponent);
|
||||
if (integrals > kMaxUInt32) {
|
||||
FillDigits64(integrals, buffer, length);
|
||||
} else {
|
||||
FillDigits32(static_cast<uint32_t>(integrals), buffer, length);
|
||||
}
|
||||
*decimal_point = *length;
|
||||
FillFractionals(fractionals, exponent, fractional_count,
|
||||
buffer, length, decimal_point);
|
||||
} else if (exponent < -128) {
|
||||
// This configuration (with at most 20 digits) means that all digits must be
|
||||
// 0.
|
||||
ASSERT(fractional_count <= 20);
|
||||
buffer[0] = '\0';
|
||||
*length = 0;
|
||||
*decimal_point = -fractional_count;
|
||||
} else {
|
||||
*decimal_point = 0;
|
||||
FillFractionals(significand, exponent, fractional_count,
|
||||
buffer, length, decimal_point);
|
||||
}
|
||||
TrimZeros(buffer, length, decimal_point);
|
||||
buffer[*length] = '\0';
|
||||
if ((*length) == 0) {
|
||||
// The string is empty and the decimal_point thus has no importance. Mimick
|
||||
// Gay's dtoa and and set it to -fractional_count.
|
||||
*decimal_point = -fractional_count;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace double_conversion
|
@ -1,56 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_FIXED_DTOA_H_
|
||||
#define DOUBLE_CONVERSION_FIXED_DTOA_H_
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
// Produces digits necessary to print a given number with
|
||||
// 'fractional_count' digits after the decimal point.
|
||||
// The buffer must be big enough to hold the result plus one terminating null
|
||||
// character.
|
||||
//
|
||||
// The produced digits might be too short in which case the caller has to fill
|
||||
// the gaps with '0's.
|
||||
// Example: FastFixedDtoa(0.001, 5, ...) is allowed to return buffer = "1", and
|
||||
// decimal_point = -2.
|
||||
// Halfway cases are rounded towards +/-Infinity (away from 0). The call
|
||||
// FastFixedDtoa(0.15, 2, ...) thus returns buffer = "2", decimal_point = 0.
|
||||
// The returned buffer may contain digits that would be truncated from the
|
||||
// shortest representation of the input.
|
||||
//
|
||||
// This method only works for some parameters. If it can't handle the input it
|
||||
// returns false. The output is null-terminated when the function succeeds.
|
||||
bool FastFixedDtoa(double v, int fractional_count,
|
||||
Vector<char> buffer, int* length, int* decimal_point);
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_FIXED_DTOA_H_
|
402
ios/Pods/DoubleConversion/double-conversion/ieee.h
generated
402
ios/Pods/DoubleConversion/double-conversion/ieee.h
generated
@ -1,402 +0,0 @@
|
||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_DOUBLE_H_
|
||||
#define DOUBLE_CONVERSION_DOUBLE_H_
|
||||
|
||||
#include "diy-fp.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
// We assume that doubles and uint64_t have the same endianness.
|
||||
static uint64_t double_to_uint64(double d) { return BitCast<uint64_t>(d); }
|
||||
static double uint64_to_double(uint64_t d64) { return BitCast<double>(d64); }
|
||||
static uint32_t float_to_uint32(float f) { return BitCast<uint32_t>(f); }
|
||||
static float uint32_to_float(uint32_t d32) { return BitCast<float>(d32); }
|
||||
|
||||
// Helper functions for doubles.
|
||||
class Double {
|
||||
public:
|
||||
static const uint64_t kSignMask = UINT64_2PART_C(0x80000000, 00000000);
|
||||
static const uint64_t kExponentMask = UINT64_2PART_C(0x7FF00000, 00000000);
|
||||
static const uint64_t kSignificandMask = UINT64_2PART_C(0x000FFFFF, FFFFFFFF);
|
||||
static const uint64_t kHiddenBit = UINT64_2PART_C(0x00100000, 00000000);
|
||||
static const int kPhysicalSignificandSize = 52; // Excludes the hidden bit.
|
||||
static const int kSignificandSize = 53;
|
||||
|
||||
Double() : d64_(0) {}
|
||||
explicit Double(double d) : d64_(double_to_uint64(d)) {}
|
||||
explicit Double(uint64_t d64) : d64_(d64) {}
|
||||
explicit Double(DiyFp diy_fp)
|
||||
: d64_(DiyFpToUint64(diy_fp)) {}
|
||||
|
||||
// The value encoded by this Double must be greater or equal to +0.0.
|
||||
// It must not be special (infinity, or NaN).
|
||||
DiyFp AsDiyFp() const {
|
||||
ASSERT(Sign() > 0);
|
||||
ASSERT(!IsSpecial());
|
||||
return DiyFp(Significand(), Exponent());
|
||||
}
|
||||
|
||||
// The value encoded by this Double must be strictly greater than 0.
|
||||
DiyFp AsNormalizedDiyFp() const {
|
||||
ASSERT(value() > 0.0);
|
||||
uint64_t f = Significand();
|
||||
int e = Exponent();
|
||||
|
||||
// The current double could be a denormal.
|
||||
while ((f & kHiddenBit) == 0) {
|
||||
f <<= 1;
|
||||
e--;
|
||||
}
|
||||
// Do the final shifts in one go.
|
||||
f <<= DiyFp::kSignificandSize - kSignificandSize;
|
||||
e -= DiyFp::kSignificandSize - kSignificandSize;
|
||||
return DiyFp(f, e);
|
||||
}
|
||||
|
||||
// Returns the double's bit as uint64.
|
||||
uint64_t AsUint64() const {
|
||||
return d64_;
|
||||
}
|
||||
|
||||
// Returns the next greater double. Returns +infinity on input +infinity.
|
||||
double NextDouble() const {
|
||||
if (d64_ == kInfinity) return Double(kInfinity).value();
|
||||
if (Sign() < 0 && Significand() == 0) {
|
||||
// -0.0
|
||||
return 0.0;
|
||||
}
|
||||
if (Sign() < 0) {
|
||||
return Double(d64_ - 1).value();
|
||||
} else {
|
||||
return Double(d64_ + 1).value();
|
||||
}
|
||||
}
|
||||
|
||||
double PreviousDouble() const {
|
||||
if (d64_ == (kInfinity | kSignMask)) return -Double::Infinity();
|
||||
if (Sign() < 0) {
|
||||
return Double(d64_ + 1).value();
|
||||
} else {
|
||||
if (Significand() == 0) return -0.0;
|
||||
return Double(d64_ - 1).value();
|
||||
}
|
||||
}
|
||||
|
||||
int Exponent() const {
|
||||
if (IsDenormal()) return kDenormalExponent;
|
||||
|
||||
uint64_t d64 = AsUint64();
|
||||
int biased_e =
|
||||
static_cast<int>((d64 & kExponentMask) >> kPhysicalSignificandSize);
|
||||
return biased_e - kExponentBias;
|
||||
}
|
||||
|
||||
uint64_t Significand() const {
|
||||
uint64_t d64 = AsUint64();
|
||||
uint64_t significand = d64 & kSignificandMask;
|
||||
if (!IsDenormal()) {
|
||||
return significand + kHiddenBit;
|
||||
} else {
|
||||
return significand;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the double is a denormal.
|
||||
bool IsDenormal() const {
|
||||
uint64_t d64 = AsUint64();
|
||||
return (d64 & kExponentMask) == 0;
|
||||
}
|
||||
|
||||
// We consider denormals not to be special.
|
||||
// Hence only Infinity and NaN are special.
|
||||
bool IsSpecial() const {
|
||||
uint64_t d64 = AsUint64();
|
||||
return (d64 & kExponentMask) == kExponentMask;
|
||||
}
|
||||
|
||||
bool IsNan() const {
|
||||
uint64_t d64 = AsUint64();
|
||||
return ((d64 & kExponentMask) == kExponentMask) &&
|
||||
((d64 & kSignificandMask) != 0);
|
||||
}
|
||||
|
||||
bool IsInfinite() const {
|
||||
uint64_t d64 = AsUint64();
|
||||
return ((d64 & kExponentMask) == kExponentMask) &&
|
||||
((d64 & kSignificandMask) == 0);
|
||||
}
|
||||
|
||||
int Sign() const {
|
||||
uint64_t d64 = AsUint64();
|
||||
return (d64 & kSignMask) == 0? 1: -1;
|
||||
}
|
||||
|
||||
// Precondition: the value encoded by this Double must be greater or equal
|
||||
// than +0.0.
|
||||
DiyFp UpperBoundary() const {
|
||||
ASSERT(Sign() > 0);
|
||||
return DiyFp(Significand() * 2 + 1, Exponent() - 1);
|
||||
}
|
||||
|
||||
// Computes the two boundaries of this.
|
||||
// The bigger boundary (m_plus) is normalized. The lower boundary has the same
|
||||
// exponent as m_plus.
|
||||
// Precondition: the value encoded by this Double must be greater than 0.
|
||||
void NormalizedBoundaries(DiyFp* out_m_minus, DiyFp* out_m_plus) const {
|
||||
ASSERT(value() > 0.0);
|
||||
DiyFp v = this->AsDiyFp();
|
||||
DiyFp m_plus = DiyFp::Normalize(DiyFp((v.f() << 1) + 1, v.e() - 1));
|
||||
DiyFp m_minus;
|
||||
if (LowerBoundaryIsCloser()) {
|
||||
m_minus = DiyFp((v.f() << 2) - 1, v.e() - 2);
|
||||
} else {
|
||||
m_minus = DiyFp((v.f() << 1) - 1, v.e() - 1);
|
||||
}
|
||||
m_minus.set_f(m_minus.f() << (m_minus.e() - m_plus.e()));
|
||||
m_minus.set_e(m_plus.e());
|
||||
*out_m_plus = m_plus;
|
||||
*out_m_minus = m_minus;
|
||||
}
|
||||
|
||||
bool LowerBoundaryIsCloser() const {
|
||||
// The boundary is closer if the significand is of the form f == 2^p-1 then
|
||||
// the lower boundary is closer.
|
||||
// Think of v = 1000e10 and v- = 9999e9.
|
||||
// Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but
|
||||
// at a distance of 1e8.
|
||||
// The only exception is for the smallest normal: the largest denormal is
|
||||
// at the same distance as its successor.
|
||||
// Note: denormals have the same exponent as the smallest normals.
|
||||
bool physical_significand_is_zero = ((AsUint64() & kSignificandMask) == 0);
|
||||
return physical_significand_is_zero && (Exponent() != kDenormalExponent);
|
||||
}
|
||||
|
||||
double value() const { return uint64_to_double(d64_); }
|
||||
|
||||
// Returns the significand size for a given order of magnitude.
|
||||
// If v = f*2^e with 2^p-1 <= f <= 2^p then p+e is v's order of magnitude.
|
||||
// This function returns the number of significant binary digits v will have
|
||||
// once it's encoded into a double. In almost all cases this is equal to
|
||||
// kSignificandSize. The only exceptions are denormals. They start with
|
||||
// leading zeroes and their effective significand-size is hence smaller.
|
||||
static int SignificandSizeForOrderOfMagnitude(int order) {
|
||||
if (order >= (kDenormalExponent + kSignificandSize)) {
|
||||
return kSignificandSize;
|
||||
}
|
||||
if (order <= kDenormalExponent) return 0;
|
||||
return order - kDenormalExponent;
|
||||
}
|
||||
|
||||
static double Infinity() {
|
||||
return Double(kInfinity).value();
|
||||
}
|
||||
|
||||
static double NaN() {
|
||||
return Double(kNaN).value();
|
||||
}
|
||||
|
||||
private:
|
||||
static const int kExponentBias = 0x3FF + kPhysicalSignificandSize;
|
||||
static const int kDenormalExponent = -kExponentBias + 1;
|
||||
static const int kMaxExponent = 0x7FF - kExponentBias;
|
||||
static const uint64_t kInfinity = UINT64_2PART_C(0x7FF00000, 00000000);
|
||||
static const uint64_t kNaN = UINT64_2PART_C(0x7FF80000, 00000000);
|
||||
|
||||
const uint64_t d64_;
|
||||
|
||||
static uint64_t DiyFpToUint64(DiyFp diy_fp) {
|
||||
uint64_t significand = diy_fp.f();
|
||||
int exponent = diy_fp.e();
|
||||
while (significand > kHiddenBit + kSignificandMask) {
|
||||
significand >>= 1;
|
||||
exponent++;
|
||||
}
|
||||
if (exponent >= kMaxExponent) {
|
||||
return kInfinity;
|
||||
}
|
||||
if (exponent < kDenormalExponent) {
|
||||
return 0;
|
||||
}
|
||||
while (exponent > kDenormalExponent && (significand & kHiddenBit) == 0) {
|
||||
significand <<= 1;
|
||||
exponent--;
|
||||
}
|
||||
uint64_t biased_exponent;
|
||||
if (exponent == kDenormalExponent && (significand & kHiddenBit) == 0) {
|
||||
biased_exponent = 0;
|
||||
} else {
|
||||
biased_exponent = static_cast<uint64_t>(exponent + kExponentBias);
|
||||
}
|
||||
return (significand & kSignificandMask) |
|
||||
(biased_exponent << kPhysicalSignificandSize);
|
||||
}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Double);
|
||||
};
|
||||
|
||||
class Single {
|
||||
public:
|
||||
static const uint32_t kSignMask = 0x80000000;
|
||||
static const uint32_t kExponentMask = 0x7F800000;
|
||||
static const uint32_t kSignificandMask = 0x007FFFFF;
|
||||
static const uint32_t kHiddenBit = 0x00800000;
|
||||
static const int kPhysicalSignificandSize = 23; // Excludes the hidden bit.
|
||||
static const int kSignificandSize = 24;
|
||||
|
||||
Single() : d32_(0) {}
|
||||
explicit Single(float f) : d32_(float_to_uint32(f)) {}
|
||||
explicit Single(uint32_t d32) : d32_(d32) {}
|
||||
|
||||
// The value encoded by this Single must be greater or equal to +0.0.
|
||||
// It must not be special (infinity, or NaN).
|
||||
DiyFp AsDiyFp() const {
|
||||
ASSERT(Sign() > 0);
|
||||
ASSERT(!IsSpecial());
|
||||
return DiyFp(Significand(), Exponent());
|
||||
}
|
||||
|
||||
// Returns the single's bit as uint64.
|
||||
uint32_t AsUint32() const {
|
||||
return d32_;
|
||||
}
|
||||
|
||||
int Exponent() const {
|
||||
if (IsDenormal()) return kDenormalExponent;
|
||||
|
||||
uint32_t d32 = AsUint32();
|
||||
int biased_e =
|
||||
static_cast<int>((d32 & kExponentMask) >> kPhysicalSignificandSize);
|
||||
return biased_e - kExponentBias;
|
||||
}
|
||||
|
||||
uint32_t Significand() const {
|
||||
uint32_t d32 = AsUint32();
|
||||
uint32_t significand = d32 & kSignificandMask;
|
||||
if (!IsDenormal()) {
|
||||
return significand + kHiddenBit;
|
||||
} else {
|
||||
return significand;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the single is a denormal.
|
||||
bool IsDenormal() const {
|
||||
uint32_t d32 = AsUint32();
|
||||
return (d32 & kExponentMask) == 0;
|
||||
}
|
||||
|
||||
// We consider denormals not to be special.
|
||||
// Hence only Infinity and NaN are special.
|
||||
bool IsSpecial() const {
|
||||
uint32_t d32 = AsUint32();
|
||||
return (d32 & kExponentMask) == kExponentMask;
|
||||
}
|
||||
|
||||
bool IsNan() const {
|
||||
uint32_t d32 = AsUint32();
|
||||
return ((d32 & kExponentMask) == kExponentMask) &&
|
||||
((d32 & kSignificandMask) != 0);
|
||||
}
|
||||
|
||||
bool IsInfinite() const {
|
||||
uint32_t d32 = AsUint32();
|
||||
return ((d32 & kExponentMask) == kExponentMask) &&
|
||||
((d32 & kSignificandMask) == 0);
|
||||
}
|
||||
|
||||
int Sign() const {
|
||||
uint32_t d32 = AsUint32();
|
||||
return (d32 & kSignMask) == 0? 1: -1;
|
||||
}
|
||||
|
||||
// Computes the two boundaries of this.
|
||||
// The bigger boundary (m_plus) is normalized. The lower boundary has the same
|
||||
// exponent as m_plus.
|
||||
// Precondition: the value encoded by this Single must be greater than 0.
|
||||
void NormalizedBoundaries(DiyFp* out_m_minus, DiyFp* out_m_plus) const {
|
||||
ASSERT(value() > 0.0);
|
||||
DiyFp v = this->AsDiyFp();
|
||||
DiyFp m_plus = DiyFp::Normalize(DiyFp((v.f() << 1) + 1, v.e() - 1));
|
||||
DiyFp m_minus;
|
||||
if (LowerBoundaryIsCloser()) {
|
||||
m_minus = DiyFp((v.f() << 2) - 1, v.e() - 2);
|
||||
} else {
|
||||
m_minus = DiyFp((v.f() << 1) - 1, v.e() - 1);
|
||||
}
|
||||
m_minus.set_f(m_minus.f() << (m_minus.e() - m_plus.e()));
|
||||
m_minus.set_e(m_plus.e());
|
||||
*out_m_plus = m_plus;
|
||||
*out_m_minus = m_minus;
|
||||
}
|
||||
|
||||
// Precondition: the value encoded by this Single must be greater or equal
|
||||
// than +0.0.
|
||||
DiyFp UpperBoundary() const {
|
||||
ASSERT(Sign() > 0);
|
||||
return DiyFp(Significand() * 2 + 1, Exponent() - 1);
|
||||
}
|
||||
|
||||
bool LowerBoundaryIsCloser() const {
|
||||
// The boundary is closer if the significand is of the form f == 2^p-1 then
|
||||
// the lower boundary is closer.
|
||||
// Think of v = 1000e10 and v- = 9999e9.
|
||||
// Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but
|
||||
// at a distance of 1e8.
|
||||
// The only exception is for the smallest normal: the largest denormal is
|
||||
// at the same distance as its successor.
|
||||
// Note: denormals have the same exponent as the smallest normals.
|
||||
bool physical_significand_is_zero = ((AsUint32() & kSignificandMask) == 0);
|
||||
return physical_significand_is_zero && (Exponent() != kDenormalExponent);
|
||||
}
|
||||
|
||||
float value() const { return uint32_to_float(d32_); }
|
||||
|
||||
static float Infinity() {
|
||||
return Single(kInfinity).value();
|
||||
}
|
||||
|
||||
static float NaN() {
|
||||
return Single(kNaN).value();
|
||||
}
|
||||
|
||||
private:
|
||||
static const int kExponentBias = 0x7F + kPhysicalSignificandSize;
|
||||
static const int kDenormalExponent = -kExponentBias + 1;
|
||||
static const int kMaxExponent = 0xFF - kExponentBias;
|
||||
static const uint32_t kInfinity = 0x7F800000;
|
||||
static const uint32_t kNaN = 0x7FC00000;
|
||||
|
||||
const uint32_t d32_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Single);
|
||||
};
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_DOUBLE_H_
|
555
ios/Pods/DoubleConversion/double-conversion/strtod.cc
generated
555
ios/Pods/DoubleConversion/double-conversion/strtod.cc
generated
@ -1,555 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "strtod.h"
|
||||
#include "bignum.h"
|
||||
#include "cached-powers.h"
|
||||
#include "ieee.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
// 2^53 = 9007199254740992.
|
||||
// Any integer with at most 15 decimal digits will hence fit into a double
|
||||
// (which has a 53bit significand) without loss of precision.
|
||||
static const int kMaxExactDoubleIntegerDecimalDigits = 15;
|
||||
// 2^64 = 18446744073709551616 > 10^19
|
||||
static const int kMaxUint64DecimalDigits = 19;
|
||||
|
||||
// Max double: 1.7976931348623157 x 10^308
|
||||
// Min non-zero double: 4.9406564584124654 x 10^-324
|
||||
// Any x >= 10^309 is interpreted as +infinity.
|
||||
// Any x <= 10^-324 is interpreted as 0.
|
||||
// Note that 2.5e-324 (despite being smaller than the min double) will be read
|
||||
// as non-zero (equal to the min non-zero double).
|
||||
static const int kMaxDecimalPower = 309;
|
||||
static const int kMinDecimalPower = -324;
|
||||
|
||||
// 2^64 = 18446744073709551616
|
||||
static const uint64_t kMaxUint64 = UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF);
|
||||
|
||||
|
||||
static const double exact_powers_of_ten[] = {
|
||||
1.0, // 10^0
|
||||
10.0,
|
||||
100.0,
|
||||
1000.0,
|
||||
10000.0,
|
||||
100000.0,
|
||||
1000000.0,
|
||||
10000000.0,
|
||||
100000000.0,
|
||||
1000000000.0,
|
||||
10000000000.0, // 10^10
|
||||
100000000000.0,
|
||||
1000000000000.0,
|
||||
10000000000000.0,
|
||||
100000000000000.0,
|
||||
1000000000000000.0,
|
||||
10000000000000000.0,
|
||||
100000000000000000.0,
|
||||
1000000000000000000.0,
|
||||
10000000000000000000.0,
|
||||
100000000000000000000.0, // 10^20
|
||||
1000000000000000000000.0,
|
||||
// 10^22 = 0x21e19e0c9bab2400000 = 0x878678326eac9 * 2^22
|
||||
10000000000000000000000.0
|
||||
};
|
||||
static const int kExactPowersOfTenSize = ARRAY_SIZE(exact_powers_of_ten);
|
||||
|
||||
// Maximum number of significant digits in the decimal representation.
|
||||
// In fact the value is 772 (see conversions.cc), but to give us some margin
|
||||
// we round up to 780.
|
||||
static const int kMaxSignificantDecimalDigits = 780;
|
||||
|
||||
static Vector<const char> TrimLeadingZeros(Vector<const char> buffer) {
|
||||
for (int i = 0; i < buffer.length(); i++) {
|
||||
if (buffer[i] != '0') {
|
||||
return buffer.SubVector(i, buffer.length());
|
||||
}
|
||||
}
|
||||
return Vector<const char>(buffer.start(), 0);
|
||||
}
|
||||
|
||||
|
||||
static Vector<const char> TrimTrailingZeros(Vector<const char> buffer) {
|
||||
for (int i = buffer.length() - 1; i >= 0; --i) {
|
||||
if (buffer[i] != '0') {
|
||||
return buffer.SubVector(0, i + 1);
|
||||
}
|
||||
}
|
||||
return Vector<const char>(buffer.start(), 0);
|
||||
}
|
||||
|
||||
|
||||
static void CutToMaxSignificantDigits(Vector<const char> buffer,
|
||||
int exponent,
|
||||
char* significant_buffer,
|
||||
int* significant_exponent) {
|
||||
for (int i = 0; i < kMaxSignificantDecimalDigits - 1; ++i) {
|
||||
significant_buffer[i] = buffer[i];
|
||||
}
|
||||
// The input buffer has been trimmed. Therefore the last digit must be
|
||||
// different from '0'.
|
||||
ASSERT(buffer[buffer.length() - 1] != '0');
|
||||
// Set the last digit to be non-zero. This is sufficient to guarantee
|
||||
// correct rounding.
|
||||
significant_buffer[kMaxSignificantDecimalDigits - 1] = '1';
|
||||
*significant_exponent =
|
||||
exponent + (buffer.length() - kMaxSignificantDecimalDigits);
|
||||
}
|
||||
|
||||
|
||||
// Trims the buffer and cuts it to at most kMaxSignificantDecimalDigits.
|
||||
// If possible the input-buffer is reused, but if the buffer needs to be
|
||||
// modified (due to cutting), then the input needs to be copied into the
|
||||
// buffer_copy_space.
|
||||
static void TrimAndCut(Vector<const char> buffer, int exponent,
|
||||
char* buffer_copy_space, int space_size,
|
||||
Vector<const char>* trimmed, int* updated_exponent) {
|
||||
Vector<const char> left_trimmed = TrimLeadingZeros(buffer);
|
||||
Vector<const char> right_trimmed = TrimTrailingZeros(left_trimmed);
|
||||
exponent += left_trimmed.length() - right_trimmed.length();
|
||||
if (right_trimmed.length() > kMaxSignificantDecimalDigits) {
|
||||
(void) space_size; // Mark variable as used.
|
||||
ASSERT(space_size >= kMaxSignificantDecimalDigits);
|
||||
CutToMaxSignificantDigits(right_trimmed, exponent,
|
||||
buffer_copy_space, updated_exponent);
|
||||
*trimmed = Vector<const char>(buffer_copy_space,
|
||||
kMaxSignificantDecimalDigits);
|
||||
} else {
|
||||
*trimmed = right_trimmed;
|
||||
*updated_exponent = exponent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reads digits from the buffer and converts them to a uint64.
|
||||
// Reads in as many digits as fit into a uint64.
|
||||
// When the string starts with "1844674407370955161" no further digit is read.
|
||||
// Since 2^64 = 18446744073709551616 it would still be possible read another
|
||||
// digit if it was less or equal than 6, but this would complicate the code.
|
||||
static uint64_t ReadUint64(Vector<const char> buffer,
|
||||
int* number_of_read_digits) {
|
||||
uint64_t result = 0;
|
||||
int i = 0;
|
||||
while (i < buffer.length() && result <= (kMaxUint64 / 10 - 1)) {
|
||||
int digit = buffer[i++] - '0';
|
||||
ASSERT(0 <= digit && digit <= 9);
|
||||
result = 10 * result + digit;
|
||||
}
|
||||
*number_of_read_digits = i;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Reads a DiyFp from the buffer.
|
||||
// The returned DiyFp is not necessarily normalized.
|
||||
// If remaining_decimals is zero then the returned DiyFp is accurate.
|
||||
// Otherwise it has been rounded and has error of at most 1/2 ulp.
|
||||
static void ReadDiyFp(Vector<const char> buffer,
|
||||
DiyFp* result,
|
||||
int* remaining_decimals) {
|
||||
int read_digits;
|
||||
uint64_t significand = ReadUint64(buffer, &read_digits);
|
||||
if (buffer.length() == read_digits) {
|
||||
*result = DiyFp(significand, 0);
|
||||
*remaining_decimals = 0;
|
||||
} else {
|
||||
// Round the significand.
|
||||
if (buffer[read_digits] >= '5') {
|
||||
significand++;
|
||||
}
|
||||
// Compute the binary exponent.
|
||||
int exponent = 0;
|
||||
*result = DiyFp(significand, exponent);
|
||||
*remaining_decimals = buffer.length() - read_digits;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool DoubleStrtod(Vector<const char> trimmed,
|
||||
int exponent,
|
||||
double* result) {
|
||||
#if !defined(DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS)
|
||||
// On x86 the floating-point stack can be 64 or 80 bits wide. If it is
|
||||
// 80 bits wide (as is the case on Linux) then double-rounding occurs and the
|
||||
// result is not accurate.
|
||||
// We know that Windows32 uses 64 bits and is therefore accurate.
|
||||
// Note that the ARM simulator is compiled for 32bits. It therefore exhibits
|
||||
// the same problem.
|
||||
return false;
|
||||
#endif
|
||||
if (trimmed.length() <= kMaxExactDoubleIntegerDecimalDigits) {
|
||||
int read_digits;
|
||||
// The trimmed input fits into a double.
|
||||
// If the 10^exponent (resp. 10^-exponent) fits into a double too then we
|
||||
// can compute the result-double simply by multiplying (resp. dividing) the
|
||||
// two numbers.
|
||||
// This is possible because IEEE guarantees that floating-point operations
|
||||
// return the best possible approximation.
|
||||
if (exponent < 0 && -exponent < kExactPowersOfTenSize) {
|
||||
// 10^-exponent fits into a double.
|
||||
*result = static_cast<double>(ReadUint64(trimmed, &read_digits));
|
||||
ASSERT(read_digits == trimmed.length());
|
||||
*result /= exact_powers_of_ten[-exponent];
|
||||
return true;
|
||||
}
|
||||
if (0 <= exponent && exponent < kExactPowersOfTenSize) {
|
||||
// 10^exponent fits into a double.
|
||||
*result = static_cast<double>(ReadUint64(trimmed, &read_digits));
|
||||
ASSERT(read_digits == trimmed.length());
|
||||
*result *= exact_powers_of_ten[exponent];
|
||||
return true;
|
||||
}
|
||||
int remaining_digits =
|
||||
kMaxExactDoubleIntegerDecimalDigits - trimmed.length();
|
||||
if ((0 <= exponent) &&
|
||||
(exponent - remaining_digits < kExactPowersOfTenSize)) {
|
||||
// The trimmed string was short and we can multiply it with
|
||||
// 10^remaining_digits. As a result the remaining exponent now fits
|
||||
// into a double too.
|
||||
*result = static_cast<double>(ReadUint64(trimmed, &read_digits));
|
||||
ASSERT(read_digits == trimmed.length());
|
||||
*result *= exact_powers_of_ten[remaining_digits];
|
||||
*result *= exact_powers_of_ten[exponent - remaining_digits];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Returns 10^exponent as an exact DiyFp.
|
||||
// The given exponent must be in the range [1; kDecimalExponentDistance[.
|
||||
static DiyFp AdjustmentPowerOfTen(int exponent) {
|
||||
ASSERT(0 < exponent);
|
||||
ASSERT(exponent < PowersOfTenCache::kDecimalExponentDistance);
|
||||
// Simply hardcode the remaining powers for the given decimal exponent
|
||||
// distance.
|
||||
ASSERT(PowersOfTenCache::kDecimalExponentDistance == 8);
|
||||
switch (exponent) {
|
||||
case 1: return DiyFp(UINT64_2PART_C(0xa0000000, 00000000), -60);
|
||||
case 2: return DiyFp(UINT64_2PART_C(0xc8000000, 00000000), -57);
|
||||
case 3: return DiyFp(UINT64_2PART_C(0xfa000000, 00000000), -54);
|
||||
case 4: return DiyFp(UINT64_2PART_C(0x9c400000, 00000000), -50);
|
||||
case 5: return DiyFp(UINT64_2PART_C(0xc3500000, 00000000), -47);
|
||||
case 6: return DiyFp(UINT64_2PART_C(0xf4240000, 00000000), -44);
|
||||
case 7: return DiyFp(UINT64_2PART_C(0x98968000, 00000000), -40);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If the function returns true then the result is the correct double.
|
||||
// Otherwise it is either the correct double or the double that is just below
|
||||
// the correct double.
|
||||
static bool DiyFpStrtod(Vector<const char> buffer,
|
||||
int exponent,
|
||||
double* result) {
|
||||
DiyFp input;
|
||||
int remaining_decimals;
|
||||
ReadDiyFp(buffer, &input, &remaining_decimals);
|
||||
// Since we may have dropped some digits the input is not accurate.
|
||||
// If remaining_decimals is different than 0 than the error is at most
|
||||
// .5 ulp (unit in the last place).
|
||||
// We don't want to deal with fractions and therefore keep a common
|
||||
// denominator.
|
||||
const int kDenominatorLog = 3;
|
||||
const int kDenominator = 1 << kDenominatorLog;
|
||||
// Move the remaining decimals into the exponent.
|
||||
exponent += remaining_decimals;
|
||||
uint64_t error = (remaining_decimals == 0 ? 0 : kDenominator / 2);
|
||||
|
||||
int old_e = input.e();
|
||||
input.Normalize();
|
||||
error <<= old_e - input.e();
|
||||
|
||||
ASSERT(exponent <= PowersOfTenCache::kMaxDecimalExponent);
|
||||
if (exponent < PowersOfTenCache::kMinDecimalExponent) {
|
||||
*result = 0.0;
|
||||
return true;
|
||||
}
|
||||
DiyFp cached_power;
|
||||
int cached_decimal_exponent;
|
||||
PowersOfTenCache::GetCachedPowerForDecimalExponent(exponent,
|
||||
&cached_power,
|
||||
&cached_decimal_exponent);
|
||||
|
||||
if (cached_decimal_exponent != exponent) {
|
||||
int adjustment_exponent = exponent - cached_decimal_exponent;
|
||||
DiyFp adjustment_power = AdjustmentPowerOfTen(adjustment_exponent);
|
||||
input.Multiply(adjustment_power);
|
||||
if (kMaxUint64DecimalDigits - buffer.length() >= adjustment_exponent) {
|
||||
// The product of input with the adjustment power fits into a 64 bit
|
||||
// integer.
|
||||
ASSERT(DiyFp::kSignificandSize == 64);
|
||||
} else {
|
||||
// The adjustment power is exact. There is hence only an error of 0.5.
|
||||
error += kDenominator / 2;
|
||||
}
|
||||
}
|
||||
|
||||
input.Multiply(cached_power);
|
||||
// The error introduced by a multiplication of a*b equals
|
||||
// error_a + error_b + error_a*error_b/2^64 + 0.5
|
||||
// Substituting a with 'input' and b with 'cached_power' we have
|
||||
// error_b = 0.5 (all cached powers have an error of less than 0.5 ulp),
|
||||
// error_ab = 0 or 1 / kDenominator > error_a*error_b/ 2^64
|
||||
int error_b = kDenominator / 2;
|
||||
int error_ab = (error == 0 ? 0 : 1); // We round up to 1.
|
||||
int fixed_error = kDenominator / 2;
|
||||
error += error_b + error_ab + fixed_error;
|
||||
|
||||
old_e = input.e();
|
||||
input.Normalize();
|
||||
error <<= old_e - input.e();
|
||||
|
||||
// See if the double's significand changes if we add/subtract the error.
|
||||
int order_of_magnitude = DiyFp::kSignificandSize + input.e();
|
||||
int effective_significand_size =
|
||||
Double::SignificandSizeForOrderOfMagnitude(order_of_magnitude);
|
||||
int precision_digits_count =
|
||||
DiyFp::kSignificandSize - effective_significand_size;
|
||||
if (precision_digits_count + kDenominatorLog >= DiyFp::kSignificandSize) {
|
||||
// This can only happen for very small denormals. In this case the
|
||||
// half-way multiplied by the denominator exceeds the range of an uint64.
|
||||
// Simply shift everything to the right.
|
||||
int shift_amount = (precision_digits_count + kDenominatorLog) -
|
||||
DiyFp::kSignificandSize + 1;
|
||||
input.set_f(input.f() >> shift_amount);
|
||||
input.set_e(input.e() + shift_amount);
|
||||
// We add 1 for the lost precision of error, and kDenominator for
|
||||
// the lost precision of input.f().
|
||||
error = (error >> shift_amount) + 1 + kDenominator;
|
||||
precision_digits_count -= shift_amount;
|
||||
}
|
||||
// We use uint64_ts now. This only works if the DiyFp uses uint64_ts too.
|
||||
ASSERT(DiyFp::kSignificandSize == 64);
|
||||
ASSERT(precision_digits_count < 64);
|
||||
uint64_t one64 = 1;
|
||||
uint64_t precision_bits_mask = (one64 << precision_digits_count) - 1;
|
||||
uint64_t precision_bits = input.f() & precision_bits_mask;
|
||||
uint64_t half_way = one64 << (precision_digits_count - 1);
|
||||
precision_bits *= kDenominator;
|
||||
half_way *= kDenominator;
|
||||
DiyFp rounded_input(input.f() >> precision_digits_count,
|
||||
input.e() + precision_digits_count);
|
||||
if (precision_bits >= half_way + error) {
|
||||
rounded_input.set_f(rounded_input.f() + 1);
|
||||
}
|
||||
// If the last_bits are too close to the half-way case than we are too
|
||||
// inaccurate and round down. In this case we return false so that we can
|
||||
// fall back to a more precise algorithm.
|
||||
|
||||
*result = Double(rounded_input).value();
|
||||
if (half_way - error < precision_bits && precision_bits < half_way + error) {
|
||||
// Too imprecise. The caller will have to fall back to a slower version.
|
||||
// However the returned number is guaranteed to be either the correct
|
||||
// double, or the next-lower double.
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns
|
||||
// - -1 if buffer*10^exponent < diy_fp.
|
||||
// - 0 if buffer*10^exponent == diy_fp.
|
||||
// - +1 if buffer*10^exponent > diy_fp.
|
||||
// Preconditions:
|
||||
// buffer.length() + exponent <= kMaxDecimalPower + 1
|
||||
// buffer.length() + exponent > kMinDecimalPower
|
||||
// buffer.length() <= kMaxDecimalSignificantDigits
|
||||
static int CompareBufferWithDiyFp(Vector<const char> buffer,
|
||||
int exponent,
|
||||
DiyFp diy_fp) {
|
||||
ASSERT(buffer.length() + exponent <= kMaxDecimalPower + 1);
|
||||
ASSERT(buffer.length() + exponent > kMinDecimalPower);
|
||||
ASSERT(buffer.length() <= kMaxSignificantDecimalDigits);
|
||||
// Make sure that the Bignum will be able to hold all our numbers.
|
||||
// Our Bignum implementation has a separate field for exponents. Shifts will
|
||||
// consume at most one bigit (< 64 bits).
|
||||
// ln(10) == 3.3219...
|
||||
ASSERT(((kMaxDecimalPower + 1) * 333 / 100) < Bignum::kMaxSignificantBits);
|
||||
Bignum buffer_bignum;
|
||||
Bignum diy_fp_bignum;
|
||||
buffer_bignum.AssignDecimalString(buffer);
|
||||
diy_fp_bignum.AssignUInt64(diy_fp.f());
|
||||
if (exponent >= 0) {
|
||||
buffer_bignum.MultiplyByPowerOfTen(exponent);
|
||||
} else {
|
||||
diy_fp_bignum.MultiplyByPowerOfTen(-exponent);
|
||||
}
|
||||
if (diy_fp.e() > 0) {
|
||||
diy_fp_bignum.ShiftLeft(diy_fp.e());
|
||||
} else {
|
||||
buffer_bignum.ShiftLeft(-diy_fp.e());
|
||||
}
|
||||
return Bignum::Compare(buffer_bignum, diy_fp_bignum);
|
||||
}
|
||||
|
||||
|
||||
// Returns true if the guess is the correct double.
|
||||
// Returns false, when guess is either correct or the next-lower double.
|
||||
static bool ComputeGuess(Vector<const char> trimmed, int exponent,
|
||||
double* guess) {
|
||||
if (trimmed.length() == 0) {
|
||||
*guess = 0.0;
|
||||
return true;
|
||||
}
|
||||
if (exponent + trimmed.length() - 1 >= kMaxDecimalPower) {
|
||||
*guess = Double::Infinity();
|
||||
return true;
|
||||
}
|
||||
if (exponent + trimmed.length() <= kMinDecimalPower) {
|
||||
*guess = 0.0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DoubleStrtod(trimmed, exponent, guess) ||
|
||||
DiyFpStrtod(trimmed, exponent, guess)) {
|
||||
return true;
|
||||
}
|
||||
if (*guess == Double::Infinity()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
double Strtod(Vector<const char> buffer, int exponent) {
|
||||
char copy_buffer[kMaxSignificantDecimalDigits];
|
||||
Vector<const char> trimmed;
|
||||
int updated_exponent;
|
||||
TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits,
|
||||
&trimmed, &updated_exponent);
|
||||
exponent = updated_exponent;
|
||||
|
||||
double guess;
|
||||
bool is_correct = ComputeGuess(trimmed, exponent, &guess);
|
||||
if (is_correct) return guess;
|
||||
|
||||
DiyFp upper_boundary = Double(guess).UpperBoundary();
|
||||
int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary);
|
||||
if (comparison < 0) {
|
||||
return guess;
|
||||
} else if (comparison > 0) {
|
||||
return Double(guess).NextDouble();
|
||||
} else if ((Double(guess).Significand() & 1) == 0) {
|
||||
// Round towards even.
|
||||
return guess;
|
||||
} else {
|
||||
return Double(guess).NextDouble();
|
||||
}
|
||||
}
|
||||
|
||||
float Strtof(Vector<const char> buffer, int exponent) {
|
||||
char copy_buffer[kMaxSignificantDecimalDigits];
|
||||
Vector<const char> trimmed;
|
||||
int updated_exponent;
|
||||
TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits,
|
||||
&trimmed, &updated_exponent);
|
||||
exponent = updated_exponent;
|
||||
|
||||
double double_guess;
|
||||
bool is_correct = ComputeGuess(trimmed, exponent, &double_guess);
|
||||
|
||||
float float_guess = static_cast<float>(double_guess);
|
||||
if (float_guess == double_guess) {
|
||||
// This shortcut triggers for integer values.
|
||||
return float_guess;
|
||||
}
|
||||
|
||||
// We must catch double-rounding. Say the double has been rounded up, and is
|
||||
// now a boundary of a float, and rounds up again. This is why we have to
|
||||
// look at previous too.
|
||||
// Example (in decimal numbers):
|
||||
// input: 12349
|
||||
// high-precision (4 digits): 1235
|
||||
// low-precision (3 digits):
|
||||
// when read from input: 123
|
||||
// when rounded from high precision: 124.
|
||||
// To do this we simply look at the neigbors of the correct result and see
|
||||
// if they would round to the same float. If the guess is not correct we have
|
||||
// to look at four values (since two different doubles could be the correct
|
||||
// double).
|
||||
|
||||
double double_next = Double(double_guess).NextDouble();
|
||||
double double_previous = Double(double_guess).PreviousDouble();
|
||||
|
||||
float f1 = static_cast<float>(double_previous);
|
||||
float f2 = float_guess;
|
||||
float f3 = static_cast<float>(double_next);
|
||||
float f4;
|
||||
if (is_correct) {
|
||||
f4 = f3;
|
||||
} else {
|
||||
double double_next2 = Double(double_next).NextDouble();
|
||||
f4 = static_cast<float>(double_next2);
|
||||
}
|
||||
(void) f2; // Mark variable as used.
|
||||
ASSERT(f1 <= f2 && f2 <= f3 && f3 <= f4);
|
||||
|
||||
// If the guess doesn't lie near a single-precision boundary we can simply
|
||||
// return its float-value.
|
||||
if (f1 == f4) {
|
||||
return float_guess;
|
||||
}
|
||||
|
||||
ASSERT((f1 != f2 && f2 == f3 && f3 == f4) ||
|
||||
(f1 == f2 && f2 != f3 && f3 == f4) ||
|
||||
(f1 == f2 && f2 == f3 && f3 != f4));
|
||||
|
||||
// guess and next are the two possible canditates (in the same way that
|
||||
// double_guess was the lower candidate for a double-precision guess).
|
||||
float guess = f1;
|
||||
float next = f4;
|
||||
DiyFp upper_boundary;
|
||||
if (guess == 0.0f) {
|
||||
float min_float = 1e-45f;
|
||||
upper_boundary = Double(static_cast<double>(min_float) / 2).AsDiyFp();
|
||||
} else {
|
||||
upper_boundary = Single(guess).UpperBoundary();
|
||||
}
|
||||
int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary);
|
||||
if (comparison < 0) {
|
||||
return guess;
|
||||
} else if (comparison > 0) {
|
||||
return next;
|
||||
} else if ((Single(guess).Significand() & 1) == 0) {
|
||||
// Round towards even.
|
||||
return guess;
|
||||
} else {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace double_conversion
|
45
ios/Pods/DoubleConversion/double-conversion/strtod.h
generated
45
ios/Pods/DoubleConversion/double-conversion/strtod.h
generated
@ -1,45 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_STRTOD_H_
|
||||
#define DOUBLE_CONVERSION_STRTOD_H_
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
// The buffer must only contain digits in the range [0-9]. It must not
|
||||
// contain a dot or a sign. It must not start with '0', and must not be empty.
|
||||
double Strtod(Vector<const char> buffer, int exponent);
|
||||
|
||||
// The buffer must only contain digits in the range [0-9]. It must not
|
||||
// contain a dot or a sign. It must not start with '0', and must not be empty.
|
||||
float Strtof(Vector<const char> buffer, int exponent);
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_STRTOD_H_
|
324
ios/Pods/DoubleConversion/double-conversion/utils.h
generated
324
ios/Pods/DoubleConversion/double-conversion/utils.h
generated
@ -1,324 +0,0 @@
|
||||
// Copyright 2010 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef DOUBLE_CONVERSION_UTILS_H_
|
||||
#define DOUBLE_CONVERSION_UTILS_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <assert.h>
|
||||
#ifndef ASSERT
|
||||
#define ASSERT(condition) \
|
||||
assert(condition);
|
||||
#endif
|
||||
#ifndef UNIMPLEMENTED
|
||||
#define UNIMPLEMENTED() (abort())
|
||||
#endif
|
||||
#ifndef UNREACHABLE
|
||||
#define UNREACHABLE() (abort())
|
||||
#endif
|
||||
|
||||
// Double operations detection based on target architecture.
|
||||
// Linux uses a 80bit wide floating point stack on x86. This induces double
|
||||
// rounding, which in turn leads to wrong results.
|
||||
// An easy way to test if the floating-point operations are correct is to
|
||||
// evaluate: 89255.0/1e22. If the floating-point stack is 64 bits wide then
|
||||
// the result is equal to 89255e-22.
|
||||
// The best way to test this, is to create a division-function and to compare
|
||||
// the output of the division with the expected result. (Inlining must be
|
||||
// disabled.)
|
||||
// On Linux,x86 89255e-22 != Div_double(89255.0/1e22)
|
||||
#if defined(_M_X64) || defined(__x86_64__) || \
|
||||
defined(__ARMEL__) || defined(__avr32__) || \
|
||||
defined(__hppa__) || defined(__ia64__) || \
|
||||
defined(__mips__) || \
|
||||
defined(__powerpc__) || defined(__ppc__) || defined(__ppc64__) || \
|
||||
defined(__sparc__) || defined(__sparc) || defined(__s390__) || \
|
||||
defined(__SH4__) || defined(__alpha__) || \
|
||||
defined(_MIPS_ARCH_MIPS32R2) || \
|
||||
defined(__AARCH64EL__)
|
||||
#define DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS 1
|
||||
#elif defined(__mc68000__)
|
||||
#undef DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS
|
||||
#elif defined(_M_IX86) || defined(__i386__) || defined(__i386)
|
||||
#if defined(_WIN32)
|
||||
// Windows uses a 64bit wide floating point stack.
|
||||
#define DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS 1
|
||||
#else
|
||||
#undef DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS
|
||||
#endif // _WIN32
|
||||
#else
|
||||
#error Target architecture was not detected as supported by Double-Conversion.
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define DOUBLE_CONVERSION_UNUSED __attribute__((unused))
|
||||
#else
|
||||
#define DOUBLE_CONVERSION_UNUSED
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef short int16_t; // NOLINT
|
||||
typedef unsigned short uint16_t; // NOLINT
|
||||
typedef int int32_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
// intptr_t and friends are defined in crtdefs.h through stdio.h.
|
||||
|
||||
#else
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#endif
|
||||
|
||||
// The following macro works on both 32 and 64-bit platforms.
|
||||
// Usage: instead of writing 0x1234567890123456
|
||||
// write UINT64_2PART_C(0x12345678,90123456);
|
||||
#define UINT64_2PART_C(a, b) (((static_cast<uint64_t>(a) << 32) + 0x##b##u))
|
||||
|
||||
|
||||
// The expression ARRAY_SIZE(a) is a compile-time constant of type
|
||||
// size_t which represents the number of elements of the given
|
||||
// array. You should only use ARRAY_SIZE on statically allocated
|
||||
// arrays.
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(a) \
|
||||
((sizeof(a) / sizeof(*(a))) / \
|
||||
static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
|
||||
#endif
|
||||
|
||||
// A macro to disallow the evil copy constructor and operator= functions
|
||||
// This should be used in the private: declarations for a class
|
||||
#ifndef DISALLOW_COPY_AND_ASSIGN
|
||||
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
#endif
|
||||
|
||||
// A macro to disallow all the implicit constructors, namely the
|
||||
// default constructor, copy constructor and operator= functions.
|
||||
//
|
||||
// This should be used in the private: declarations for a class
|
||||
// that wants to prevent anyone from instantiating it. This is
|
||||
// especially useful for classes containing only static methods.
|
||||
#ifndef DISALLOW_IMPLICIT_CONSTRUCTORS
|
||||
#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
|
||||
TypeName(); \
|
||||
DISALLOW_COPY_AND_ASSIGN(TypeName)
|
||||
#endif
|
||||
|
||||
namespace double_conversion {
|
||||
|
||||
static const int kCharSize = sizeof(char);
|
||||
|
||||
// Returns the maximum of the two parameters.
|
||||
template <typename T>
|
||||
static T Max(T a, T b) {
|
||||
return a < b ? b : a;
|
||||
}
|
||||
|
||||
|
||||
// Returns the minimum of the two parameters.
|
||||
template <typename T>
|
||||
static T Min(T a, T b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
|
||||
inline int StrLength(const char* string) {
|
||||
size_t length = strlen(string);
|
||||
ASSERT(length == static_cast<size_t>(static_cast<int>(length)));
|
||||
return static_cast<int>(length);
|
||||
}
|
||||
|
||||
// This is a simplified version of V8's Vector class.
|
||||
template <typename T>
|
||||
class Vector {
|
||||
public:
|
||||
Vector() : start_(NULL), length_(0) {}
|
||||
Vector(T* data, int length) : start_(data), length_(length) {
|
||||
ASSERT(length == 0 || (length > 0 && data != NULL));
|
||||
}
|
||||
|
||||
// Returns a vector using the same backing storage as this one,
|
||||
// spanning from and including 'from', to but not including 'to'.
|
||||
Vector<T> SubVector(int from, int to) {
|
||||
ASSERT(to <= length_);
|
||||
ASSERT(from < to);
|
||||
ASSERT(0 <= from);
|
||||
return Vector<T>(start() + from, to - from);
|
||||
}
|
||||
|
||||
// Returns the length of the vector.
|
||||
int length() const { return length_; }
|
||||
|
||||
// Returns whether or not the vector is empty.
|
||||
bool is_empty() const { return length_ == 0; }
|
||||
|
||||
// Returns the pointer to the start of the data in the vector.
|
||||
T* start() const { return start_; }
|
||||
|
||||
// Access individual vector elements - checks bounds in debug mode.
|
||||
T& operator[](int index) const {
|
||||
ASSERT(0 <= index && index < length_);
|
||||
return start_[index];
|
||||
}
|
||||
|
||||
T& first() { return start_[0]; }
|
||||
|
||||
T& last() { return start_[length_ - 1]; }
|
||||
|
||||
private:
|
||||
T* start_;
|
||||
int length_;
|
||||
};
|
||||
|
||||
|
||||
// Helper class for building result strings in a character buffer. The
|
||||
// purpose of the class is to use safe operations that checks the
|
||||
// buffer bounds on all operations in debug mode.
|
||||
class StringBuilder {
|
||||
public:
|
||||
StringBuilder(char* buffer, int size)
|
||||
: buffer_(buffer, size), position_(0) { }
|
||||
|
||||
~StringBuilder() { if (!is_finalized()) Finalize(); }
|
||||
|
||||
int size() const { return buffer_.length(); }
|
||||
|
||||
// Get the current position in the builder.
|
||||
int position() const {
|
||||
ASSERT(!is_finalized());
|
||||
return position_;
|
||||
}
|
||||
|
||||
// Reset the position.
|
||||
void Reset() { position_ = 0; }
|
||||
|
||||
// Add a single character to the builder. It is not allowed to add
|
||||
// 0-characters; use the Finalize() method to terminate the string
|
||||
// instead.
|
||||
void AddCharacter(char c) {
|
||||
ASSERT(c != '\0');
|
||||
ASSERT(!is_finalized() && position_ < buffer_.length());
|
||||
buffer_[position_++] = c;
|
||||
}
|
||||
|
||||
// Add an entire string to the builder. Uses strlen() internally to
|
||||
// compute the length of the input string.
|
||||
void AddString(const char* s) {
|
||||
AddSubstring(s, StrLength(s));
|
||||
}
|
||||
|
||||
// Add the first 'n' characters of the given string 's' to the
|
||||
// builder. The input string must have enough characters.
|
||||
void AddSubstring(const char* s, int n) {
|
||||
ASSERT(!is_finalized() && position_ + n < buffer_.length());
|
||||
ASSERT(static_cast<size_t>(n) <= strlen(s));
|
||||
memmove(&buffer_[position_], s, n * kCharSize);
|
||||
position_ += n;
|
||||
}
|
||||
|
||||
|
||||
// Add character padding to the builder. If count is non-positive,
|
||||
// nothing is added to the builder.
|
||||
void AddPadding(char c, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
AddCharacter(c);
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize the string by 0-terminating it and returning the buffer.
|
||||
char* Finalize() {
|
||||
ASSERT(!is_finalized() && position_ < buffer_.length());
|
||||
buffer_[position_] = '\0';
|
||||
// Make sure nobody managed to add a 0-character to the
|
||||
// buffer while building the string.
|
||||
ASSERT(strlen(buffer_.start()) == static_cast<size_t>(position_));
|
||||
position_ = -1;
|
||||
ASSERT(is_finalized());
|
||||
return buffer_.start();
|
||||
}
|
||||
|
||||
private:
|
||||
Vector<char> buffer_;
|
||||
int position_;
|
||||
|
||||
bool is_finalized() const { return position_ < 0; }
|
||||
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(StringBuilder);
|
||||
};
|
||||
|
||||
// The type-based aliasing rule allows the compiler to assume that pointers of
|
||||
// different types (for some definition of different) never alias each other.
|
||||
// Thus the following code does not work:
|
||||
//
|
||||
// float f = foo();
|
||||
// int fbits = *(int*)(&f);
|
||||
//
|
||||
// The compiler 'knows' that the int pointer can't refer to f since the types
|
||||
// don't match, so the compiler may cache f in a register, leaving random data
|
||||
// in fbits. Using C++ style casts makes no difference, however a pointer to
|
||||
// char data is assumed to alias any other pointer. This is the 'memcpy
|
||||
// exception'.
|
||||
//
|
||||
// Bit_cast uses the memcpy exception to move the bits from a variable of one
|
||||
// type of a variable of another type. Of course the end result is likely to
|
||||
// be implementation dependent. Most compilers (gcc-4.2 and MSVC 2005)
|
||||
// will completely optimize BitCast away.
|
||||
//
|
||||
// There is an additional use for BitCast.
|
||||
// Recent gccs will warn when they see casts that may result in breakage due to
|
||||
// the type-based aliasing rule. If you have checked that there is no breakage
|
||||
// you can use BitCast to cast one pointer type to another. This confuses gcc
|
||||
// enough that it can no longer see that you have cast one pointer type to
|
||||
// another thus avoiding the warning.
|
||||
template <class Dest, class Source>
|
||||
inline Dest BitCast(const Source& source) {
|
||||
// Compile time assertion: sizeof(Dest) == sizeof(Source)
|
||||
// A compile error here means your Dest and Source have different sizes.
|
||||
DOUBLE_CONVERSION_UNUSED
|
||||
typedef char VerifySizesAreEqual[sizeof(Dest) == sizeof(Source) ? 1 : -1];
|
||||
|
||||
Dest dest;
|
||||
memmove(&dest, &source, sizeof(dest));
|
||||
return dest;
|
||||
}
|
||||
|
||||
template <class Dest, class Source>
|
||||
inline Dest BitCast(Source* source) {
|
||||
return BitCast<Dest>(reinterpret_cast<uintptr_t>(source));
|
||||
}
|
||||
|
||||
} // namespace double_conversion
|
||||
|
||||
#endif // DOUBLE_CONVERSION_UTILS_H_
|
@ -1,84 +0,0 @@
|
||||
//
|
||||
// FLAnimatedImage.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Raphael Schaad on 7/8/13.
|
||||
// Copyright (c) 2013-2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
// Allow user classes conveniently just importing one header.
|
||||
#import "FLAnimatedImageView.h"
|
||||
|
||||
|
||||
#ifndef NS_DESIGNATED_INITIALIZER
|
||||
#if __has_attribute(objc_designated_initializer)
|
||||
#define NS_DESIGNATED_INITIALIZER __attribute((objc_designated_initializer))
|
||||
#else
|
||||
#define NS_DESIGNATED_INITIALIZER
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern const NSTimeInterval kFLAnimatedImageDelayTimeIntervalMinimum;
|
||||
|
||||
//
|
||||
// An `FLAnimatedImage`'s job is to deliver frames in a highly performant way and works in conjunction with `FLAnimatedImageView`.
|
||||
// It subclasses `NSObject` and not `UIImage` because it's only an "image" in the sense that a sea lion is a lion.
|
||||
// It tries to intelligently choose the frame cache size depending on the image and memory situation with the goal to lower CPU usage for smaller ones, lower memory usage for larger ones and always deliver frames for high performant play-back.
|
||||
// Note: `posterImage`, `size`, `loopCount`, `delayTimes` and `frameCount` don't change after successful initialization.
|
||||
//
|
||||
@interface FLAnimatedImage : NSObject
|
||||
|
||||
@property (nonatomic, strong, readonly) UIImage *posterImage; // Guaranteed to be loaded; usually equivalent to `-imageLazilyCachedAtIndex:0`
|
||||
@property (nonatomic, assign, readonly) CGSize size; // The `.posterImage`'s `.size`
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger loopCount; // 0 means repeating the animation indefinitely
|
||||
@property (nonatomic, strong, readonly) NSDictionary *delayTimesForIndexes; // Of type `NSTimeInterval` boxed in `NSNumber`s
|
||||
@property (nonatomic, assign, readonly) NSUInteger frameCount; // Number of valid frames; equal to `[.delayTimes count]`
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger frameCacheSizeCurrent; // Current size of intelligently chosen buffer window; can range in the interval [1..frameCount]
|
||||
@property (nonatomic, assign) NSUInteger frameCacheSizeMax; // Allow to cap the cache size; 0 means no specific limit (default)
|
||||
|
||||
// Intended to be called from main thread synchronously; will return immediately.
|
||||
// If the result isn't cached, will return `nil`; the caller should then pause playback, not increment frame counter and keep polling.
|
||||
// After an initial loading time, depending on `frameCacheSize`, frames should be available immediately from the cache.
|
||||
- (UIImage *)imageLazilyCachedAtIndex:(NSUInteger)index;
|
||||
|
||||
// Pass either a `UIImage` or an `FLAnimatedImage` and get back its size
|
||||
+ (CGSize)sizeForImage:(id)image;
|
||||
|
||||
// On success, the initializers return an `FLAnimatedImage` with all fields initialized, on failure they return `nil` and an error will be logged.
|
||||
- (instancetype)initWithAnimatedGIFData:(NSData *)data;
|
||||
// Pass 0 for optimalFrameCacheSize to get the default, predrawing is enabled by default.
|
||||
- (instancetype)initWithAnimatedGIFData:(NSData *)data optimalFrameCacheSize:(NSUInteger)optimalFrameCacheSize predrawingEnabled:(BOOL)isPredrawingEnabled NS_DESIGNATED_INITIALIZER;
|
||||
+ (instancetype)animatedImageWithGIFData:(NSData *)data;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSData *data; // The data the receiver was initialized with; read-only
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLLogLevel) {
|
||||
FLLogLevelNone = 0,
|
||||
FLLogLevelError,
|
||||
FLLogLevelWarn,
|
||||
FLLogLevelInfo,
|
||||
FLLogLevelDebug,
|
||||
FLLogLevelVerbose
|
||||
};
|
||||
|
||||
@interface FLAnimatedImage (Logging)
|
||||
|
||||
+ (void)setLogBlock:(void (^)(NSString *logString, FLLogLevel logLevel))logBlock logLevel:(FLLogLevel)logLevel;
|
||||
+ (void)logStringFromBlock:(NSString *(^)(void))stringBlock withLevel:(FLLogLevel)level;
|
||||
|
||||
@end
|
||||
|
||||
#define FLLog(logLevel, format, ...) [FLAnimatedImage logStringFromBlock:^NSString *{ return [NSString stringWithFormat:(format), ## __VA_ARGS__]; } withLevel:(logLevel)]
|
||||
|
||||
@interface FLWeakProxy : NSProxy
|
||||
|
||||
+ (instancetype)weakProxyForObject:(id)targetObject;
|
||||
|
||||
@end
|
@ -1,816 +0,0 @@
|
||||
//
|
||||
// FLAnimatedImage.m
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Raphael Schaad on 7/8/13.
|
||||
// Copyright (c) 2013-2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "FLAnimatedImage.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
|
||||
// From vm_param.h, define for iOS 8.0 or higher to build on device.
|
||||
#ifndef BYTE_SIZE
|
||||
#define BYTE_SIZE 8 // byte size in bits
|
||||
#endif
|
||||
|
||||
#define MEGABYTE (1024 * 1024)
|
||||
|
||||
// This is how the fastest browsers do it as per 2012: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
|
||||
const NSTimeInterval kFLAnimatedImageDelayTimeIntervalMinimum = 0.02;
|
||||
|
||||
// An animated image's data size (dimensions * frameCount) category; its value is the max allowed memory (in MB).
|
||||
// E.g.: A 100x200px GIF with 30 frames is ~2.3MB in our pixel format and would fall into the `FLAnimatedImageDataSizeCategoryAll` category.
|
||||
typedef NS_ENUM(NSUInteger, FLAnimatedImageDataSizeCategory) {
|
||||
FLAnimatedImageDataSizeCategoryAll = 10, // All frames permanently in memory (be nice to the CPU)
|
||||
FLAnimatedImageDataSizeCategoryDefault = 75, // A frame cache of default size in memory (usually real-time performance and keeping low memory profile)
|
||||
FLAnimatedImageDataSizeCategoryOnDemand = 250, // Only keep one frame at the time in memory (easier on memory, slowest performance)
|
||||
FLAnimatedImageDataSizeCategoryUnsupported // Even for one frame too large, computer says no.
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLAnimatedImageFrameCacheSize) {
|
||||
FLAnimatedImageFrameCacheSizeNoLimit = 0, // 0 means no specific limit
|
||||
FLAnimatedImageFrameCacheSizeLowMemory = 1, // The minimum frame cache size; this will produce frames on-demand.
|
||||
FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning = 2, // If we can produce the frames faster than we consume, one frame ahead will already result in a stutter-free playback.
|
||||
FLAnimatedImageFrameCacheSizeDefault = 5 // Build up a comfy buffer window to cope with CPU hiccups etc.
|
||||
};
|
||||
|
||||
|
||||
#if defined(DEBUG) && DEBUG
|
||||
@protocol FLAnimatedImageDebugDelegate <NSObject>
|
||||
@optional
|
||||
- (void)debug_animatedImage:(FLAnimatedImage *)animatedImage didUpdateCachedFrames:(NSIndexSet *)indexesOfFramesInCache;
|
||||
- (void)debug_animatedImage:(FLAnimatedImage *)animatedImage didRequestCachedFrame:(NSUInteger)index;
|
||||
- (CGFloat)debug_animatedImagePredrawingSlowdownFactor:(FLAnimatedImage *)animatedImage;
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
||||
@interface FLAnimatedImage ()
|
||||
|
||||
@property (nonatomic, assign, readonly) NSUInteger frameCacheSizeOptimal; // The optimal number of frames to cache based on image size & number of frames; never changes
|
||||
@property (nonatomic, assign, readonly, getter=isPredrawingEnabled) BOOL predrawingEnabled; // Enables predrawing of images to improve performance.
|
||||
@property (nonatomic, assign) NSUInteger frameCacheSizeMaxInternal; // Allow to cap the cache size e.g. when memory warnings occur; 0 means no specific limit (default)
|
||||
@property (nonatomic, assign) NSUInteger requestedFrameIndex; // Most recently requested frame index
|
||||
@property (nonatomic, assign, readonly) NSUInteger posterImageFrameIndex; // Index of non-purgable poster image; never changes
|
||||
@property (nonatomic, strong, readonly) NSMutableDictionary *cachedFramesForIndexes;
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *cachedFrameIndexes; // Indexes of cached frames
|
||||
@property (nonatomic, strong, readonly) NSMutableIndexSet *requestedFrameIndexes; // Indexes of frames that are currently produced in the background
|
||||
@property (nonatomic, strong, readonly) NSIndexSet *allFramesIndexSet; // Default index set with the full range of indexes; never changes
|
||||
@property (nonatomic, assign) NSUInteger memoryWarningCount;
|
||||
@property (nonatomic, strong, readonly) dispatch_queue_t serialQueue;
|
||||
@property (nonatomic, strong, readonly) __attribute__((NSObject)) CGImageSourceRef imageSource;
|
||||
|
||||
// The weak proxy is used to break retain cycles with delayed actions from memory warnings.
|
||||
// We are lying about the actual type here to gain static type checking and eliminate casts.
|
||||
// The actual type of the object is `FLWeakProxy`.
|
||||
@property (nonatomic, strong, readonly) FLAnimatedImage *weakProxy;
|
||||
|
||||
#if defined(DEBUG) && DEBUG
|
||||
@property (nonatomic, weak) id<FLAnimatedImageDebugDelegate> debug_delegate;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// For custom dispatching of memory warnings to avoid deallocation races since NSNotificationCenter doesn't retain objects it is notifying.
|
||||
static NSHashTable *allAnimatedImagesWeak;
|
||||
|
||||
@implementation FLAnimatedImage
|
||||
|
||||
#pragma mark - Accessors
|
||||
#pragma mark Public
|
||||
|
||||
// This is the definite value the frame cache needs to size itself to.
|
||||
- (NSUInteger)frameCacheSizeCurrent
|
||||
{
|
||||
NSUInteger frameCacheSizeCurrent = self.frameCacheSizeOptimal;
|
||||
|
||||
// If set, respect the caps.
|
||||
if (self.frameCacheSizeMax > FLAnimatedImageFrameCacheSizeNoLimit) {
|
||||
frameCacheSizeCurrent = MIN(frameCacheSizeCurrent, self.frameCacheSizeMax);
|
||||
}
|
||||
|
||||
if (self.frameCacheSizeMaxInternal > FLAnimatedImageFrameCacheSizeNoLimit) {
|
||||
frameCacheSizeCurrent = MIN(frameCacheSizeCurrent, self.frameCacheSizeMaxInternal);
|
||||
}
|
||||
|
||||
return frameCacheSizeCurrent;
|
||||
}
|
||||
|
||||
|
||||
- (void)setFrameCacheSizeMax:(NSUInteger)frameCacheSizeMax
|
||||
{
|
||||
if (_frameCacheSizeMax != frameCacheSizeMax) {
|
||||
|
||||
// Remember whether the new cap will cause the current cache size to shrink; then we'll make sure to purge from the cache if needed.
|
||||
BOOL willFrameCacheSizeShrink = (frameCacheSizeMax < self.frameCacheSizeCurrent);
|
||||
|
||||
// Update the value
|
||||
_frameCacheSizeMax = frameCacheSizeMax;
|
||||
|
||||
if (willFrameCacheSizeShrink) {
|
||||
[self purgeFrameCacheIfNeeded];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (void)setFrameCacheSizeMaxInternal:(NSUInteger)frameCacheSizeMaxInternal
|
||||
{
|
||||
if (_frameCacheSizeMaxInternal != frameCacheSizeMaxInternal) {
|
||||
|
||||
// Remember whether the new cap will cause the current cache size to shrink; then we'll make sure to purge from the cache if needed.
|
||||
BOOL willFrameCacheSizeShrink = (frameCacheSizeMaxInternal < self.frameCacheSizeCurrent);
|
||||
|
||||
// Update the value
|
||||
_frameCacheSizeMaxInternal = frameCacheSizeMaxInternal;
|
||||
|
||||
if (willFrameCacheSizeShrink) {
|
||||
[self purgeFrameCacheIfNeeded];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [FLAnimatedImage class]) {
|
||||
// UIKit memory warning notification handler shared by all of the instances
|
||||
allAnimatedImagesWeak = [NSHashTable weakObjectsHashTable];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:nil usingBlock:^(NSNotification *note) {
|
||||
// UIKit notifications are posted on the main thread. didReceiveMemoryWarning: is expecting the main run loop, and we don't lock on allAnimatedImagesWeak
|
||||
NSAssert([NSThread isMainThread], @"Received memory warning on non-main thread");
|
||||
// Get a strong reference to all of the images. If an instance is returned in this array, it is still live and has not entered dealloc.
|
||||
// Note that FLAnimatedImages can be created on any thread, so the hash table must be locked.
|
||||
NSArray *images = nil;
|
||||
@synchronized(allAnimatedImagesWeak) {
|
||||
images = [[allAnimatedImagesWeak allObjects] copy];
|
||||
}
|
||||
// Now issue notifications to all of the images while holding a strong reference to them
|
||||
[images makeObjectsPerformSelector:@selector(didReceiveMemoryWarning:) withObject:note];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
FLAnimatedImage *animatedImage = [self initWithAnimatedGIFData:nil];
|
||||
if (!animatedImage) {
|
||||
FLLog(FLLogLevelError, @"Use `-initWithAnimatedGIFData:` and supply the animated GIF data as an argument to initialize an object of type `FLAnimatedImage`.");
|
||||
}
|
||||
return animatedImage;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithAnimatedGIFData:(NSData *)data
|
||||
{
|
||||
return [self initWithAnimatedGIFData:data optimalFrameCacheSize:0 predrawingEnabled:YES];
|
||||
}
|
||||
|
||||
- (instancetype)initWithAnimatedGIFData:(NSData *)data optimalFrameCacheSize:(NSUInteger)optimalFrameCacheSize predrawingEnabled:(BOOL)isPredrawingEnabled
|
||||
{
|
||||
// Early return if no data supplied!
|
||||
BOOL hasData = ([data length] > 0);
|
||||
if (!hasData) {
|
||||
FLLog(FLLogLevelError, @"No animated GIF data supplied.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// Do one-time initializations of `readonly` properties directly to ivar to prevent implicit actions and avoid need for private `readwrite` property overrides.
|
||||
|
||||
// Keep a strong reference to `data` and expose it read-only publicly.
|
||||
// However, we will use the `_imageSource` as handler to the image data throughout our life cycle.
|
||||
_data = data;
|
||||
_predrawingEnabled = isPredrawingEnabled;
|
||||
|
||||
// Initialize internal data structures
|
||||
_cachedFramesForIndexes = [[NSMutableDictionary alloc] init];
|
||||
_cachedFrameIndexes = [[NSMutableIndexSet alloc] init];
|
||||
_requestedFrameIndexes = [[NSMutableIndexSet alloc] init];
|
||||
|
||||
// Note: We could leverage `CGImageSourceCreateWithURL` too to add a second initializer `-initWithAnimatedGIFContentsOfURL:`.
|
||||
_imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data,
|
||||
(__bridge CFDictionaryRef)@{(NSString *)kCGImageSourceShouldCache: @NO});
|
||||
// Early return on failure!
|
||||
if (!_imageSource) {
|
||||
FLLog(FLLogLevelError, @"Failed to `CGImageSourceCreateWithData` for animated GIF data %@", data);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Early return if not GIF!
|
||||
CFStringRef imageSourceContainerType = CGImageSourceGetType(_imageSource);
|
||||
BOOL isGIFData = UTTypeConformsTo(imageSourceContainerType, kUTTypeGIF);
|
||||
if (!isGIFData) {
|
||||
FLLog(FLLogLevelError, @"Supplied data is of type %@ and doesn't seem to be GIF data %@", imageSourceContainerType, data);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get `LoopCount`
|
||||
// Note: 0 means repeating the animation indefinitely.
|
||||
// Image properties example:
|
||||
// {
|
||||
// FileSize = 314446;
|
||||
// "{GIF}" = {
|
||||
// HasGlobalColorMap = 1;
|
||||
// LoopCount = 0;
|
||||
// };
|
||||
// }
|
||||
NSDictionary *imageProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(_imageSource, NULL);
|
||||
_loopCount = [[[imageProperties objectForKey:(id)kCGImagePropertyGIFDictionary] objectForKey:(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
|
||||
|
||||
// Iterate through frame images
|
||||
size_t imageCount = CGImageSourceGetCount(_imageSource);
|
||||
NSUInteger skippedFrameCount = 0;
|
||||
NSMutableDictionary *delayTimesForIndexesMutable = [NSMutableDictionary dictionaryWithCapacity:imageCount];
|
||||
for (size_t i = 0; i < imageCount; i++) {
|
||||
@autoreleasepool {
|
||||
CGImageRef frameImageRef = CGImageSourceCreateImageAtIndex(_imageSource, i, NULL);
|
||||
if (frameImageRef) {
|
||||
UIImage *frameImage = [UIImage imageWithCGImage:frameImageRef];
|
||||
// Check for valid `frameImage` before parsing its properties as frames can be corrupted (and `frameImage` even `nil` when `frameImageRef` was valid).
|
||||
if (frameImage) {
|
||||
// Set poster image
|
||||
if (!self.posterImage) {
|
||||
_posterImage = frameImage;
|
||||
// Set its size to proxy our size.
|
||||
_size = _posterImage.size;
|
||||
// Remember index of poster image so we never purge it; also add it to the cache.
|
||||
_posterImageFrameIndex = i;
|
||||
[self.cachedFramesForIndexes setObject:self.posterImage forKey:@(self.posterImageFrameIndex)];
|
||||
[self.cachedFrameIndexes addIndex:self.posterImageFrameIndex];
|
||||
}
|
||||
|
||||
// Get `DelayTime`
|
||||
// Note: It's not in (1/100) of a second like still falsely described in the documentation as per iOS 8 (rdar://19507384) but in seconds stored as `kCFNumberFloat32Type`.
|
||||
// Frame properties example:
|
||||
// {
|
||||
// ColorModel = RGB;
|
||||
// Depth = 8;
|
||||
// PixelHeight = 960;
|
||||
// PixelWidth = 640;
|
||||
// "{GIF}" = {
|
||||
// DelayTime = "0.4";
|
||||
// UnclampedDelayTime = "0.4";
|
||||
// };
|
||||
// }
|
||||
|
||||
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(_imageSource, i, NULL);
|
||||
NSDictionary *framePropertiesGIF = [frameProperties objectForKey:(id)kCGImagePropertyGIFDictionary];
|
||||
|
||||
// Try to use the unclamped delay time; fall back to the normal delay time.
|
||||
NSNumber *delayTime = [framePropertiesGIF objectForKey:(id)kCGImagePropertyGIFUnclampedDelayTime];
|
||||
if (!delayTime) {
|
||||
delayTime = [framePropertiesGIF objectForKey:(id)kCGImagePropertyGIFDelayTime];
|
||||
}
|
||||
// If we don't get a delay time from the properties, fall back to `kDelayTimeIntervalDefault` or carry over the preceding frame's value.
|
||||
const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
|
||||
if (!delayTime) {
|
||||
if (i == 0) {
|
||||
FLLog(FLLogLevelInfo, @"Falling back to default delay time for first frame %@ because none found in GIF properties %@", frameImage, frameProperties);
|
||||
delayTime = @(kDelayTimeIntervalDefault);
|
||||
} else {
|
||||
FLLog(FLLogLevelInfo, @"Falling back to preceding delay time for frame %zu %@ because none found in GIF properties %@", i, frameImage, frameProperties);
|
||||
delayTime = delayTimesForIndexesMutable[@(i - 1)];
|
||||
}
|
||||
}
|
||||
// Support frame delays as low as `kFLAnimatedImageDelayTimeIntervalMinimum`, with anything below being rounded up to `kDelayTimeIntervalDefault` for legacy compatibility.
|
||||
// To support the minimum even when rounding errors occur, use an epsilon when comparing. We downcast to float because that's what we get for delayTime from ImageIO.
|
||||
if ([delayTime floatValue] < ((float)kFLAnimatedImageDelayTimeIntervalMinimum - FLT_EPSILON)) {
|
||||
FLLog(FLLogLevelInfo, @"Rounding frame %zu's `delayTime` from %f up to default %f (minimum supported: %f).", i, [delayTime floatValue], kDelayTimeIntervalDefault, kFLAnimatedImageDelayTimeIntervalMinimum);
|
||||
delayTime = @(kDelayTimeIntervalDefault);
|
||||
}
|
||||
delayTimesForIndexesMutable[@(i)] = delayTime;
|
||||
} else {
|
||||
skippedFrameCount++;
|
||||
FLLog(FLLogLevelInfo, @"Dropping frame %zu because valid `CGImageRef` %@ did result in `nil`-`UIImage`.", i, frameImageRef);
|
||||
}
|
||||
CFRelease(frameImageRef);
|
||||
} else {
|
||||
skippedFrameCount++;
|
||||
FLLog(FLLogLevelInfo, @"Dropping frame %zu because failed to `CGImageSourceCreateImageAtIndex` with image source %@", i, _imageSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
_delayTimesForIndexes = [delayTimesForIndexesMutable copy];
|
||||
_frameCount = imageCount;
|
||||
|
||||
if (self.frameCount == 0) {
|
||||
FLLog(FLLogLevelInfo, @"Failed to create any valid frames for GIF with properties %@", imageProperties);
|
||||
return nil;
|
||||
} else if (self.frameCount == 1) {
|
||||
// Warn when we only have a single frame but return a valid GIF.
|
||||
FLLog(FLLogLevelInfo, @"Created valid GIF but with only a single frame. Image properties: %@", imageProperties);
|
||||
} else {
|
||||
// We have multiple frames, rock on!
|
||||
}
|
||||
|
||||
// If no value is provided, select a default based on the GIF.
|
||||
if (optimalFrameCacheSize == 0) {
|
||||
// Calculate the optimal frame cache size: try choosing a larger buffer window depending on the predicted image size.
|
||||
// It's only dependent on the image size & number of frames and never changes.
|
||||
CGFloat animatedImageDataSize = CGImageGetBytesPerRow(self.posterImage.CGImage) * self.size.height * (self.frameCount - skippedFrameCount) / MEGABYTE;
|
||||
if (animatedImageDataSize <= FLAnimatedImageDataSizeCategoryAll) {
|
||||
_frameCacheSizeOptimal = self.frameCount;
|
||||
} else if (animatedImageDataSize <= FLAnimatedImageDataSizeCategoryDefault) {
|
||||
// This value doesn't depend on device memory much because if we're not keeping all frames in memory we will always be decoding 1 frame up ahead per 1 frame that gets played and at this point we might as well just keep a small buffer just large enough to keep from running out of frames.
|
||||
_frameCacheSizeOptimal = FLAnimatedImageFrameCacheSizeDefault;
|
||||
} else {
|
||||
// The predicted size exceeds the limits to build up a cache and we go into low memory mode from the beginning.
|
||||
_frameCacheSizeOptimal = FLAnimatedImageFrameCacheSizeLowMemory;
|
||||
}
|
||||
} else {
|
||||
// Use the provided value.
|
||||
_frameCacheSizeOptimal = optimalFrameCacheSize;
|
||||
}
|
||||
// In any case, cap the optimal cache size at the frame count.
|
||||
_frameCacheSizeOptimal = MIN(_frameCacheSizeOptimal, self.frameCount);
|
||||
|
||||
// Convenience/minor performance optimization; keep an index set handy with the full range to return in `-frameIndexesToCache`.
|
||||
_allFramesIndexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, self.frameCount)];
|
||||
|
||||
// See the property declarations for descriptions.
|
||||
_weakProxy = (id)[FLWeakProxy weakProxyForObject:self];
|
||||
|
||||
// Register this instance in the weak table for memory notifications. The NSHashTable will clean up after itself when we're gone.
|
||||
// Note that FLAnimatedImages can be created on any thread, so the hash table must be locked.
|
||||
@synchronized(allAnimatedImagesWeak) {
|
||||
[allAnimatedImagesWeak addObject:self];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
+ (instancetype)animatedImageWithGIFData:(NSData *)data
|
||||
{
|
||||
FLAnimatedImage *animatedImage = [[FLAnimatedImage alloc] initWithAnimatedGIFData:data];
|
||||
return animatedImage;
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_weakProxy) {
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:_weakProxy];
|
||||
}
|
||||
|
||||
if (_imageSource) {
|
||||
CFRelease(_imageSource);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Public Methods
|
||||
|
||||
// See header for more details.
|
||||
// Note: both consumer and producer are throttled: consumer by frame timings and producer by the available memory (max buffer window size).
|
||||
- (UIImage *)imageLazilyCachedAtIndex:(NSUInteger)index
|
||||
{
|
||||
// Early return if the requested index is beyond bounds.
|
||||
// Note: We're comparing an index with a count and need to bail on greater than or equal to.
|
||||
if (index >= self.frameCount) {
|
||||
FLLog(FLLogLevelWarn, @"Skipping requested frame %lu beyond bounds (total frame count: %lu) for animated image: %@", (unsigned long)index, (unsigned long)self.frameCount, self);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Remember requested frame index, this influences what we should cache next.
|
||||
self.requestedFrameIndex = index;
|
||||
#if defined(DEBUG) && DEBUG
|
||||
if ([self.debug_delegate respondsToSelector:@selector(debug_animatedImage:didRequestCachedFrame:)]) {
|
||||
[self.debug_delegate debug_animatedImage:self didRequestCachedFrame:index];
|
||||
}
|
||||
#endif
|
||||
|
||||
// Quick check to avoid doing any work if we already have all possible frames cached, a common case.
|
||||
if ([self.cachedFrameIndexes count] < self.frameCount) {
|
||||
// If we have frames that should be cached but aren't and aren't requested yet, request them.
|
||||
// Exclude existing cached frames, frames already requested, and specially cached poster image.
|
||||
NSMutableIndexSet *frameIndexesToAddToCacheMutable = [self frameIndexesToCache];
|
||||
[frameIndexesToAddToCacheMutable removeIndexes:self.cachedFrameIndexes];
|
||||
[frameIndexesToAddToCacheMutable removeIndexes:self.requestedFrameIndexes];
|
||||
[frameIndexesToAddToCacheMutable removeIndex:self.posterImageFrameIndex];
|
||||
NSIndexSet *frameIndexesToAddToCache = [frameIndexesToAddToCacheMutable copy];
|
||||
|
||||
// Asynchronously add frames to our cache.
|
||||
if ([frameIndexesToAddToCache count] > 0) {
|
||||
[self addFrameIndexesToCache:frameIndexesToAddToCache];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the specified image.
|
||||
UIImage *image = self.cachedFramesForIndexes[@(index)];
|
||||
|
||||
// Purge if needed based on the current playhead position.
|
||||
[self purgeFrameCacheIfNeeded];
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
// Only called once from `-imageLazilyCachedAtIndex` but factored into its own method for logical grouping.
|
||||
- (void)addFrameIndexesToCache:(NSIndexSet *)frameIndexesToAddToCache
|
||||
{
|
||||
// Order matters. First, iterate over the indexes starting from the requested frame index.
|
||||
// Then, if there are any indexes before the requested frame index, do those.
|
||||
NSRange firstRange = NSMakeRange(self.requestedFrameIndex, self.frameCount - self.requestedFrameIndex);
|
||||
NSRange secondRange = NSMakeRange(0, self.requestedFrameIndex);
|
||||
if (firstRange.length + secondRange.length != self.frameCount) {
|
||||
FLLog(FLLogLevelWarn, @"Two-part frame cache range doesn't equal full range.");
|
||||
}
|
||||
|
||||
// Add to the requested list before we actually kick them off, so they don't get into the queue twice.
|
||||
[self.requestedFrameIndexes addIndexes:frameIndexesToAddToCache];
|
||||
|
||||
// Lazily create dedicated isolation queue.
|
||||
if (!self.serialQueue) {
|
||||
_serialQueue = dispatch_queue_create("com.flipboard.framecachingqueue", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
|
||||
// Start streaming requested frames in the background into the cache.
|
||||
// Avoid capturing self in the block as there's no reason to keep doing work if the animated image went away.
|
||||
FLAnimatedImage * __weak weakSelf = self;
|
||||
dispatch_async(self.serialQueue, ^{
|
||||
// Produce and cache next needed frame.
|
||||
void (^frameRangeBlock)(NSRange, BOOL *) = ^(NSRange range, BOOL *stop) {
|
||||
// Iterate through contiguous indexes; can be faster than `enumerateIndexesInRange:options:usingBlock:`.
|
||||
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
|
||||
#if defined(DEBUG) && DEBUG
|
||||
CFTimeInterval predrawBeginTime = CACurrentMediaTime();
|
||||
#endif
|
||||
UIImage *image = [weakSelf imageAtIndex:i];
|
||||
#if defined(DEBUG) && DEBUG
|
||||
CFTimeInterval predrawDuration = CACurrentMediaTime() - predrawBeginTime;
|
||||
CFTimeInterval slowdownDuration = 0.0;
|
||||
if ([self.debug_delegate respondsToSelector:@selector(debug_animatedImagePredrawingSlowdownFactor:)]) {
|
||||
CGFloat predrawingSlowdownFactor = [self.debug_delegate debug_animatedImagePredrawingSlowdownFactor:self];
|
||||
slowdownDuration = predrawDuration * predrawingSlowdownFactor - predrawDuration;
|
||||
[NSThread sleepForTimeInterval:slowdownDuration];
|
||||
}
|
||||
FLLog(FLLogLevelVerbose, @"Predrew frame %lu in %f ms for animated image: %@", (unsigned long)i, (predrawDuration + slowdownDuration) * 1000, self);
|
||||
#endif
|
||||
// The results get returned one by one as soon as they're ready (and not in batch).
|
||||
// The benefits of having the first frames as quick as possible outweigh building up a buffer to cope with potential hiccups when the CPU suddenly gets busy.
|
||||
if (image && weakSelf) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
weakSelf.cachedFramesForIndexes[@(i)] = image;
|
||||
[weakSelf.cachedFrameIndexes addIndex:i];
|
||||
[weakSelf.requestedFrameIndexes removeIndex:i];
|
||||
#if defined(DEBUG) && DEBUG
|
||||
if ([weakSelf.debug_delegate respondsToSelector:@selector(debug_animatedImage:didUpdateCachedFrames:)]) {
|
||||
[weakSelf.debug_delegate debug_animatedImage:weakSelf didUpdateCachedFrames:weakSelf.cachedFrameIndexes];
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[frameIndexesToAddToCache enumerateRangesInRange:firstRange options:0 usingBlock:frameRangeBlock];
|
||||
[frameIndexesToAddToCache enumerateRangesInRange:secondRange options:0 usingBlock:frameRangeBlock];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+ (CGSize)sizeForImage:(id)image
|
||||
{
|
||||
CGSize imageSize = CGSizeZero;
|
||||
|
||||
// Early return for nil
|
||||
if (!image) {
|
||||
return imageSize;
|
||||
}
|
||||
|
||||
if ([image isKindOfClass:[UIImage class]]) {
|
||||
UIImage *uiImage = (UIImage *)image;
|
||||
imageSize = uiImage.size;
|
||||
} else if ([image isKindOfClass:[FLAnimatedImage class]]) {
|
||||
FLAnimatedImage *animatedImage = (FLAnimatedImage *)image;
|
||||
imageSize = animatedImage.size;
|
||||
} else {
|
||||
// Bear trap to capture bad images; we have seen crashers cropping up on iOS 7.
|
||||
FLLog(FLLogLevelError, @"`image` isn't of expected types `UIImage` or `FLAnimatedImage`: %@", image);
|
||||
}
|
||||
|
||||
return imageSize;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private Methods
|
||||
#pragma mark Frame Loading
|
||||
|
||||
- (UIImage *)imageAtIndex:(NSUInteger)index
|
||||
{
|
||||
// It's very important to use the cached `_imageSource` since the random access to a frame with `CGImageSourceCreateImageAtIndex` turns from an O(1) into an O(n) operation when re-initializing the image source every time.
|
||||
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
|
||||
|
||||
// Early return for nil
|
||||
if (!imageRef) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *image = [UIImage imageWithCGImage:imageRef];
|
||||
CFRelease(imageRef);
|
||||
|
||||
// Loading in the image object is only half the work, the displaying image view would still have to synchronosly wait and decode the image, so we go ahead and do that here on the background thread.
|
||||
if (self.isPredrawingEnabled) {
|
||||
image = [[self class] predrawnImageFromImage:image];
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Frame Caching
|
||||
|
||||
- (NSMutableIndexSet *)frameIndexesToCache
|
||||
{
|
||||
NSMutableIndexSet *indexesToCache = nil;
|
||||
// Quick check to avoid building the index set if the number of frames to cache equals the total frame count.
|
||||
if (self.frameCacheSizeCurrent == self.frameCount) {
|
||||
indexesToCache = [self.allFramesIndexSet mutableCopy];
|
||||
} else {
|
||||
indexesToCache = [[NSMutableIndexSet alloc] init];
|
||||
|
||||
// Add indexes to the set in two separate blocks- the first starting from the requested frame index, up to the limit or the end.
|
||||
// The second, if needed, the remaining number of frames beginning at index zero.
|
||||
NSUInteger firstLength = MIN(self.frameCacheSizeCurrent, self.frameCount - self.requestedFrameIndex);
|
||||
NSRange firstRange = NSMakeRange(self.requestedFrameIndex, firstLength);
|
||||
[indexesToCache addIndexesInRange:firstRange];
|
||||
NSUInteger secondLength = self.frameCacheSizeCurrent - firstLength;
|
||||
if (secondLength > 0) {
|
||||
NSRange secondRange = NSMakeRange(0, secondLength);
|
||||
[indexesToCache addIndexesInRange:secondRange];
|
||||
}
|
||||
// Double check our math, before we add the poster image index which may increase it by one.
|
||||
if ([indexesToCache count] != self.frameCacheSizeCurrent) {
|
||||
FLLog(FLLogLevelWarn, @"Number of frames to cache doesn't equal expected cache size.");
|
||||
}
|
||||
|
||||
[indexesToCache addIndex:self.posterImageFrameIndex];
|
||||
}
|
||||
|
||||
return indexesToCache;
|
||||
}
|
||||
|
||||
|
||||
- (void)purgeFrameCacheIfNeeded
|
||||
{
|
||||
// Purge frames that are currently cached but don't need to be.
|
||||
// But not if we're still under the number of frames to cache.
|
||||
// This way, if all frames are allowed to be cached (the common case), we can skip all the `NSIndexSet` math below.
|
||||
if ([self.cachedFrameIndexes count] > self.frameCacheSizeCurrent) {
|
||||
NSMutableIndexSet *indexesToPurge = [self.cachedFrameIndexes mutableCopy];
|
||||
[indexesToPurge removeIndexes:[self frameIndexesToCache]];
|
||||
[indexesToPurge enumerateRangesUsingBlock:^(NSRange range, BOOL *stop) {
|
||||
// Iterate through contiguous indexes; can be faster than `enumerateIndexesInRange:options:usingBlock:`.
|
||||
for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
|
||||
[self.cachedFrameIndexes removeIndex:i];
|
||||
[self.cachedFramesForIndexes removeObjectForKey:@(i)];
|
||||
// Note: Don't `CGImageSourceRemoveCacheAtIndex` on the image source for frames that we don't want cached any longer to maintain O(1) time access.
|
||||
#if defined(DEBUG) && DEBUG
|
||||
if ([self.debug_delegate respondsToSelector:@selector(debug_animatedImage:didUpdateCachedFrames:)]) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.debug_delegate debug_animatedImage:self didUpdateCachedFrames:self.cachedFrameIndexes];
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)growFrameCacheSizeAfterMemoryWarning:(NSNumber *)frameCacheSize
|
||||
{
|
||||
self.frameCacheSizeMaxInternal = [frameCacheSize unsignedIntegerValue];
|
||||
FLLog(FLLogLevelDebug, @"Grew frame cache size max to %lu after memory warning for animated image: %@", (unsigned long)self.frameCacheSizeMaxInternal, self);
|
||||
|
||||
// Schedule resetting the frame cache size max completely after a while.
|
||||
const NSTimeInterval kResetDelay = 3.0;
|
||||
[self.weakProxy performSelector:@selector(resetFrameCacheSizeMaxInternal) withObject:nil afterDelay:kResetDelay];
|
||||
}
|
||||
|
||||
|
||||
- (void)resetFrameCacheSizeMaxInternal
|
||||
{
|
||||
self.frameCacheSizeMaxInternal = FLAnimatedImageFrameCacheSizeNoLimit;
|
||||
FLLog(FLLogLevelDebug, @"Reset frame cache size max (current frame cache size: %lu) for animated image: %@", (unsigned long)self.frameCacheSizeCurrent, self);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark System Memory Warnings Notification Handler
|
||||
|
||||
- (void)didReceiveMemoryWarning:(NSNotification *)notification
|
||||
{
|
||||
self.memoryWarningCount++;
|
||||
|
||||
// If we were about to grow larger, but got rapped on our knuckles by the system again, cancel.
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self.weakProxy selector:@selector(growFrameCacheSizeAfterMemoryWarning:) object:@(FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning)];
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self.weakProxy selector:@selector(resetFrameCacheSizeMaxInternal) object:nil];
|
||||
|
||||
// Go down to the minimum and by that implicitly immediately purge from the cache if needed to not get jettisoned by the system and start producing frames on-demand.
|
||||
FLLog(FLLogLevelDebug, @"Attempt setting frame cache size max to %lu (previous was %lu) after memory warning #%lu for animated image: %@", (unsigned long)FLAnimatedImageFrameCacheSizeLowMemory, (unsigned long)self.frameCacheSizeMaxInternal, (unsigned long)self.memoryWarningCount, self);
|
||||
self.frameCacheSizeMaxInternal = FLAnimatedImageFrameCacheSizeLowMemory;
|
||||
|
||||
// Schedule growing larger again after a while, but cap our attempts to prevent a periodic sawtooth wave (ramps upward and then sharply drops) of memory usage.
|
||||
//
|
||||
// [mem]^ (2) (5) (6) 1) Loading frames for the first time
|
||||
// (*)| , , , 2) Mem warning #1; purge cache
|
||||
// | /| (4)/| /| 3) Grow cache size a bit after a while, if no mem warning occurs
|
||||
// | / | _/ | _/ | 4) Try to grow cache size back to optimum after a while, if no mem warning occurs
|
||||
// |(1)/ |_/ |/ |__(7) 5) Mem warning #2; purge cache
|
||||
// |__/ (3) 6) After repetition of (3) and (4), mem warning #3; purge cache
|
||||
// +----------------------> 7) After 3 mem warnings, stay at minimum cache size
|
||||
// [t]
|
||||
// *) The mem high water mark before we get warned might change for every cycle.
|
||||
//
|
||||
const NSUInteger kGrowAttemptsMax = 2;
|
||||
const NSTimeInterval kGrowDelay = 2.0;
|
||||
if ((self.memoryWarningCount - 1) <= kGrowAttemptsMax) {
|
||||
[self.weakProxy performSelector:@selector(growFrameCacheSizeAfterMemoryWarning:) withObject:@(FLAnimatedImageFrameCacheSizeGrowAfterMemoryWarning) afterDelay:kGrowDelay];
|
||||
}
|
||||
|
||||
// Note: It's not possible to get the level of a memory warning with a public API: http://stackoverflow.com/questions/2915247/iphone-os-memory-warnings-what-do-the-different-levels-mean/2915477#2915477
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Image Decoding
|
||||
|
||||
// Decodes the image's data and draws it off-screen fully in memory; it's thread-safe and hence can be called on a background thread.
|
||||
// On success, the returned object is a new `UIImage` instance with the same content as the one passed in.
|
||||
// On failure, the returned object is the unchanged passed in one; the data will not be predrawn in memory though and an error will be logged.
|
||||
// First inspired by & good Karma to: https://gist.github.com/steipete/1144242
|
||||
+ (UIImage *)predrawnImageFromImage:(UIImage *)imageToPredraw
|
||||
{
|
||||
// Always use a device RGB color space for simplicity and predictability what will be going on.
|
||||
CGColorSpaceRef colorSpaceDeviceRGBRef = CGColorSpaceCreateDeviceRGB();
|
||||
// Early return on failure!
|
||||
if (!colorSpaceDeviceRGBRef) {
|
||||
FLLog(FLLogLevelError, @"Failed to `CGColorSpaceCreateDeviceRGB` for image %@", imageToPredraw);
|
||||
return imageToPredraw;
|
||||
}
|
||||
|
||||
// Even when the image doesn't have transparency, we have to add the extra channel because Quartz doesn't support other pixel formats than 32 bpp/8 bpc for RGB:
|
||||
// kCGImageAlphaNoneSkipFirst, kCGImageAlphaNoneSkipLast, kCGImageAlphaPremultipliedFirst, kCGImageAlphaPremultipliedLast
|
||||
// (source: docs "Quartz 2D Programming Guide > Graphics Contexts > Table 2-1 Pixel formats supported for bitmap graphics contexts")
|
||||
size_t numberOfComponents = CGColorSpaceGetNumberOfComponents(colorSpaceDeviceRGBRef) + 1; // 4: RGB + A
|
||||
|
||||
// "In iOS 4.0 and later, and OS X v10.6 and later, you can pass NULL if you want Quartz to allocate memory for the bitmap." (source: docs)
|
||||
void *data = NULL;
|
||||
size_t width = imageToPredraw.size.width;
|
||||
size_t height = imageToPredraw.size.height;
|
||||
size_t bitsPerComponent = CHAR_BIT;
|
||||
|
||||
size_t bitsPerPixel = (bitsPerComponent * numberOfComponents);
|
||||
size_t bytesPerPixel = (bitsPerPixel / BYTE_SIZE);
|
||||
size_t bytesPerRow = (bytesPerPixel * width);
|
||||
|
||||
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
|
||||
|
||||
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageToPredraw.CGImage);
|
||||
// If the alpha info doesn't match to one of the supported formats (see above), pick a reasonable supported one.
|
||||
// "For bitmaps created in iOS 3.2 and later, the drawing environment uses the premultiplied ARGB format to store the bitmap data." (source: docs)
|
||||
if (alphaInfo == kCGImageAlphaNone || alphaInfo == kCGImageAlphaOnly) {
|
||||
alphaInfo = kCGImageAlphaNoneSkipFirst;
|
||||
} else if (alphaInfo == kCGImageAlphaFirst) {
|
||||
alphaInfo = kCGImageAlphaPremultipliedFirst;
|
||||
} else if (alphaInfo == kCGImageAlphaLast) {
|
||||
alphaInfo = kCGImageAlphaPremultipliedLast;
|
||||
}
|
||||
// "The constants for specifying the alpha channel information are declared with the `CGImageAlphaInfo` type but can be passed to this parameter safely." (source: docs)
|
||||
bitmapInfo |= alphaInfo;
|
||||
|
||||
// Create our own graphics context to draw to; `UIGraphicsGetCurrentContext`/`UIGraphicsBeginImageContextWithOptions` doesn't create a new context but returns the current one which isn't thread-safe (e.g. main thread could use it at the same time).
|
||||
// Note: It's not worth caching the bitmap context for multiple frames ("unique key" would be `width`, `height` and `hasAlpha`), it's ~50% slower. Time spent in libRIP's `CGSBlendBGRA8888toARGB8888` suddenly shoots up -- not sure why.
|
||||
CGContextRef bitmapContextRef = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, colorSpaceDeviceRGBRef, bitmapInfo);
|
||||
CGColorSpaceRelease(colorSpaceDeviceRGBRef);
|
||||
// Early return on failure!
|
||||
if (!bitmapContextRef) {
|
||||
FLLog(FLLogLevelError, @"Failed to `CGBitmapContextCreate` with color space %@ and parameters (width: %zu height: %zu bitsPerComponent: %zu bytesPerRow: %zu) for image %@", colorSpaceDeviceRGBRef, width, height, bitsPerComponent, bytesPerRow, imageToPredraw);
|
||||
return imageToPredraw;
|
||||
}
|
||||
|
||||
// Draw image in bitmap context and create image by preserving receiver's properties.
|
||||
CGContextDrawImage(bitmapContextRef, CGRectMake(0.0, 0.0, imageToPredraw.size.width, imageToPredraw.size.height), imageToPredraw.CGImage);
|
||||
CGImageRef predrawnImageRef = CGBitmapContextCreateImage(bitmapContextRef);
|
||||
UIImage *predrawnImage = [UIImage imageWithCGImage:predrawnImageRef scale:imageToPredraw.scale orientation:imageToPredraw.imageOrientation];
|
||||
CGImageRelease(predrawnImageRef);
|
||||
CGContextRelease(bitmapContextRef);
|
||||
|
||||
// Early return on failure!
|
||||
if (!predrawnImage) {
|
||||
FLLog(FLLogLevelError, @"Failed to `imageWithCGImage:scale:orientation:` with image ref %@ created with color space %@ and bitmap context %@ and properties and properties (scale: %f orientation: %ld) for image %@", predrawnImageRef, colorSpaceDeviceRGBRef, bitmapContextRef, imageToPredraw.scale, (long)imageToPredraw.imageOrientation, imageToPredraw);
|
||||
return imageToPredraw;
|
||||
}
|
||||
|
||||
return predrawnImage;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Description
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSString *description = [super description];
|
||||
|
||||
description = [description stringByAppendingFormat:@" size=%@", NSStringFromCGSize(self.size)];
|
||||
description = [description stringByAppendingFormat:@" frameCount=%lu", (unsigned long)self.frameCount];
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - Logging
|
||||
|
||||
@implementation FLAnimatedImage (Logging)
|
||||
|
||||
static void (^_logBlock)(NSString *logString, FLLogLevel logLevel) = nil;
|
||||
static FLLogLevel _logLevel;
|
||||
|
||||
+ (void)setLogBlock:(void (^)(NSString *logString, FLLogLevel logLevel))logBlock logLevel:(FLLogLevel)logLevel
|
||||
{
|
||||
_logBlock = logBlock;
|
||||
_logLevel = logLevel;
|
||||
}
|
||||
|
||||
+ (void)logStringFromBlock:(NSString *(^)(void))stringBlock withLevel:(FLLogLevel)level
|
||||
{
|
||||
if (level <= _logLevel && _logBlock && stringBlock) {
|
||||
_logBlock(stringBlock(), level);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark - FLWeakProxy
|
||||
|
||||
@interface FLWeakProxy ()
|
||||
|
||||
@property (nonatomic, weak) id target;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FLWeakProxy
|
||||
|
||||
#pragma mark Life Cycle
|
||||
|
||||
// This is the designated creation method of an `FLWeakProxy` and
|
||||
// as a subclass of `NSProxy` it doesn't respond to or need `-init`.
|
||||
+ (instancetype)weakProxyForObject:(id)targetObject
|
||||
{
|
||||
FLWeakProxy *weakProxy = [FLWeakProxy alloc];
|
||||
weakProxy.target = targetObject;
|
||||
return weakProxy;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Forwarding Messages
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)selector
|
||||
{
|
||||
// Keep it lightweight: access the ivar directly
|
||||
return _target;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - NSWeakProxy Method Overrides
|
||||
#pragma mark Handling Unimplemented Methods
|
||||
|
||||
- (void)forwardInvocation:(NSInvocation *)invocation
|
||||
{
|
||||
// Fallback for when target is nil. Don't do anything, just return 0/NULL/nil.
|
||||
// The method signature we've received to get here is just a dummy to keep `doesNotRecognizeSelector:` from firing.
|
||||
// We can't really handle struct return types here because we don't know the length.
|
||||
void *nullPointer = NULL;
|
||||
[invocation setReturnValue:&nullPointer];
|
||||
}
|
||||
|
||||
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
|
||||
{
|
||||
// We only get here if `forwardingTargetForSelector:` returns nil.
|
||||
// In that case, our weak target has been reclaimed. Return a dummy method signature to keep `doesNotRecognizeSelector:` from firing.
|
||||
// We'll emulate the Obj-c messaging nil behavior by setting the return value to nil in `forwardInvocation:`, but we'll assume that the return value is `sizeof(void *)`.
|
||||
// Other libraries handle this situation by making use of a global method signature cache, but that seems heavier than necessary and has issues as well.
|
||||
// See https://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html and https://github.com/steipete/PSTDelegateProxy/issues/1 for examples of using a method signature cache.
|
||||
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
|
||||
}
|
||||
|
||||
|
||||
@end
|
@ -1,36 +0,0 @@
|
||||
//
|
||||
// FLAnimatedImageView.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Raphael Schaad on 7/8/13.
|
||||
// Copyright (c) 2013-2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class FLAnimatedImage;
|
||||
@protocol FLAnimatedImageViewDebugDelegate;
|
||||
|
||||
|
||||
//
|
||||
// An `FLAnimatedImageView` can take an `FLAnimatedImage` and plays it automatically when in view hierarchy and stops when removed.
|
||||
// The animation can also be controlled with the `UIImageView` methods `-start/stop/isAnimating`.
|
||||
// It is a fully compatible `UIImageView` subclass and can be used as a drop-in component to work with existing code paths expecting to display a `UIImage`.
|
||||
// Under the hood it uses a `CADisplayLink` for playback, which can be inspected with `currentFrame` & `currentFrameIndex`.
|
||||
//
|
||||
@interface FLAnimatedImageView : UIImageView
|
||||
|
||||
// Setting `[UIImageView.image]` to a non-`nil` value clears out existing `animatedImage`.
|
||||
// And vice versa, setting `animatedImage` will initially populate the `[UIImageView.image]` to its `posterImage` and then start animating and hold `currentFrame`.
|
||||
@property (nonatomic, strong) FLAnimatedImage *animatedImage;
|
||||
@property (nonatomic, copy) void(^loopCompletionBlock)(NSUInteger loopCountRemaining);
|
||||
|
||||
@property (nonatomic, strong, readonly) UIImage *currentFrame;
|
||||
@property (nonatomic, assign, readonly) NSUInteger currentFrameIndex;
|
||||
|
||||
// The animation runloop mode. Enables playback during scrolling by allowing timer events (i.e. animation) with NSRunLoopCommonModes.
|
||||
// To keep scrolling smooth on single-core devices such as iPhone 3GS/4 and iPod Touch 4th gen, the default run loop mode is NSDefaultRunLoopMode. Otherwise, the default is NSDefaultRunLoopMode.
|
||||
@property (nonatomic, copy) NSString *runLoopMode;
|
||||
|
||||
@end
|
@ -1,437 +0,0 @@
|
||||
//
|
||||
// FLAnimatedImageView.h
|
||||
// Flipboard
|
||||
//
|
||||
// Created by Raphael Schaad on 7/8/13.
|
||||
// Copyright (c) 2013-2015 Flipboard. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#import "FLAnimatedImageView.h"
|
||||
#import "FLAnimatedImage.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
|
||||
#if defined(DEBUG) && DEBUG
|
||||
@protocol FLAnimatedImageViewDebugDelegate <NSObject>
|
||||
@optional
|
||||
- (void)debug_animatedImageView:(FLAnimatedImageView *)animatedImageView waitingForFrame:(NSUInteger)index duration:(NSTimeInterval)duration;
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
||||
@interface FLAnimatedImageView ()
|
||||
|
||||
// Override of public `readonly` properties as private `readwrite`
|
||||
@property (nonatomic, strong, readwrite) UIImage *currentFrame;
|
||||
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
|
||||
|
||||
@property (nonatomic, assign) NSUInteger loopCountdown;
|
||||
@property (nonatomic, assign) NSTimeInterval accumulator;
|
||||
@property (nonatomic, strong) CADisplayLink *displayLink;
|
||||
|
||||
@property (nonatomic, assign) BOOL shouldAnimate; // Before checking this value, call `-updateShouldAnimate` whenever the animated image or visibility (window, superview, hidden, alpha) has changed.
|
||||
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
|
||||
|
||||
#if defined(DEBUG) && DEBUG
|
||||
@property (nonatomic, weak) id<FLAnimatedImageViewDebugDelegate> debug_delegate;
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FLAnimatedImageView
|
||||
@synthesize runLoopMode = _runLoopMode;
|
||||
|
||||
#pragma mark - Initializers
|
||||
|
||||
// -initWithImage: isn't documented as a designated initializer of UIImageView, but it actually seems to be.
|
||||
// Using -initWithImage: doesn't call any of the other designated initializers.
|
||||
- (instancetype)initWithImage:(UIImage *)image
|
||||
{
|
||||
self = [super initWithImage:image];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// -initWithImage:highlightedImage: also isn't documented as a designated initializer of UIImageView, but it doesn't call any other designated initializers.
|
||||
- (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage
|
||||
{
|
||||
self = [super initWithImage:image highlightedImage:highlightedImage];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commonInit
|
||||
{
|
||||
self.runLoopMode = [[self class] defaultRunLoopMode];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Accessors
|
||||
#pragma mark Public
|
||||
|
||||
- (void)setAnimatedImage:(FLAnimatedImage *)animatedImage
|
||||
{
|
||||
if (![_animatedImage isEqual:animatedImage]) {
|
||||
if (animatedImage) {
|
||||
// Clear out the image.
|
||||
super.image = nil;
|
||||
// Ensure disabled highlighting; it's not supported (see `-setHighlighted:`).
|
||||
super.highlighted = NO;
|
||||
// UIImageView seems to bypass some accessors when calculating its intrinsic content size, so this ensures its intrinsic content size comes from the animated image.
|
||||
[self invalidateIntrinsicContentSize];
|
||||
} else {
|
||||
// Stop animating before the animated image gets cleared out.
|
||||
[self stopAnimating];
|
||||
}
|
||||
|
||||
_animatedImage = animatedImage;
|
||||
|
||||
self.currentFrame = animatedImage.posterImage;
|
||||
self.currentFrameIndex = 0;
|
||||
if (animatedImage.loopCount > 0) {
|
||||
self.loopCountdown = animatedImage.loopCount;
|
||||
} else {
|
||||
self.loopCountdown = NSUIntegerMax;
|
||||
}
|
||||
self.accumulator = 0.0;
|
||||
|
||||
// Start animating after the new animated image has been set.
|
||||
[self updateShouldAnimate];
|
||||
if (self.shouldAnimate) {
|
||||
[self startAnimating];
|
||||
}
|
||||
|
||||
[self.layer setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// Removes the display link from all run loop modes.
|
||||
[_displayLink invalidate];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UIView Method Overrides
|
||||
#pragma mark Observing View-Related Changes
|
||||
|
||||
- (void)didMoveToSuperview
|
||||
{
|
||||
[super didMoveToSuperview];
|
||||
|
||||
[self updateShouldAnimate];
|
||||
if (self.shouldAnimate) {
|
||||
[self startAnimating];
|
||||
} else {
|
||||
[self stopAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
|
||||
[self updateShouldAnimate];
|
||||
if (self.shouldAnimate) {
|
||||
[self startAnimating];
|
||||
} else {
|
||||
[self stopAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAlpha:(CGFloat)alpha
|
||||
{
|
||||
[super setAlpha:alpha];
|
||||
|
||||
[self updateShouldAnimate];
|
||||
if (self.shouldAnimate) {
|
||||
[self startAnimating];
|
||||
} else {
|
||||
[self stopAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHidden:(BOOL)hidden
|
||||
{
|
||||
[super setHidden:hidden];
|
||||
|
||||
[self updateShouldAnimate];
|
||||
if (self.shouldAnimate) {
|
||||
[self startAnimating];
|
||||
} else {
|
||||
[self stopAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Auto Layout
|
||||
|
||||
- (CGSize)intrinsicContentSize
|
||||
{
|
||||
// Default to let UIImageView handle the sizing of its image, and anything else it might consider.
|
||||
CGSize intrinsicContentSize = [super intrinsicContentSize];
|
||||
|
||||
// If we have have an animated image, use its image size.
|
||||
// UIImageView's intrinsic content size seems to be the size of its image. The obvious approach, simply calling `-invalidateIntrinsicContentSize` when setting an animated image, results in UIImageView steadfastly returning `{UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric}` for its intrinsicContentSize.
|
||||
// (Perhaps UIImageView bypasses its `-image` getter in its implementation of `-intrinsicContentSize`, as `-image` is not called after calling `-invalidateIntrinsicContentSize`.)
|
||||
if (self.animatedImage) {
|
||||
intrinsicContentSize = self.image.size;
|
||||
}
|
||||
|
||||
return intrinsicContentSize;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UIImageView Method Overrides
|
||||
#pragma mark Image Data
|
||||
|
||||
- (UIImage *)image
|
||||
{
|
||||
UIImage *image = nil;
|
||||
if (self.animatedImage) {
|
||||
// Initially set to the poster image.
|
||||
image = self.currentFrame;
|
||||
} else {
|
||||
image = super.image;
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
- (void)setImage:(UIImage *)image
|
||||
{
|
||||
if (image) {
|
||||
// Clear out the animated image and implicitly pause animation playback.
|
||||
self.animatedImage = nil;
|
||||
}
|
||||
|
||||
super.image = image;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Animating Images
|
||||
|
||||
- (NSTimeInterval)frameDelayGreatestCommonDivisor
|
||||
{
|
||||
// Presision is set to half of the `kFLAnimatedImageDelayTimeIntervalMinimum` in order to minimize frame dropping.
|
||||
const NSTimeInterval kGreatestCommonDivisorPrecision = 2.0 / kFLAnimatedImageDelayTimeIntervalMinimum;
|
||||
|
||||
NSArray *delays = self.animatedImage.delayTimesForIndexes.allValues;
|
||||
|
||||
// Scales the frame delays by `kGreatestCommonDivisorPrecision`
|
||||
// then converts it to an UInteger for in order to calculate the GCD.
|
||||
NSUInteger scaledGCD = lrint([delays.firstObject floatValue] * kGreatestCommonDivisorPrecision);
|
||||
for (NSNumber *value in delays) {
|
||||
scaledGCD = gcd(lrint([value floatValue] * kGreatestCommonDivisorPrecision), scaledGCD);
|
||||
}
|
||||
|
||||
// Reverse to scale to get the value back into seconds.
|
||||
return scaledGCD / kGreatestCommonDivisorPrecision;
|
||||
}
|
||||
|
||||
|
||||
static NSUInteger gcd(NSUInteger a, NSUInteger b)
|
||||
{
|
||||
// http://en.wikipedia.org/wiki/Greatest_common_divisor
|
||||
if (a < b) {
|
||||
return gcd(b, a);
|
||||
} else if (a == b) {
|
||||
return b;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
NSUInteger remainder = a % b;
|
||||
if (remainder == 0) {
|
||||
return b;
|
||||
}
|
||||
a = b;
|
||||
b = remainder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)startAnimating
|
||||
{
|
||||
if (self.animatedImage) {
|
||||
// Lazily create the display link.
|
||||
if (!self.displayLink) {
|
||||
// It is important to note the use of a weak proxy here to avoid a retain cycle. `-displayLinkWithTarget:selector:`
|
||||
// will retain its target until it is invalidated. We use a weak proxy so that the image view will get deallocated
|
||||
// independent of the display link's lifetime. Upon image view deallocation, we invalidate the display
|
||||
// link which will lead to the deallocation of both the display link and the weak proxy.
|
||||
FLWeakProxy *weakProxy = [FLWeakProxy weakProxyForObject:self];
|
||||
self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)];
|
||||
|
||||
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode];
|
||||
}
|
||||
|
||||
// Note: The display link's `.frameInterval` value of 1 (default) means getting callbacks at the refresh rate of the display (~60Hz).
|
||||
// Setting it to 2 divides the frame rate by 2 and hence calls back at every other display refresh.
|
||||
const NSTimeInterval kDisplayRefreshRate = 60.0; // 60Hz
|
||||
self.displayLink.frameInterval = MAX([self frameDelayGreatestCommonDivisor] * kDisplayRefreshRate, 1);
|
||||
|
||||
self.displayLink.paused = NO;
|
||||
} else {
|
||||
[super startAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRunLoopMode:(NSString *)runLoopMode
|
||||
{
|
||||
if (![@[NSDefaultRunLoopMode, NSRunLoopCommonModes] containsObject:runLoopMode]) {
|
||||
NSAssert(NO, @"Invalid run loop mode: %@", runLoopMode);
|
||||
_runLoopMode = [[self class] defaultRunLoopMode];
|
||||
} else {
|
||||
_runLoopMode = runLoopMode;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopAnimating
|
||||
{
|
||||
if (self.animatedImage) {
|
||||
self.displayLink.paused = YES;
|
||||
} else {
|
||||
[super stopAnimating];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isAnimating
|
||||
{
|
||||
BOOL isAnimating = NO;
|
||||
if (self.animatedImage) {
|
||||
isAnimating = self.displayLink && !self.displayLink.isPaused;
|
||||
} else {
|
||||
isAnimating = [super isAnimating];
|
||||
}
|
||||
return isAnimating;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Highlighted Image Unsupport
|
||||
|
||||
- (void)setHighlighted:(BOOL)highlighted
|
||||
{
|
||||
// Highlighted image is unsupported for animated images, but implementing it breaks the image view when embedded in a UICollectionViewCell.
|
||||
if (!self.animatedImage) {
|
||||
[super setHighlighted:highlighted];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private Methods
|
||||
#pragma mark Animation
|
||||
|
||||
// Don't repeatedly check our window & superview in `-displayDidRefresh:` for performance reasons.
|
||||
// Just update our cached value whenever the animated image or visibility (window, superview, hidden, alpha) is changed.
|
||||
- (void)updateShouldAnimate
|
||||
{
|
||||
BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alpha > 0.0;
|
||||
self.shouldAnimate = self.animatedImage && isVisible;
|
||||
}
|
||||
|
||||
|
||||
- (void)displayDidRefresh:(CADisplayLink *)displayLink
|
||||
{
|
||||
// If for some reason a wild call makes it through when we shouldn't be animating, bail.
|
||||
// Early return!
|
||||
if (!self.shouldAnimate) {
|
||||
FLLog(FLLogLevelWarn, @"Trying to animate image when we shouldn't: %@", self);
|
||||
return;
|
||||
}
|
||||
|
||||
NSNumber *delayTimeNumber = [self.animatedImage.delayTimesForIndexes objectForKey:@(self.currentFrameIndex)];
|
||||
// If we don't have a frame delay (e.g. corrupt frame), don't update the view but skip the playhead to the next frame (in else-block).
|
||||
if (delayTimeNumber) {
|
||||
NSTimeInterval delayTime = [delayTimeNumber floatValue];
|
||||
// If we have a nil image (e.g. waiting for frame), don't update the view nor playhead.
|
||||
UIImage *image = [self.animatedImage imageLazilyCachedAtIndex:self.currentFrameIndex];
|
||||
if (image) {
|
||||
FLLog(FLLogLevelVerbose, @"Showing frame %lu for animated image: %@", (unsigned long)self.currentFrameIndex, self.animatedImage);
|
||||
self.currentFrame = image;
|
||||
if (self.needsDisplayWhenImageBecomesAvailable) {
|
||||
[self.layer setNeedsDisplay];
|
||||
self.needsDisplayWhenImageBecomesAvailable = NO;
|
||||
}
|
||||
|
||||
self.accumulator += displayLink.duration * displayLink.frameInterval;
|
||||
|
||||
// While-loop first inspired by & good Karma to: https://github.com/ondalabs/OLImageView/blob/master/OLImageView.m
|
||||
while (self.accumulator >= delayTime) {
|
||||
self.accumulator -= delayTime;
|
||||
self.currentFrameIndex++;
|
||||
if (self.currentFrameIndex >= self.animatedImage.frameCount) {
|
||||
// If we've looped the number of times that this animated image describes, stop looping.
|
||||
self.loopCountdown--;
|
||||
if (self.loopCompletionBlock) {
|
||||
self.loopCompletionBlock(self.loopCountdown);
|
||||
}
|
||||
|
||||
if (self.loopCountdown == 0) {
|
||||
[self stopAnimating];
|
||||
return;
|
||||
}
|
||||
self.currentFrameIndex = 0;
|
||||
}
|
||||
// Calling `-setNeedsDisplay` will just paint the current frame, not the new frame that we may have moved to.
|
||||
// Instead, set `needsDisplayWhenImageBecomesAvailable` to `YES` -- this will paint the new image once loaded.
|
||||
self.needsDisplayWhenImageBecomesAvailable = YES;
|
||||
}
|
||||
} else {
|
||||
FLLog(FLLogLevelDebug, @"Waiting for frame %lu for animated image: %@", (unsigned long)self.currentFrameIndex, self.animatedImage);
|
||||
#if defined(DEBUG) && DEBUG
|
||||
if ([self.debug_delegate respondsToSelector:@selector(debug_animatedImageView:waitingForFrame:duration:)]) {
|
||||
[self.debug_delegate debug_animatedImageView:self waitingForFrame:self.currentFrameIndex duration:(NSTimeInterval)displayLink.duration * displayLink.frameInterval];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
self.currentFrameIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString *)defaultRunLoopMode
|
||||
{
|
||||
// Key off `activeProcessorCount` (as opposed to `processorCount`) since the system could shut down cores in certain situations.
|
||||
return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - CALayerDelegate (Informal)
|
||||
#pragma mark Providing the Layer's Content
|
||||
|
||||
- (void)displayLayer:(CALayer *)layer
|
||||
{
|
||||
layer.contents = (__bridge id)self.image.CGImage;
|
||||
}
|
||||
|
||||
|
||||
@end
|
21
ios/Pods/FLAnimatedImage/LICENSE
generated
21
ios/Pods/FLAnimatedImage/LICENSE
generated
@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2016 Flipboard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
103
ios/Pods/FLAnimatedImage/README.md
generated
103
ios/Pods/FLAnimatedImage/README.md
generated
@ -1,103 +0,0 @@
|
||||
FLAnimatedImage is a performant animated GIF engine for iOS:
|
||||
|
||||
- Plays multiple GIFs simultaneously with a playback speed comparable to desktop browsers
|
||||
- Honors variable frame delays
|
||||
- Behaves gracefully under memory pressure
|
||||
- Eliminates delays or blocking during the first playback loop
|
||||
- Interprets the frame delays of fast GIFs the same way modern browsers do
|
||||
|
||||
It's a well-tested [component that powers all GIFs in Flipboard](http://engineering.flipboard.com/2014/05/animated-gif/). To understand its behavior it comes with an interactive demo:
|
||||
|
||||
![Flipboard playing multiple GIFs](https://github.com/Flipboard/FLAnimatedImage/raw/master/images/flanimatedimage-demo-player.gif)
|
||||
|
||||
## Who is this for?
|
||||
|
||||
- Apps that don't support animated GIFs yet
|
||||
- Apps that already support animated GIFs but want a higher performance solution
|
||||
- People who want to tinker with the code ([the corresponding blog post](http://engineering.flipboard.com/2014/05/animated-gif/) is a great place to start; also see the *To Do* section below)
|
||||
|
||||
## Installation & Usage
|
||||
|
||||
FLAnimatedImage is a well encapsulated drop-in component. Simply replace your `UIImageView` instances with instances of `FLAnimatedImageView` to get animated GIF support. There is no central cache or state to manage.
|
||||
|
||||
If using CocoaPods, the quickest way to try it out is to type this on the command line:
|
||||
|
||||
```shell
|
||||
$ pod try FLAnimatedImage
|
||||
```
|
||||
|
||||
To add it to your app, copy the two classes `FLAnimatedImage.h/.m` and `FLAnimatedImageView.h/.m` into your Xcode project or add via [CocoaPods](http://cocoapods.org) by adding this to your Podfile:
|
||||
|
||||
```ruby
|
||||
pod 'FLAnimatedImage', '~> 1.0'
|
||||
```
|
||||
|
||||
If using [Carthage](https://github.com/Carthage/Carthage), add following line into your `Cartfile`
|
||||
|
||||
```
|
||||
github "Flipboard/FLAnimatedImage"
|
||||
```
|
||||
|
||||
In your code, `#import "FLAnimatedImage.h"`, create an image from an animated GIF, and setup the image view to display it:
|
||||
|
||||
```objective-c
|
||||
FLAnimatedImage *image = [FLAnimatedImage animatedImageWithGIFData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif"]]];
|
||||
FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init];
|
||||
imageView.animatedImage = image;
|
||||
imageView.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
|
||||
[self.view addSubview:imageView];
|
||||
```
|
||||
|
||||
It's flexible to integrate in your custom image loading stack and backwards compatible to iOS 6.
|
||||
|
||||
It uses ARC and the Apple frameworks `QuartzCore`, `ImageIO`, `MobileCoreServices`, and `CoreGraphics`.
|
||||
|
||||
It is capable of fine-grained logging. A block can be set on `FLAnimatedImage` that's invoked when logging occurs with various log levels via the `+setLogBlock:logLevel:` method. For example:
|
||||
|
||||
```objective-c
|
||||
// Set up FLAnimatedImage logging.
|
||||
[FLAnimatedImage setLogBlock:^(NSString *logString, FLLogLevel logLevel) {
|
||||
// Using NSLog
|
||||
NSLog(@"%@", logString);
|
||||
|
||||
// ...or CocoaLumberjackLogger only logging warnings and errors
|
||||
if (logLevel == FLLogLevelError) {
|
||||
DDLogError(@"%@", logString);
|
||||
} else if (logLevel == FLLogLevelWarn) {
|
||||
DDLogWarn(@"%@", logString);
|
||||
}
|
||||
} logLevel:FLLogLevelWarn];
|
||||
```
|
||||
|
||||
Since FLAnimatedImage is licensed under MIT, it's compatible with the terms of using it for any app on the App Store.
|
||||
|
||||
## To Do
|
||||
- Support other animated image formats such as APNG or WebP (WebP support implemented [here](https://github.com/Flipboard/FLAnimatedImage/pull/86))
|
||||
- Integration into network libraries and image caches
|
||||
- Investigate whether `FLAnimatedImage` should become a `UIImage` subclass
|
||||
- Smarter buffering
|
||||
- Bring demo app to iPhone
|
||||
|
||||
This has successfully shipped to many people as is, but please do come with your questions, issues and pull requests!
|
||||
|
||||
## Select apps using FLAnimatedImage
|
||||
- [Dropbox](https://www.dropbox.com)
|
||||
- [Medium](https://medium.com)
|
||||
- [Facebook](https://facebook.com)
|
||||
- [Pinterest](https://pinterest.com)
|
||||
- [LiveBooth](http://www.liveboothapp.com)
|
||||
- [Design Shots](https://itunes.apple.com/app/id792517951)
|
||||
- [lWlVl Festival](http://lwlvl.com)
|
||||
- [Close-up](http://closeu.pe)
|
||||
- [Zip Code Finder](https://itunes.apple.com/app/id893031254)
|
||||
- [getGIF](https://itunes.apple.com/app/id964784701)
|
||||
- [Giffage](http://giffage.com)
|
||||
- [Flipboard](https://flipboard.com)
|
||||
- [Gifalicious](https://itunes.apple.com/us/app/gifalicious-see-your-gifs/id965346708?mt=8)
|
||||
- [Slack](https://slack.com/)
|
||||
- [Telegram](https://telegram.org/)
|
||||
- [HashPhotos](https://itunes.apple.com/app/id685784609)
|
||||
- [Ello](https://ello.co/)
|
||||
- [Dumpert](http://dumpert.nl)
|
||||
|
||||
If you're using FLAnimatedImage in your app please open a PR to add it to this list!
|
177
ios/Pods/Folly/LICENSE
generated
177
ios/Pods/Folly/LICENSE
generated
@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
246
ios/Pods/Folly/README.md
generated
246
ios/Pods/Folly/README.md
generated
@ -1,246 +0,0 @@
|
||||
Folly: Facebook Open-source Library
|
||||
-----------------------------------
|
||||
|
||||
[![Build Status](https://travis-ci.org/facebook/folly.svg?branch=master)](https://travis-ci.org/facebook/folly)
|
||||
|
||||
### What is `folly`?
|
||||
|
||||
Folly (acronymed loosely after Facebook Open Source Library) is a
|
||||
library of C++14 components designed with practicality and efficiency
|
||||
in mind. **Folly contains a variety of core library components used extensively
|
||||
at Facebook**. In particular, it's often a dependency of Facebook's other
|
||||
open source C++ efforts and place where those projects can share code.
|
||||
|
||||
It complements (as opposed to competing against) offerings
|
||||
such as Boost and of course `std`. In fact, we embark on defining our
|
||||
own component only when something we need is either not available, or
|
||||
does not meet the needed performance profile. We endeavor to remove
|
||||
things from folly if or when `std` or Boost obsoletes them.
|
||||
|
||||
Performance concerns permeate much of Folly, sometimes leading to
|
||||
designs that are more idiosyncratic than they would otherwise be (see
|
||||
e.g. `PackedSyncPtr.h`, `SmallLocks.h`). Good performance at large
|
||||
scale is a unifying theme in all of Folly.
|
||||
|
||||
### Logical Design
|
||||
|
||||
Folly is a collection of relatively independent components, some as
|
||||
simple as a few symbols. There is no restriction on internal
|
||||
dependencies, meaning that a given folly module may use any other
|
||||
folly components.
|
||||
|
||||
All symbols are defined in the top-level namespace `folly`, except of
|
||||
course macros. Macro names are ALL_UPPERCASE and should be prefixed
|
||||
with `FOLLY_`. Namespace `folly` defines other internal namespaces
|
||||
such as `internal` or `detail`. User code should not depend on symbols
|
||||
in those namespaces.
|
||||
|
||||
Folly has an `experimental` directory as well. This designation connotes
|
||||
primarily that we feel the API may change heavily over time. This code,
|
||||
typically, is still in heavy use and is well tested.
|
||||
|
||||
### Physical Design
|
||||
|
||||
At the top level Folly uses the classic "stuttering" scheme
|
||||
`folly/folly` used by Boost and others. The first directory serves as
|
||||
an installation root of the library (with possible versioning a la
|
||||
`folly-1.0/`), and the second is to distinguish the library when
|
||||
including files, e.g. `#include <folly/FBString.h>`.
|
||||
|
||||
The directory structure is flat (mimicking the namespace structure),
|
||||
i.e. we don't have an elaborate directory hierarchy (it is possible
|
||||
this will change in future versions). The subdirectory `experimental`
|
||||
contains files that are used inside folly and possibly at Facebook but
|
||||
not considered stable enough for client use. Your code should not use
|
||||
files in `folly/experimental` lest it may break when you update Folly.
|
||||
|
||||
The `folly/folly/test` subdirectory includes the unittests for all
|
||||
components, usually named `ComponentXyzTest.cpp` for each
|
||||
`ComponentXyz.*`. The `folly/folly/docs` directory contains
|
||||
documentation.
|
||||
|
||||
### What's in it?
|
||||
|
||||
Because of folly's fairly flat structure, the best way to see what's in it
|
||||
is to look at the headers in [top level `folly/` directory](https://github.com/facebook/folly/tree/master/folly). You can also
|
||||
check the [`docs` folder](folly/docs) for documentation, starting with the
|
||||
[overview](folly/docs/Overview.md).
|
||||
|
||||
Folly is published on Github at https://github.com/facebook/folly
|
||||
|
||||
### Build Notes
|
||||
|
||||
#### Dependencies
|
||||
|
||||
folly requires gcc 4.9+ and a version of boost compiled with C++14 support.
|
||||
|
||||
googletest is required to build and run folly's tests. You can download
|
||||
it from https://github.com/google/googletest/archive/release-1.8.0.tar.gz
|
||||
The following commands can be used to download and install it:
|
||||
|
||||
```
|
||||
wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz && \
|
||||
tar zxf release-1.8.0.tar.gz && \
|
||||
rm -f release-1.8.0.tar.gz && \
|
||||
cd googletest-release-1.8.0 && \
|
||||
cmake . && \
|
||||
make && \
|
||||
make install
|
||||
```
|
||||
|
||||
#### Finding dependencies in non-default locations
|
||||
|
||||
If you have boost, gtest, or other dependencies installed in a non-default
|
||||
location, you can use the `CMAKE_INCLUDE_PATH` and `CMAKE_LIBRARY_PATH`
|
||||
variables to make CMAKE look also look for header files and libraries in
|
||||
non-standard locations. For example, to also search the directories
|
||||
`/alt/include/path1` and `/alt/include/path2` for header files and the
|
||||
directories `/alt/lib/path1` and `/alt/lib/path2` for libraries, you can invoke
|
||||
`cmake` as follows:
|
||||
|
||||
```
|
||||
cmake \
|
||||
-DCMAKE_INCLUDE_PATH=/alt/include/path1:/alt/include/path2 \
|
||||
-DCMAKE_LIBRARY_PATH=/alt/lib/path1:/alt/lib/path2 ...
|
||||
```
|
||||
|
||||
#### Ubuntu 16.04 LTS
|
||||
|
||||
The following packages are required (feel free to cut and paste the apt-get
|
||||
command below):
|
||||
|
||||
```
|
||||
sudo apt-get install \
|
||||
g++ \
|
||||
cmake \
|
||||
libboost-all-dev \
|
||||
libevent-dev \
|
||||
libdouble-conversion-dev \
|
||||
libgoogle-glog-dev \
|
||||
libgflags-dev \
|
||||
libiberty-dev \
|
||||
liblz4-dev \
|
||||
liblzma-dev \
|
||||
libsnappy-dev \
|
||||
make \
|
||||
zlib1g-dev \
|
||||
binutils-dev \
|
||||
libjemalloc-dev \
|
||||
libssl-dev \
|
||||
pkg-config
|
||||
```
|
||||
|
||||
If advanced debugging functionality is required, use:
|
||||
|
||||
```
|
||||
sudo apt-get install \
|
||||
libunwind8-dev \
|
||||
libelf-dev \
|
||||
libdwarf-dev
|
||||
```
|
||||
|
||||
In the folly directory, run:
|
||||
```
|
||||
mkdir _build && cd _build
|
||||
cmake ..
|
||||
make -j $(nproc)
|
||||
make install
|
||||
```
|
||||
|
||||
#### OS X (Homebrew)
|
||||
|
||||
folly is available as a Formula and releases may be built via `brew install folly`.
|
||||
|
||||
You may also use `folly/build/bootstrap-osx-homebrew.sh` to build against `master`:
|
||||
|
||||
```
|
||||
cd folly
|
||||
./build/bootstrap-osx-homebrew.sh
|
||||
```
|
||||
|
||||
#### OS X (MacPorts)
|
||||
|
||||
Install the required packages from MacPorts:
|
||||
|
||||
```
|
||||
sudo port install \
|
||||
autoconf \
|
||||
automake \
|
||||
boost \
|
||||
gflags \
|
||||
git \
|
||||
google-glog \
|
||||
libevent \
|
||||
libtool \
|
||||
lz4 \
|
||||
lzma \
|
||||
scons \
|
||||
snappy \
|
||||
zlib
|
||||
```
|
||||
|
||||
Download and install double-conversion:
|
||||
|
||||
```
|
||||
git clone https://github.com/google/double-conversion.git
|
||||
cd double-conversion
|
||||
cmake -DBUILD_SHARED_LIBS=ON .
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
Download and install folly with the parameters listed below:
|
||||
|
||||
```
|
||||
git clone https://github.com/facebook/folly.git
|
||||
cd folly/folly
|
||||
autoreconf -ivf
|
||||
./configure CPPFLAGS="-I/opt/local/include" LDFLAGS="-L/opt/local/lib"
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
#### Windows (Vcpkg)
|
||||
|
||||
folly is available in [Vcpkg](https://github.com/Microsoft/vcpkg#vcpkg) and releases may be built via `vcpkg install folly:x64-windows`.
|
||||
|
||||
You may also use `vcpkg install folly:x64-windows --head` to build against `master`.
|
||||
|
||||
#### Other Linux distributions
|
||||
|
||||
- double-conversion (https://github.com/google/double-conversion)
|
||||
|
||||
Download and build double-conversion.
|
||||
You may need to tell cmake where to find it.
|
||||
|
||||
[double-conversion/] `ln -s src double-conversion`
|
||||
|
||||
[folly/] `mkdir build && cd build`
|
||||
[folly/build/] `cmake "-DCMAKE_INCLUDE_PATH=$DOUBLE_CONVERSION_HOME/include" "-DCMAKE_LIBRARY_PATH=$DOUBLE_CONVERSION_HOME/lib" ..`
|
||||
|
||||
[folly/build/] `make`
|
||||
|
||||
- additional platform specific dependencies:
|
||||
|
||||
Fedora >= 21 64-bit (last tested on Fedora 28 64-bit)
|
||||
- gcc
|
||||
- gcc-c++
|
||||
- cmake
|
||||
- automake
|
||||
- boost-devel
|
||||
- libtool
|
||||
- lz4-devel
|
||||
- lzma-devel
|
||||
- snappy-devel
|
||||
- zlib-devel
|
||||
- glog-devel
|
||||
- gflags-devel
|
||||
- scons
|
||||
- double-conversion-devel
|
||||
- openssl-devel
|
||||
- libevent-devel
|
||||
|
||||
Optional
|
||||
- libdwarf-dev
|
||||
- libelf-dev
|
||||
- libunwind8-dev
|
158
ios/Pods/Folly/folly/AtomicBitSet.h
generated
158
ios/Pods/Folly/folly/AtomicBitSet.h
generated
@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <folly/Portability.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* An atomic bitset of fixed size (specified at compile time).
|
||||
*/
|
||||
template <size_t N>
|
||||
class AtomicBitSet : private boost::noncopyable {
|
||||
public:
|
||||
/**
|
||||
* Construct an AtomicBitSet; all bits are initially false.
|
||||
*/
|
||||
AtomicBitSet();
|
||||
|
||||
/**
|
||||
* Set bit idx to true, using the given memory order. Returns the
|
||||
* previous value of the bit.
|
||||
*
|
||||
* Note that the operation is a read-modify-write operation due to the use
|
||||
* of fetch_or.
|
||||
*/
|
||||
bool set(size_t idx, std::memory_order order = std::memory_order_seq_cst);
|
||||
|
||||
/**
|
||||
* Set bit idx to false, using the given memory order. Returns the
|
||||
* previous value of the bit.
|
||||
*
|
||||
* Note that the operation is a read-modify-write operation due to the use
|
||||
* of fetch_and.
|
||||
*/
|
||||
bool reset(size_t idx, std::memory_order order = std::memory_order_seq_cst);
|
||||
|
||||
/**
|
||||
* Set bit idx to the given value, using the given memory order. Returns
|
||||
* the previous value of the bit.
|
||||
*
|
||||
* Note that the operation is a read-modify-write operation due to the use
|
||||
* of fetch_and or fetch_or.
|
||||
*
|
||||
* Yes, this is an overload of set(), to keep as close to std::bitset's
|
||||
* interface as possible.
|
||||
*/
|
||||
bool set(
|
||||
size_t idx,
|
||||
bool value,
|
||||
std::memory_order order = std::memory_order_seq_cst);
|
||||
|
||||
/**
|
||||
* Read bit idx.
|
||||
*/
|
||||
bool test(size_t idx, std::memory_order order = std::memory_order_seq_cst)
|
||||
const;
|
||||
|
||||
/**
|
||||
* Same as test() with the default memory order.
|
||||
*/
|
||||
bool operator[](size_t idx) const;
|
||||
|
||||
/**
|
||||
* Return the size of the bitset.
|
||||
*/
|
||||
constexpr size_t size() const {
|
||||
return N;
|
||||
}
|
||||
|
||||
private:
|
||||
// Pick the largest lock-free type available
|
||||
#if (ATOMIC_LLONG_LOCK_FREE == 2)
|
||||
typedef unsigned long long BlockType;
|
||||
#elif (ATOMIC_LONG_LOCK_FREE == 2)
|
||||
typedef unsigned long BlockType;
|
||||
#else
|
||||
// Even if not lock free, what can we do?
|
||||
typedef unsigned int BlockType;
|
||||
#endif
|
||||
typedef std::atomic<BlockType> AtomicBlockType;
|
||||
|
||||
static constexpr size_t kBitsPerBlock =
|
||||
std::numeric_limits<BlockType>::digits;
|
||||
|
||||
static constexpr size_t blockIndex(size_t bit) {
|
||||
return bit / kBitsPerBlock;
|
||||
}
|
||||
|
||||
static constexpr size_t bitOffset(size_t bit) {
|
||||
return bit % kBitsPerBlock;
|
||||
}
|
||||
|
||||
// avoid casts
|
||||
static constexpr BlockType kOne = 1;
|
||||
|
||||
std::array<AtomicBlockType, N> data_;
|
||||
};
|
||||
|
||||
// value-initialize to zero
|
||||
template <size_t N>
|
||||
inline AtomicBitSet<N>::AtomicBitSet() : data_() {}
|
||||
|
||||
template <size_t N>
|
||||
inline bool AtomicBitSet<N>::set(size_t idx, std::memory_order order) {
|
||||
assert(idx < N * kBitsPerBlock);
|
||||
BlockType mask = kOne << bitOffset(idx);
|
||||
return data_[blockIndex(idx)].fetch_or(mask, order) & mask;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
inline bool AtomicBitSet<N>::reset(size_t idx, std::memory_order order) {
|
||||
assert(idx < N * kBitsPerBlock);
|
||||
BlockType mask = kOne << bitOffset(idx);
|
||||
return data_[blockIndex(idx)].fetch_and(~mask, order) & mask;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
inline bool
|
||||
AtomicBitSet<N>::set(size_t idx, bool value, std::memory_order order) {
|
||||
return value ? set(idx, order) : reset(idx, order);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
inline bool AtomicBitSet<N>::test(size_t idx, std::memory_order order) const {
|
||||
assert(idx < N * kBitsPerBlock);
|
||||
BlockType mask = kOne << bitOffset(idx);
|
||||
return data_[blockIndex(idx)].load(order) & mask;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
inline bool AtomicBitSet<N>::operator[](size_t idx) const {
|
||||
return test(idx);
|
||||
}
|
||||
|
||||
} // namespace folly
|
543
ios/Pods/Folly/folly/AtomicHashArray-inl.h
generated
543
ios/Pods/Folly/folly/AtomicHashArray-inl.h
generated
@ -1,543 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_ATOMICHASHARRAY_H_
|
||||
#error "This should only be included by AtomicHashArray.h"
|
||||
#endif
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <folly/detail/AtomicHashUtils.h>
|
||||
#include <folly/lang/Bits.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
// AtomicHashArray private constructor --
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::
|
||||
AtomicHashArray(
|
||||
size_t capacity,
|
||||
KeyT emptyKey,
|
||||
KeyT lockedKey,
|
||||
KeyT erasedKey,
|
||||
double _maxLoadFactor,
|
||||
uint32_t cacheSize)
|
||||
: capacity_(capacity),
|
||||
maxEntries_(size_t(_maxLoadFactor * capacity_ + 0.5)),
|
||||
kEmptyKey_(emptyKey),
|
||||
kLockedKey_(lockedKey),
|
||||
kErasedKey_(erasedKey),
|
||||
kAnchorMask_(nextPowTwo(capacity_) - 1),
|
||||
numEntries_(0, cacheSize),
|
||||
numPendingEntries_(0, cacheSize),
|
||||
isFull_(0),
|
||||
numErases_(0) {}
|
||||
|
||||
/*
|
||||
* findInternal --
|
||||
*
|
||||
* Sets ret.second to value found and ret.index to index
|
||||
* of key and returns true, or if key does not exist returns false and
|
||||
* ret.index is set to capacity_.
|
||||
*/
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
|
||||
typename AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::SimpleRetT
|
||||
AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::findInternal(const LookupKeyT key_in) {
|
||||
checkLegalKeyIfKey<LookupKeyT>(key_in);
|
||||
|
||||
for (size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in),
|
||||
numProbes = 0;
|
||||
;
|
||||
idx = ProbeFcn()(idx, numProbes, capacity_)) {
|
||||
const KeyT key = acquireLoadKey(cells_[idx]);
|
||||
if (LIKELY(LookupEqualFcn()(key, key_in))) {
|
||||
return SimpleRetT(idx, true);
|
||||
}
|
||||
if (UNLIKELY(key == kEmptyKey_)) {
|
||||
// if we hit an empty element, this key does not exist
|
||||
return SimpleRetT(capacity_, false);
|
||||
}
|
||||
// NOTE: the way we count numProbes must be same in find(), insert(),
|
||||
// and erase(). Otherwise it may break probing.
|
||||
++numProbes;
|
||||
if (UNLIKELY(numProbes >= capacity_)) {
|
||||
// probed every cell...fail
|
||||
return SimpleRetT(capacity_, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* insertInternal --
|
||||
*
|
||||
* Returns false on failure due to key collision or full.
|
||||
* Also sets ret.index to the index of the key. If the map is full, sets
|
||||
* ret.index = capacity_. Also sets ret.second to cell value, thus if insert
|
||||
* successful this will be what we just inserted, if there is a key collision
|
||||
* this will be the previously inserted value, and if the map is full it is
|
||||
* default.
|
||||
*/
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
template <
|
||||
typename LookupKeyT,
|
||||
typename LookupHashFcn,
|
||||
typename LookupEqualFcn,
|
||||
typename LookupKeyToKeyFcn,
|
||||
typename... ArgTs>
|
||||
typename AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::SimpleRetT
|
||||
AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::insertInternal(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
|
||||
const short NO_NEW_INSERTS = 1;
|
||||
const short NO_PENDING_INSERTS = 2;
|
||||
checkLegalKeyIfKey<LookupKeyT>(key_in);
|
||||
|
||||
size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in);
|
||||
size_t numProbes = 0;
|
||||
for (;;) {
|
||||
DCHECK_LT(idx, capacity_);
|
||||
value_type* cell = &cells_[idx];
|
||||
if (relaxedLoadKey(*cell) == kEmptyKey_) {
|
||||
// NOTE: isFull_ is set based on numEntries_.readFast(), so it's
|
||||
// possible to insert more than maxEntries_ entries. However, it's not
|
||||
// possible to insert past capacity_.
|
||||
++numPendingEntries_;
|
||||
if (isFull_.load(std::memory_order_acquire)) {
|
||||
--numPendingEntries_;
|
||||
|
||||
// Before deciding whether this insert succeeded, this thread needs to
|
||||
// wait until no other thread can add a new entry.
|
||||
|
||||
// Correctness assumes isFull_ is true at this point. If
|
||||
// another thread now does ++numPendingEntries_, we expect it
|
||||
// to pass the isFull_.load() test above. (It shouldn't insert
|
||||
// a new entry.)
|
||||
detail::atomic_hash_spin_wait([&] {
|
||||
return (isFull_.load(std::memory_order_acquire) !=
|
||||
NO_PENDING_INSERTS) &&
|
||||
(numPendingEntries_.readFull() != 0);
|
||||
});
|
||||
isFull_.store(NO_PENDING_INSERTS, std::memory_order_release);
|
||||
|
||||
if (relaxedLoadKey(*cell) == kEmptyKey_) {
|
||||
// Don't insert past max load factor
|
||||
return SimpleRetT(capacity_, false);
|
||||
}
|
||||
} else {
|
||||
// An unallocated cell. Try once to lock it. If we succeed, insert here.
|
||||
// If we fail, fall through to comparison below; maybe the insert that
|
||||
// just beat us was for this very key....
|
||||
if (tryLockCell(cell)) {
|
||||
KeyT key_new;
|
||||
// Write the value - done before unlocking
|
||||
try {
|
||||
key_new = LookupKeyToKeyFcn()(key_in);
|
||||
typedef
|
||||
typename std::remove_const<LookupKeyT>::type LookupKeyTNoConst;
|
||||
constexpr bool kAlreadyChecked =
|
||||
std::is_same<KeyT, LookupKeyTNoConst>::value;
|
||||
if (!kAlreadyChecked) {
|
||||
checkLegalKeyIfKey(key_new);
|
||||
}
|
||||
DCHECK(relaxedLoadKey(*cell) == kLockedKey_);
|
||||
// A const mapped_type is only constant once constructed, so cast
|
||||
// away any const for the placement new here.
|
||||
using mapped = typename std::remove_const<mapped_type>::type;
|
||||
new (const_cast<mapped*>(&cell->second))
|
||||
ValueT(std::forward<ArgTs>(vCtorArgs)...);
|
||||
unlockCell(cell, key_new); // Sets the new key
|
||||
} catch (...) {
|
||||
// Transition back to empty key---requires handling
|
||||
// locked->empty below.
|
||||
unlockCell(cell, kEmptyKey_);
|
||||
--numPendingEntries_;
|
||||
throw;
|
||||
}
|
||||
// An erase() can race here and delete right after our insertion
|
||||
// Direct comparison rather than EqualFcn ok here
|
||||
// (we just inserted it)
|
||||
DCHECK(
|
||||
relaxedLoadKey(*cell) == key_new ||
|
||||
relaxedLoadKey(*cell) == kErasedKey_);
|
||||
--numPendingEntries_;
|
||||
++numEntries_; // This is a thread cached atomic increment :)
|
||||
if (numEntries_.readFast() >= maxEntries_) {
|
||||
isFull_.store(NO_NEW_INSERTS, std::memory_order_relaxed);
|
||||
}
|
||||
return SimpleRetT(idx, true);
|
||||
}
|
||||
--numPendingEntries_;
|
||||
}
|
||||
}
|
||||
DCHECK(relaxedLoadKey(*cell) != kEmptyKey_);
|
||||
if (kLockedKey_ == acquireLoadKey(*cell)) {
|
||||
detail::atomic_hash_spin_wait(
|
||||
[&] { return kLockedKey_ == acquireLoadKey(*cell); });
|
||||
}
|
||||
|
||||
const KeyT thisKey = acquireLoadKey(*cell);
|
||||
if (LookupEqualFcn()(thisKey, key_in)) {
|
||||
// Found an existing entry for our key, but we don't overwrite the
|
||||
// previous value.
|
||||
return SimpleRetT(idx, false);
|
||||
} else if (thisKey == kEmptyKey_ || thisKey == kLockedKey_) {
|
||||
// We need to try again (i.e., don't increment numProbes or
|
||||
// advance idx): this case can happen if the constructor for
|
||||
// ValueT threw for this very cell (the rethrow block above).
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: the way we count numProbes must be same in find(),
|
||||
// insert(), and erase(). Otherwise it may break probing.
|
||||
++numProbes;
|
||||
if (UNLIKELY(numProbes >= capacity_)) {
|
||||
// probed every cell...fail
|
||||
return SimpleRetT(capacity_, false);
|
||||
}
|
||||
|
||||
idx = ProbeFcn()(idx, numProbes, capacity_);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* erase --
|
||||
*
|
||||
* This will attempt to erase the given key key_in if the key is found. It
|
||||
* returns 1 iff the key was located and marked as erased, and 0 otherwise.
|
||||
*
|
||||
* Memory is not freed or reclaimed by erase, i.e. the cell containing the
|
||||
* erased key will never be reused. If there's an associated value, we won't
|
||||
* touch it either.
|
||||
*/
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
size_t AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::erase(KeyT key_in) {
|
||||
CHECK_NE(key_in, kEmptyKey_);
|
||||
CHECK_NE(key_in, kLockedKey_);
|
||||
CHECK_NE(key_in, kErasedKey_);
|
||||
|
||||
for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;;
|
||||
idx = ProbeFcn()(idx, numProbes, capacity_)) {
|
||||
DCHECK_LT(idx, capacity_);
|
||||
value_type* cell = &cells_[idx];
|
||||
KeyT currentKey = acquireLoadKey(*cell);
|
||||
if (currentKey == kEmptyKey_ || currentKey == kLockedKey_) {
|
||||
// If we hit an empty (or locked) element, this key does not exist. This
|
||||
// is similar to how it's handled in find().
|
||||
return 0;
|
||||
}
|
||||
if (EqualFcn()(currentKey, key_in)) {
|
||||
// Found an existing entry for our key, attempt to mark it erased.
|
||||
// Some other thread may have erased our key, but this is ok.
|
||||
KeyT expect = currentKey;
|
||||
if (cellKeyPtr(*cell)->compare_exchange_strong(expect, kErasedKey_)) {
|
||||
numErases_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// Even if there's a value in the cell, we won't delete (or even
|
||||
// default construct) it because some other thread may be accessing it.
|
||||
// Locking it meanwhile won't work either since another thread may be
|
||||
// holding a pointer to it.
|
||||
|
||||
// We found the key and successfully erased it.
|
||||
return 1;
|
||||
}
|
||||
// If another thread succeeds in erasing our key, we'll stop our search.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: the way we count numProbes must be same in find(), insert(),
|
||||
// and erase(). Otherwise it may break probing.
|
||||
++numProbes;
|
||||
if (UNLIKELY(numProbes >= capacity_)) {
|
||||
// probed every cell...fail
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
typename AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::SmartPtr
|
||||
AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::create(size_t maxSize, const Config& c) {
|
||||
CHECK_LE(c.maxLoadFactor, 1.0);
|
||||
CHECK_GT(c.maxLoadFactor, 0.0);
|
||||
CHECK_NE(c.emptyKey, c.lockedKey);
|
||||
size_t capacity = size_t(maxSize / c.maxLoadFactor);
|
||||
size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * capacity;
|
||||
|
||||
auto const mem = Allocator().allocate(sz);
|
||||
try {
|
||||
new (mem) AtomicHashArray(
|
||||
capacity,
|
||||
c.emptyKey,
|
||||
c.lockedKey,
|
||||
c.erasedKey,
|
||||
c.maxLoadFactor,
|
||||
c.entryCountThreadCacheSize);
|
||||
} catch (...) {
|
||||
Allocator().deallocate(mem, sz);
|
||||
throw;
|
||||
}
|
||||
|
||||
SmartPtr map(static_cast<AtomicHashArray*>((void*)mem));
|
||||
|
||||
/*
|
||||
* Mark all cells as empty.
|
||||
*
|
||||
* Note: we're bending the rules a little here accessing the key
|
||||
* element in our cells even though the cell object has not been
|
||||
* constructed, and casting them to atomic objects (see cellKeyPtr).
|
||||
* (Also, in fact we never actually invoke the value_type
|
||||
* constructor.) This is in order to avoid needing to default
|
||||
* construct a bunch of value_type when we first start up: if you
|
||||
* have an expensive default constructor for the value type this can
|
||||
* noticeably speed construction time for an AHA.
|
||||
*/
|
||||
FOR_EACH_RANGE (i, 0, map->capacity_) {
|
||||
cellKeyPtr(map->cells_[i])
|
||||
->store(map->kEmptyKey_, std::memory_order_relaxed);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
void AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::destroy(AtomicHashArray* p) {
|
||||
assert(p);
|
||||
|
||||
size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * p->capacity_;
|
||||
|
||||
FOR_EACH_RANGE (i, 0, p->capacity_) {
|
||||
if (p->cells_[i].first != p->kEmptyKey_) {
|
||||
p->cells_[i].~value_type();
|
||||
}
|
||||
}
|
||||
p->~AtomicHashArray();
|
||||
|
||||
Allocator().deallocate((char*)p, sz);
|
||||
}
|
||||
|
||||
// clear -- clears all keys and values in the map and resets all counters
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
void AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::clear() {
|
||||
FOR_EACH_RANGE (i, 0, capacity_) {
|
||||
if (cells_[i].first != kEmptyKey_) {
|
||||
cells_[i].~value_type();
|
||||
*const_cast<KeyT*>(&cells_[i].first) = kEmptyKey_;
|
||||
}
|
||||
CHECK(cells_[i].first == kEmptyKey_);
|
||||
}
|
||||
numEntries_.set(0);
|
||||
numPendingEntries_.set(0);
|
||||
isFull_.store(0, std::memory_order_relaxed);
|
||||
numErases_.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Iterator implementation
|
||||
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
template <class ContT, class IterVal>
|
||||
struct AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::aha_iterator
|
||||
: boost::iterator_facade<
|
||||
aha_iterator<ContT, IterVal>,
|
||||
IterVal,
|
||||
boost::forward_traversal_tag> {
|
||||
explicit aha_iterator() : aha_(nullptr) {}
|
||||
|
||||
// Conversion ctor for interoperability between const_iterator and
|
||||
// iterator. The enable_if<> magic keeps us well-behaved for
|
||||
// is_convertible<> (v. the iterator_facade documentation).
|
||||
template <class OtherContT, class OtherVal>
|
||||
aha_iterator(
|
||||
const aha_iterator<OtherContT, OtherVal>& o,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<OtherVal*, IterVal*>::value>::type* = nullptr)
|
||||
: aha_(o.aha_), offset_(o.offset_) {}
|
||||
|
||||
explicit aha_iterator(ContT* array, size_t offset)
|
||||
: aha_(array), offset_(offset) {}
|
||||
|
||||
// Returns unique index that can be used with findAt().
|
||||
// WARNING: The following function will fail silently for hashtable
|
||||
// with capacity > 2^32
|
||||
uint32_t getIndex() const {
|
||||
return offset_;
|
||||
}
|
||||
|
||||
void advancePastEmpty() {
|
||||
while (offset_ < aha_->capacity_ && !isValid()) {
|
||||
++offset_;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AtomicHashArray;
|
||||
friend class boost::iterator_core_access;
|
||||
|
||||
void increment() {
|
||||
++offset_;
|
||||
advancePastEmpty();
|
||||
}
|
||||
|
||||
bool equal(const aha_iterator& o) const {
|
||||
return aha_ == o.aha_ && offset_ == o.offset_;
|
||||
}
|
||||
|
||||
IterVal& dereference() const {
|
||||
return aha_->cells_[offset_];
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
KeyT key = acquireLoadKey(aha_->cells_[offset_]);
|
||||
return key != aha_->kEmptyKey_ && key != aha_->kLockedKey_ &&
|
||||
key != aha_->kErasedKey_;
|
||||
}
|
||||
|
||||
private:
|
||||
ContT* aha_;
|
||||
size_t offset_;
|
||||
}; // aha_iterator
|
||||
|
||||
} // namespace folly
|
448
ios/Pods/Folly/folly/AtomicHashArray.h
generated
448
ios/Pods/Folly/folly/AtomicHashArray.h
generated
@ -1,448 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* AtomicHashArray is the building block for AtomicHashMap. It provides the
|
||||
* core lock-free functionality, but is limited by the fact that it cannot
|
||||
* grow past its initialization size and is a little more awkward (no public
|
||||
* constructor, for example). If you're confident that you won't run out of
|
||||
* space, don't mind the awkardness, and really need bare-metal performance,
|
||||
* feel free to use AHA directly.
|
||||
*
|
||||
* Check out AtomicHashMap.h for more thorough documentation on perf and
|
||||
* general pros and cons relative to other hash maps.
|
||||
*
|
||||
* @author Spencer Ahrens <sahrens@fb.com>
|
||||
* @author Jordan DeLong <delong.j@fb.com>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#define FOLLY_ATOMICHASHARRAY_H_
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <folly/ThreadCachedInt.h>
|
||||
#include <folly/Utility.h>
|
||||
#include <folly/hash/Hash.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
struct AtomicHashArrayLinearProbeFcn {
|
||||
inline size_t operator()(size_t idx, size_t /* numProbes */, size_t capacity)
|
||||
const {
|
||||
idx += 1; // linear probing
|
||||
|
||||
// Avoid modulus because it's slow
|
||||
return LIKELY(idx < capacity) ? idx : (idx - capacity);
|
||||
}
|
||||
};
|
||||
|
||||
struct AtomicHashArrayQuadraticProbeFcn {
|
||||
inline size_t operator()(size_t idx, size_t numProbes, size_t capacity)
|
||||
const {
|
||||
idx += numProbes; // quadratic probing
|
||||
|
||||
// Avoid modulus because it's slow
|
||||
return LIKELY(idx < capacity) ? idx : (idx - capacity);
|
||||
}
|
||||
};
|
||||
|
||||
// Enables specializing checkLegalKey without specializing its class.
|
||||
namespace detail {
|
||||
template <typename NotKeyT, typename KeyT>
|
||||
inline void checkLegalKeyIfKeyTImpl(
|
||||
NotKeyT /* ignored */,
|
||||
KeyT /* emptyKey */,
|
||||
KeyT /* lockedKey */,
|
||||
KeyT /* erasedKey */) {}
|
||||
|
||||
template <typename KeyT>
|
||||
inline void checkLegalKeyIfKeyTImpl(
|
||||
KeyT key_in,
|
||||
KeyT emptyKey,
|
||||
KeyT lockedKey,
|
||||
KeyT erasedKey) {
|
||||
DCHECK_NE(key_in, emptyKey);
|
||||
DCHECK_NE(key_in, lockedKey);
|
||||
DCHECK_NE(key_in, erasedKey);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn = std::hash<KeyT>,
|
||||
class EqualFcn = std::equal_to<KeyT>,
|
||||
class Allocator = std::allocator<char>,
|
||||
class ProbeFcn = AtomicHashArrayLinearProbeFcn,
|
||||
class KeyConvertFcn = Identity>
|
||||
class AtomicHashMap;
|
||||
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn = std::hash<KeyT>,
|
||||
class EqualFcn = std::equal_to<KeyT>,
|
||||
class Allocator = std::allocator<char>,
|
||||
class ProbeFcn = AtomicHashArrayLinearProbeFcn,
|
||||
class KeyConvertFcn = Identity>
|
||||
class AtomicHashArray : boost::noncopyable {
|
||||
static_assert(
|
||||
(std::is_convertible<KeyT, int32_t>::value ||
|
||||
std::is_convertible<KeyT, int64_t>::value ||
|
||||
std::is_convertible<KeyT, const void*>::value),
|
||||
"You are trying to use AtomicHashArray with disallowed key "
|
||||
"types. You must use atomically compare-and-swappable integer "
|
||||
"keys, or a different container class.");
|
||||
|
||||
public:
|
||||
typedef KeyT key_type;
|
||||
typedef ValueT mapped_type;
|
||||
typedef HashFcn hasher;
|
||||
typedef EqualFcn key_equal;
|
||||
typedef KeyConvertFcn key_convert;
|
||||
typedef std::pair<const KeyT, ValueT> value_type;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
typedef value_type* pointer;
|
||||
typedef const value_type* const_pointer;
|
||||
|
||||
const size_t capacity_;
|
||||
const size_t maxEntries_;
|
||||
const KeyT kEmptyKey_;
|
||||
const KeyT kLockedKey_;
|
||||
const KeyT kErasedKey_;
|
||||
|
||||
template <class ContT, class IterVal>
|
||||
struct aha_iterator;
|
||||
|
||||
typedef aha_iterator<const AtomicHashArray, const value_type> const_iterator;
|
||||
typedef aha_iterator<AtomicHashArray, value_type> iterator;
|
||||
|
||||
// You really shouldn't need this if you use the SmartPtr provided by create,
|
||||
// but if you really want to do something crazy like stick the released
|
||||
// pointer into a DescriminatedPtr or something, you'll need this to clean up
|
||||
// after yourself.
|
||||
static void destroy(AtomicHashArray*);
|
||||
|
||||
private:
|
||||
const size_t kAnchorMask_;
|
||||
|
||||
struct Deleter {
|
||||
void operator()(AtomicHashArray* ptr) {
|
||||
AtomicHashArray::destroy(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
typedef std::unique_ptr<AtomicHashArray, Deleter> SmartPtr;
|
||||
|
||||
/*
|
||||
* create --
|
||||
*
|
||||
* Creates AtomicHashArray objects. Use instead of constructor/destructor.
|
||||
*
|
||||
* We do things this way in order to avoid the perf penalty of a second
|
||||
* pointer indirection when composing these into AtomicHashMap, which needs
|
||||
* to store an array of pointers so that it can perform atomic operations on
|
||||
* them when growing.
|
||||
*
|
||||
* Instead of a mess of arguments, we take a max size and a Config struct to
|
||||
* simulate named ctor parameters. The Config struct has sensible defaults
|
||||
* for everything, but is overloaded - if you specify a positive capacity,
|
||||
* that will be used directly instead of computing it based on
|
||||
* maxLoadFactor.
|
||||
*
|
||||
* Create returns an AHA::SmartPtr which is a unique_ptr with a custom
|
||||
* deleter to make sure everything is cleaned up properly.
|
||||
*/
|
||||
struct Config {
|
||||
KeyT emptyKey;
|
||||
KeyT lockedKey;
|
||||
KeyT erasedKey;
|
||||
double maxLoadFactor;
|
||||
double growthFactor;
|
||||
uint32_t entryCountThreadCacheSize;
|
||||
size_t capacity; // if positive, overrides maxLoadFactor
|
||||
|
||||
// Cannot have constexpr ctor because some compilers rightly complain.
|
||||
Config()
|
||||
: emptyKey((KeyT)-1),
|
||||
lockedKey((KeyT)-2),
|
||||
erasedKey((KeyT)-3),
|
||||
maxLoadFactor(0.8),
|
||||
growthFactor(-1),
|
||||
entryCountThreadCacheSize(1000),
|
||||
capacity(0) {}
|
||||
};
|
||||
|
||||
// Cannot have pre-instantiated const Config instance because of SIOF.
|
||||
static SmartPtr create(size_t maxSize, const Config& c = Config());
|
||||
|
||||
/*
|
||||
* find --
|
||||
*
|
||||
*
|
||||
* Returns the iterator to the element if found, otherwise end().
|
||||
*
|
||||
* As an optional feature, the type of the key to look up (LookupKeyT) is
|
||||
* allowed to be different from the type of keys actually stored (KeyT).
|
||||
*
|
||||
* This enables use cases where materializing the key is costly and usually
|
||||
* redudant, e.g., canonicalizing/interning a set of strings and being able
|
||||
* to look up by StringPiece. To use this feature, LookupHashFcn must take
|
||||
* a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first
|
||||
* and second parameter, respectively.
|
||||
*
|
||||
* See folly/test/ArrayHashArrayTest.cpp for sample usage.
|
||||
*/
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal>
|
||||
iterator find(LookupKeyT k) {
|
||||
return iterator(
|
||||
this, findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k).idx);
|
||||
}
|
||||
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal>
|
||||
const_iterator find(LookupKeyT k) const {
|
||||
return const_cast<AtomicHashArray*>(this)
|
||||
->find<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
|
||||
}
|
||||
|
||||
/*
|
||||
* insert --
|
||||
*
|
||||
* Returns a pair with iterator to the element at r.first and bool success.
|
||||
* Retrieve the index with ret.first.getIndex().
|
||||
*
|
||||
* Fails on key collision (does not overwrite) or if map becomes
|
||||
* full, at which point no element is inserted, iterator is set to end(),
|
||||
* and success is set false. On collisions, success is set false, but the
|
||||
* iterator is set to the existing entry.
|
||||
*/
|
||||
std::pair<iterator, bool> insert(const value_type& r) {
|
||||
return emplace(r.first, r.second);
|
||||
}
|
||||
std::pair<iterator, bool> insert(value_type&& r) {
|
||||
return emplace(r.first, std::move(r.second));
|
||||
}
|
||||
|
||||
/*
|
||||
* emplace --
|
||||
*
|
||||
* Same contract as insert(), but performs in-place construction
|
||||
* of the value type using the specified arguments.
|
||||
*
|
||||
* Also, like find(), this method optionally allows 'key_in' to have a type
|
||||
* different from that stored in the table; see find(). If and only if no
|
||||
* equal key is already present, this method converts 'key_in' to a key of
|
||||
* type KeyT using the provided LookupKeyToKeyFcn.
|
||||
*/
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal,
|
||||
typename LookupKeyToKeyFcn = key_convert,
|
||||
typename... ArgTs>
|
||||
std::pair<iterator, bool> emplace(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
|
||||
SimpleRetT ret = insertInternal<
|
||||
LookupKeyT,
|
||||
LookupHashFcn,
|
||||
LookupEqualFcn,
|
||||
LookupKeyToKeyFcn>(key_in, std::forward<ArgTs>(vCtorArgs)...);
|
||||
return std::make_pair(iterator(this, ret.idx), ret.success);
|
||||
}
|
||||
|
||||
// returns the number of elements erased - should never exceed 1
|
||||
size_t erase(KeyT k);
|
||||
|
||||
// clears all keys and values in the map and resets all counters. Not thread
|
||||
// safe.
|
||||
void clear();
|
||||
|
||||
// Exact number of elements in the map - note that readFull() acquires a
|
||||
// mutex. See folly/ThreadCachedInt.h for more details.
|
||||
size_t size() const {
|
||||
return numEntries_.readFull() - numErases_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
iterator begin() {
|
||||
iterator it(this, 0);
|
||||
it.advancePastEmpty();
|
||||
return it;
|
||||
}
|
||||
const_iterator begin() const {
|
||||
const_iterator it(this, 0);
|
||||
it.advancePastEmpty();
|
||||
return it;
|
||||
}
|
||||
|
||||
iterator end() {
|
||||
return iterator(this, capacity_);
|
||||
}
|
||||
const_iterator end() const {
|
||||
return const_iterator(this, capacity_);
|
||||
}
|
||||
|
||||
// See AtomicHashMap::findAt - access elements directly
|
||||
// WARNING: The following 2 functions will fail silently for hashtable
|
||||
// with capacity > 2^32
|
||||
iterator findAt(uint32_t idx) {
|
||||
DCHECK_LT(idx, capacity_);
|
||||
return iterator(this, idx);
|
||||
}
|
||||
const_iterator findAt(uint32_t idx) const {
|
||||
return const_cast<AtomicHashArray*>(this)->findAt(idx);
|
||||
}
|
||||
|
||||
iterator makeIter(size_t idx) {
|
||||
return iterator(this, idx);
|
||||
}
|
||||
const_iterator makeIter(size_t idx) const {
|
||||
return const_iterator(this, idx);
|
||||
}
|
||||
|
||||
// The max load factor allowed for this map
|
||||
double maxLoadFactor() const {
|
||||
return ((double)maxEntries_) / capacity_;
|
||||
}
|
||||
|
||||
void setEntryCountThreadCacheSize(uint32_t newSize) {
|
||||
numEntries_.setCacheSize(newSize);
|
||||
numPendingEntries_.setCacheSize(newSize);
|
||||
}
|
||||
|
||||
uint32_t getEntryCountThreadCacheSize() const {
|
||||
return numEntries_.getCacheSize();
|
||||
}
|
||||
|
||||
/* Private data and helper functions... */
|
||||
|
||||
private:
|
||||
friend class AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn>;
|
||||
|
||||
struct SimpleRetT {
|
||||
size_t idx;
|
||||
bool success;
|
||||
SimpleRetT(size_t i, bool s) : idx(i), success(s) {}
|
||||
SimpleRetT() = default;
|
||||
};
|
||||
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal,
|
||||
typename LookupKeyToKeyFcn = Identity,
|
||||
typename... ArgTs>
|
||||
SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs);
|
||||
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal>
|
||||
SimpleRetT findInternal(const LookupKeyT key);
|
||||
|
||||
template <typename MaybeKeyT>
|
||||
void checkLegalKeyIfKey(MaybeKeyT key) {
|
||||
detail::checkLegalKeyIfKeyTImpl(key, kEmptyKey_, kLockedKey_, kErasedKey_);
|
||||
}
|
||||
|
||||
static std::atomic<KeyT>* cellKeyPtr(const value_type& r) {
|
||||
// We need some illegal casting here in order to actually store
|
||||
// our value_type as a std::pair<const,>. But a little bit of
|
||||
// undefined behavior never hurt anyone ...
|
||||
static_assert(
|
||||
sizeof(std::atomic<KeyT>) == sizeof(KeyT),
|
||||
"std::atomic is implemented in an unexpected way for AHM");
|
||||
return const_cast<std::atomic<KeyT>*>(
|
||||
reinterpret_cast<std::atomic<KeyT> const*>(&r.first));
|
||||
}
|
||||
|
||||
static KeyT relaxedLoadKey(const value_type& r) {
|
||||
return cellKeyPtr(r)->load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
static KeyT acquireLoadKey(const value_type& r) {
|
||||
return cellKeyPtr(r)->load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
// Fun with thread local storage - atomic increment is expensive
|
||||
// (relatively), so we accumulate in the thread cache and periodically
|
||||
// flush to the actual variable, and walk through the unflushed counts when
|
||||
// reading the value, so be careful of calling size() too frequently. This
|
||||
// increases insertion throughput several times over while keeping the count
|
||||
// accurate.
|
||||
ThreadCachedInt<uint64_t> numEntries_; // Successful key inserts
|
||||
ThreadCachedInt<uint64_t> numPendingEntries_; // Used by insertInternal
|
||||
std::atomic<int64_t> isFull_; // Used by insertInternal
|
||||
std::atomic<int64_t> numErases_; // Successful key erases
|
||||
|
||||
value_type cells_[0]; // This must be the last field of this class
|
||||
|
||||
// Force constructor/destructor private since create/destroy should be
|
||||
// used externally instead
|
||||
AtomicHashArray(
|
||||
size_t capacity,
|
||||
KeyT emptyKey,
|
||||
KeyT lockedKey,
|
||||
KeyT erasedKey,
|
||||
double maxLoadFactor,
|
||||
uint32_t cacheSize);
|
||||
|
||||
~AtomicHashArray() = default;
|
||||
|
||||
inline void unlockCell(value_type* const cell, KeyT newKey) {
|
||||
cellKeyPtr(*cell)->store(newKey, std::memory_order_release);
|
||||
}
|
||||
|
||||
inline bool tryLockCell(value_type* const cell) {
|
||||
KeyT expect = kEmptyKey_;
|
||||
return cellKeyPtr(*cell)->compare_exchange_strong(
|
||||
expect, kLockedKey_, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
template <class LookupKeyT = key_type, class LookupHashFcn = hasher>
|
||||
inline size_t keyToAnchorIdx(const LookupKeyT k) const {
|
||||
const size_t hashVal = LookupHashFcn()(k);
|
||||
const size_t probe = hashVal & kAnchorMask_;
|
||||
return LIKELY(probe < capacity_) ? probe : hashVal % capacity_;
|
||||
}
|
||||
|
||||
}; // AtomicHashArray
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#include <folly/AtomicHashArray-inl.h>
|
653
ios/Pods/Folly/folly/AtomicHashMap-inl.h
generated
653
ios/Pods/Folly/folly/AtomicHashMap-inl.h
generated
@ -1,653 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FOLLY_ATOMICHASHMAP_H_
|
||||
#error "This should only be included by AtomicHashMap.h"
|
||||
#endif
|
||||
|
||||
#include <folly/detail/AtomicHashUtils.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
// AtomicHashMap constructor -- Atomic wrapper that allows growth
|
||||
// This class has a lot of overhead (184 Bytes) so only use for big maps
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::AtomicHashMap(size_t finalSizeEst, const Config& config)
|
||||
: kGrowthFrac_(
|
||||
config.growthFactor < 0 ? 1.0f - config.maxLoadFactor
|
||||
: config.growthFactor) {
|
||||
CHECK(config.maxLoadFactor > 0.0f && config.maxLoadFactor < 1.0f);
|
||||
subMaps_[0].store(
|
||||
SubMap::create(finalSizeEst, config).release(),
|
||||
std::memory_order_relaxed);
|
||||
auto subMapCount = kNumSubMaps_;
|
||||
FOR_EACH_RANGE (i, 1, subMapCount) {
|
||||
subMaps_[i].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
numMapsAllocated_.store(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// emplace --
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
template <
|
||||
typename LookupKeyT,
|
||||
typename LookupHashFcn,
|
||||
typename LookupEqualFcn,
|
||||
typename LookupKeyToKeyFcn,
|
||||
typename... ArgTs>
|
||||
std::pair<
|
||||
typename AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::iterator,
|
||||
bool>
|
||||
AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::emplace(LookupKeyT k, ArgTs&&... vCtorArgs) {
|
||||
SimpleRetT ret = insertInternal<
|
||||
LookupKeyT,
|
||||
LookupHashFcn,
|
||||
LookupEqualFcn,
|
||||
LookupKeyToKeyFcn>(k, std::forward<ArgTs>(vCtorArgs)...);
|
||||
SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
|
||||
return std::make_pair(
|
||||
iterator(this, ret.i, subMap->makeIter(ret.j)), ret.success);
|
||||
}
|
||||
|
||||
// insertInternal -- Allocates new sub maps as existing ones fill up.
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
template <
|
||||
typename LookupKeyT,
|
||||
typename LookupHashFcn,
|
||||
typename LookupEqualFcn,
|
||||
typename LookupKeyToKeyFcn,
|
||||
typename... ArgTs>
|
||||
typename AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::SimpleRetT
|
||||
AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs) {
|
||||
beginInsertInternal:
|
||||
auto nextMapIdx = // this maintains our state
|
||||
numMapsAllocated_.load(std::memory_order_acquire);
|
||||
typename SubMap::SimpleRetT ret;
|
||||
FOR_EACH_RANGE (i, 0, nextMapIdx) {
|
||||
// insert in each map successively. If one succeeds, we're done!
|
||||
SubMap* subMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
ret = subMap->template insertInternal<
|
||||
LookupKeyT,
|
||||
LookupHashFcn,
|
||||
LookupEqualFcn,
|
||||
LookupKeyToKeyFcn>(key, std::forward<ArgTs>(vCtorArgs)...);
|
||||
if (ret.idx == subMap->capacity_) {
|
||||
continue; // map is full, so try the next one
|
||||
}
|
||||
// Either collision or success - insert in either case
|
||||
return SimpleRetT(i, ret.idx, ret.success);
|
||||
}
|
||||
|
||||
// If we made it this far, all maps are full and we need to try to allocate
|
||||
// the next one.
|
||||
|
||||
SubMap* primarySubMap = subMaps_[0].load(std::memory_order_relaxed);
|
||||
if (nextMapIdx >= kNumSubMaps_ ||
|
||||
primarySubMap->capacity_ * kGrowthFrac_ < 1.0) {
|
||||
// Can't allocate any more sub maps.
|
||||
throw AtomicHashMapFullError();
|
||||
}
|
||||
|
||||
if (tryLockMap(nextMapIdx)) {
|
||||
// Alloc a new map and shove it in. We can change whatever
|
||||
// we want because other threads are waiting on us...
|
||||
size_t numCellsAllocated = (size_t)(
|
||||
primarySubMap->capacity_ *
|
||||
std::pow(1.0 + kGrowthFrac_, nextMapIdx - 1));
|
||||
size_t newSize = size_t(numCellsAllocated * kGrowthFrac_);
|
||||
DCHECK(
|
||||
subMaps_[nextMapIdx].load(std::memory_order_relaxed) ==
|
||||
(SubMap*)kLockedPtr_);
|
||||
// create a new map using the settings stored in the first map
|
||||
|
||||
Config config;
|
||||
config.emptyKey = primarySubMap->kEmptyKey_;
|
||||
config.lockedKey = primarySubMap->kLockedKey_;
|
||||
config.erasedKey = primarySubMap->kErasedKey_;
|
||||
config.maxLoadFactor = primarySubMap->maxLoadFactor();
|
||||
config.entryCountThreadCacheSize =
|
||||
primarySubMap->getEntryCountThreadCacheSize();
|
||||
subMaps_[nextMapIdx].store(
|
||||
SubMap::create(newSize, config).release(), std::memory_order_relaxed);
|
||||
|
||||
// Publish the map to other threads.
|
||||
numMapsAllocated_.fetch_add(1, std::memory_order_release);
|
||||
DCHECK_EQ(
|
||||
nextMapIdx + 1, numMapsAllocated_.load(std::memory_order_relaxed));
|
||||
} else {
|
||||
// If we lost the race, we'll have to wait for the next map to get
|
||||
// allocated before doing any insertion here.
|
||||
detail::atomic_hash_spin_wait([&] {
|
||||
return nextMapIdx >= numMapsAllocated_.load(std::memory_order_acquire);
|
||||
});
|
||||
}
|
||||
|
||||
// Relaxed is ok here because either we just created this map, or we
|
||||
// just did a spin wait with an acquire load on numMapsAllocated_.
|
||||
SubMap* loadedMap = subMaps_[nextMapIdx].load(std::memory_order_relaxed);
|
||||
DCHECK(loadedMap && loadedMap != (SubMap*)kLockedPtr_);
|
||||
ret = loadedMap->insertInternal(key, std::forward<ArgTs>(vCtorArgs)...);
|
||||
if (ret.idx != loadedMap->capacity_) {
|
||||
return SimpleRetT(nextMapIdx, ret.idx, ret.success);
|
||||
}
|
||||
// We took way too long and the new map is already full...try again from
|
||||
// the top (this should pretty much never happen).
|
||||
goto beginInsertInternal;
|
||||
}
|
||||
|
||||
// find --
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
|
||||
typename AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::iterator
|
||||
AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::find(LookupKeyT k) {
|
||||
SimpleRetT ret = findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
|
||||
if (!ret.success) {
|
||||
return end();
|
||||
}
|
||||
SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
|
||||
return iterator(this, ret.i, subMap->makeIter(ret.j));
|
||||
}
|
||||
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
|
||||
typename AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::const_iterator
|
||||
AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::find(LookupKeyT k) const {
|
||||
return const_cast<AtomicHashMap*>(this)
|
||||
->find<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
|
||||
}
|
||||
|
||||
// findInternal --
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
|
||||
typename AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::SimpleRetT
|
||||
AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::findInternal(const LookupKeyT k) const {
|
||||
SubMap* const primaryMap = subMaps_[0].load(std::memory_order_relaxed);
|
||||
typename SubMap::SimpleRetT ret =
|
||||
primaryMap
|
||||
->template findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
|
||||
if (LIKELY(ret.idx != primaryMap->capacity_)) {
|
||||
return SimpleRetT(0, ret.idx, ret.success);
|
||||
}
|
||||
const unsigned int numMaps =
|
||||
numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE (i, 1, numMaps) {
|
||||
// Check each map successively. If one succeeds, we're done!
|
||||
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
ret =
|
||||
thisMap
|
||||
->template findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(
|
||||
k);
|
||||
if (LIKELY(ret.idx != thisMap->capacity_)) {
|
||||
return SimpleRetT(i, ret.idx, ret.success);
|
||||
}
|
||||
}
|
||||
// Didn't find our key...
|
||||
return SimpleRetT(numMaps, 0, false);
|
||||
}
|
||||
|
||||
// findAtInternal -- see encodeIndex() for details.
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
typename AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::SimpleRetT
|
||||
AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::findAtInternal(uint32_t idx) const {
|
||||
uint32_t subMapIdx, subMapOffset;
|
||||
if (idx & kSecondaryMapBit_) {
|
||||
// idx falls in a secondary map
|
||||
idx &= ~kSecondaryMapBit_; // unset secondary bit
|
||||
subMapIdx = idx >> kSubMapIndexShift_;
|
||||
DCHECK_LT(subMapIdx, numMapsAllocated_.load(std::memory_order_relaxed));
|
||||
subMapOffset = idx & kSubMapIndexMask_;
|
||||
} else {
|
||||
// idx falls in primary map
|
||||
subMapIdx = 0;
|
||||
subMapOffset = idx;
|
||||
}
|
||||
return SimpleRetT(subMapIdx, subMapOffset, true);
|
||||
}
|
||||
|
||||
// erase --
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
typename AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::size_type
|
||||
AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::erase(const KeyT k) {
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE (i, 0, numMaps) {
|
||||
// Check each map successively. If one succeeds, we're done!
|
||||
if (subMaps_[i].load(std::memory_order_relaxed)->erase(k)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Didn't find our key...
|
||||
return 0;
|
||||
}
|
||||
|
||||
// capacity -- summation of capacities of all submaps
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
size_t AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::capacity() const {
|
||||
size_t totalCap(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE (i, 0, numMaps) {
|
||||
totalCap += subMaps_[i].load(std::memory_order_relaxed)->capacity_;
|
||||
}
|
||||
return totalCap;
|
||||
}
|
||||
|
||||
// spaceRemaining --
|
||||
// number of new insertions until current submaps are all at max load
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
size_t AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::spaceRemaining() const {
|
||||
size_t spaceRem(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE (i, 0, numMaps) {
|
||||
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
spaceRem +=
|
||||
std::max(0, thisMap->maxEntries_ - &thisMap->numEntries_.readFull());
|
||||
}
|
||||
return spaceRem;
|
||||
}
|
||||
|
||||
// clear -- Wipes all keys and values from primary map and destroys
|
||||
// all secondary maps. Not thread safe.
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
void AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::clear() {
|
||||
subMaps_[0].load(std::memory_order_relaxed)->clear();
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_relaxed);
|
||||
FOR_EACH_RANGE (i, 1, numMaps) {
|
||||
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
DCHECK(thisMap);
|
||||
SubMap::destroy(thisMap);
|
||||
subMaps_[i].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
numMapsAllocated_.store(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// size --
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
size_t AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::size() const {
|
||||
size_t totalSize(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE (i, 0, numMaps) {
|
||||
totalSize += subMaps_[i].load(std::memory_order_relaxed)->size();
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
// encodeIndex -- Encode the submap index and offset into return.
|
||||
// index_ret must be pre-populated with the submap offset.
|
||||
//
|
||||
// We leave index_ret untouched when referring to the primary map
|
||||
// so it can be as large as possible (31 data bits). Max size of
|
||||
// secondary maps is limited by what can fit in the low 27 bits.
|
||||
//
|
||||
// Returns the following bit-encoded data in index_ret:
|
||||
// if subMap == 0 (primary map) =>
|
||||
// bit(s) value
|
||||
// 31 0
|
||||
// 0-30 submap offset (index_ret input)
|
||||
//
|
||||
// if subMap > 0 (secondary maps) =>
|
||||
// bit(s) value
|
||||
// 31 1
|
||||
// 27-30 which subMap
|
||||
// 0-26 subMap offset (index_ret input)
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
inline uint32_t AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::encodeIndex(uint32_t subMap, uint32_t offset) {
|
||||
DCHECK_EQ(offset & kSecondaryMapBit_, 0); // offset can't be too big
|
||||
if (subMap == 0) {
|
||||
return offset;
|
||||
}
|
||||
// Make sure subMap isn't too big
|
||||
DCHECK_EQ(subMap >> kNumSubMapBits_, 0);
|
||||
// Make sure subMap bits of offset are clear
|
||||
DCHECK_EQ(offset & (~kSubMapIndexMask_ | kSecondaryMapBit_), 0);
|
||||
|
||||
// Set high-order bits to encode which submap this index belongs to
|
||||
return offset | (subMap << kSubMapIndexShift_) | kSecondaryMapBit_;
|
||||
}
|
||||
|
||||
// Iterator implementation
|
||||
|
||||
template <
|
||||
typename KeyT,
|
||||
typename ValueT,
|
||||
typename HashFcn,
|
||||
typename EqualFcn,
|
||||
typename Allocator,
|
||||
typename ProbeFcn,
|
||||
typename KeyConvertFcn>
|
||||
template <class ContT, class IterVal, class SubIt>
|
||||
struct AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>::ahm_iterator
|
||||
: boost::iterator_facade<
|
||||
ahm_iterator<ContT, IterVal, SubIt>,
|
||||
IterVal,
|
||||
boost::forward_traversal_tag> {
|
||||
explicit ahm_iterator() : ahm_(nullptr) {}
|
||||
|
||||
// Conversion ctor for interoperability between const_iterator and
|
||||
// iterator. The enable_if<> magic keeps us well-behaved for
|
||||
// is_convertible<> (v. the iterator_facade documentation).
|
||||
template <class OtherContT, class OtherVal, class OtherSubIt>
|
||||
ahm_iterator(
|
||||
const ahm_iterator<OtherContT, OtherVal, OtherSubIt>& o,
|
||||
typename std::enable_if<
|
||||
std::is_convertible<OtherSubIt, SubIt>::value>::type* = nullptr)
|
||||
: ahm_(o.ahm_), subMap_(o.subMap_), subIt_(o.subIt_) {}
|
||||
|
||||
/*
|
||||
* Returns the unique index that can be used for access directly
|
||||
* into the data storage.
|
||||
*/
|
||||
uint32_t getIndex() const {
|
||||
CHECK(!isEnd());
|
||||
return ahm_->encodeIndex(subMap_, subIt_.getIndex());
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AtomicHashMap;
|
||||
explicit ahm_iterator(ContT* ahm, uint32_t subMap, const SubIt& subIt)
|
||||
: ahm_(ahm), subMap_(subMap), subIt_(subIt) {}
|
||||
|
||||
friend class boost::iterator_core_access;
|
||||
|
||||
void increment() {
|
||||
CHECK(!isEnd());
|
||||
++subIt_;
|
||||
checkAdvanceToNextSubmap();
|
||||
}
|
||||
|
||||
bool equal(const ahm_iterator& other) const {
|
||||
if (ahm_ != other.ahm_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isEnd() || other.isEnd()) {
|
||||
return isEnd() == other.isEnd();
|
||||
}
|
||||
|
||||
return subMap_ == other.subMap_ && subIt_ == other.subIt_;
|
||||
}
|
||||
|
||||
IterVal& dereference() const {
|
||||
return *subIt_;
|
||||
}
|
||||
|
||||
bool isEnd() const {
|
||||
return ahm_ == nullptr;
|
||||
}
|
||||
|
||||
void checkAdvanceToNextSubmap() {
|
||||
if (isEnd()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SubMap* thisMap = ahm_->subMaps_[subMap_].load(std::memory_order_relaxed);
|
||||
while (subIt_ == thisMap->end()) {
|
||||
// This sub iterator is done, advance to next one
|
||||
if (subMap_ + 1 <
|
||||
ahm_->numMapsAllocated_.load(std::memory_order_acquire)) {
|
||||
++subMap_;
|
||||
thisMap = ahm_->subMaps_[subMap_].load(std::memory_order_relaxed);
|
||||
subIt_ = thisMap->begin();
|
||||
} else {
|
||||
ahm_ = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ContT* ahm_;
|
||||
uint32_t subMap_;
|
||||
SubIt subIt_;
|
||||
}; // ahm_iterator
|
||||
|
||||
} // namespace folly
|
500
ios/Pods/Folly/folly/AtomicHashMap.h
generated
500
ios/Pods/Folly/folly/AtomicHashMap.h
generated
@ -1,500 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
* AtomicHashMap --
|
||||
*
|
||||
* A high-performance concurrent hash map with int32 or int64 keys. Supports
|
||||
* insert, find(key), findAt(index), erase(key), size, and more. Memory cannot
|
||||
* be freed or reclaimed by erase. Can grow to a maximum of about 18 times the
|
||||
* initial capacity, but performance degrades linearly with growth. Can also be
|
||||
* used as an object store with unique 32-bit references directly into the
|
||||
* internal storage (retrieved with iterator::getIndex()).
|
||||
*
|
||||
* Advantages:
|
||||
* - High-performance (~2-4x tbb::concurrent_hash_map in heavily
|
||||
* multi-threaded environments).
|
||||
* - Efficient memory usage if initial capacity is not over estimated
|
||||
* (especially for small keys and values).
|
||||
* - Good fragmentation properties (only allocates in large slabs which can
|
||||
* be reused with clear() and never move).
|
||||
* - Can generate unique, long-lived 32-bit references for efficient lookup
|
||||
* (see findAt()).
|
||||
*
|
||||
* Disadvantages:
|
||||
* - Keys must be native int32 or int64, or explicitly converted.
|
||||
* - Must be able to specify unique empty, locked, and erased keys
|
||||
* - Performance degrades linearly as size grows beyond initialization
|
||||
* capacity.
|
||||
* - Max size limit of ~18x initial size (dependent on max load factor).
|
||||
* - Memory is not freed or reclaimed by erase.
|
||||
*
|
||||
* Usage and Operation Details:
|
||||
* Simple performance/memory tradeoff with maxLoadFactor. Higher load factors
|
||||
* give better memory utilization but probe lengths increase, reducing
|
||||
* performance.
|
||||
*
|
||||
* Implementation and Performance Details:
|
||||
* AHArray is a fixed size contiguous block of value_type cells. When
|
||||
* writing a cell, the key is locked while the rest of the record is
|
||||
* written. Once done, the cell is unlocked by setting the key. find()
|
||||
* is completely wait-free and doesn't require any non-relaxed atomic
|
||||
* operations. AHA cannot grow beyond initialization capacity, but is
|
||||
* faster because of reduced data indirection.
|
||||
*
|
||||
* AHMap is a wrapper around AHArray sub-maps that allows growth and provides
|
||||
* an interface closer to the STL UnorderedAssociativeContainer concept. These
|
||||
* sub-maps are allocated on the fly and are processed in series, so the more
|
||||
* there are (from growing past initial capacity), the worse the performance.
|
||||
*
|
||||
* Insert returns false if there is a key collision and throws if the max size
|
||||
* of the map is exceeded.
|
||||
*
|
||||
* Benchmark performance with 8 simultaneous threads processing 1 million
|
||||
* unique <int64, int64> entries on a 4-core, 2.5 GHz machine:
|
||||
*
|
||||
* Load Factor Mem Efficiency usec/Insert usec/Find
|
||||
* 50% 50% 0.19 0.05
|
||||
* 85% 85% 0.20 0.06
|
||||
* 90% 90% 0.23 0.08
|
||||
* 95% 95% 0.27 0.10
|
||||
*
|
||||
* See folly/tests/AtomicHashMapTest.cpp for more benchmarks.
|
||||
*
|
||||
* @author Spencer Ahrens <sahrens@fb.com>
|
||||
* @author Jordan DeLong <delong.j@fb.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#define FOLLY_ATOMICHASHMAP_H_
|
||||
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/type_traits/is_convertible.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <folly/AtomicHashArray.h>
|
||||
#include <folly/CPortability.h>
|
||||
#include <folly/Likely.h>
|
||||
#include <folly/ThreadCachedInt.h>
|
||||
#include <folly/container/Foreach.h>
|
||||
#include <folly/hash/Hash.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/*
|
||||
* AtomicHashMap provides an interface somewhat similar to the
|
||||
* UnorderedAssociativeContainer concept in C++. This does not
|
||||
* exactly match this concept (or even the basic Container concept),
|
||||
* because of some restrictions imposed by our datastructure.
|
||||
*
|
||||
* Specific differences (there are quite a few):
|
||||
*
|
||||
* - Efficiently thread safe for inserts (main point of this stuff),
|
||||
* wait-free for lookups.
|
||||
*
|
||||
* - You can erase from this container, but the cell containing the key will
|
||||
* not be free or reclaimed.
|
||||
*
|
||||
* - You can erase everything by calling clear() (and you must guarantee only
|
||||
* one thread can be using the container to do that).
|
||||
*
|
||||
* - We aren't DefaultConstructible, CopyConstructible, Assignable, or
|
||||
* EqualityComparable. (Most of these are probably not something
|
||||
* you actually want to do with this anyway.)
|
||||
*
|
||||
* - We don't support the various bucket functions, rehash(),
|
||||
* reserve(), or equal_range(). Also no constructors taking
|
||||
* iterators, although this could change.
|
||||
*
|
||||
* - Several insertion functions, notably operator[], are not
|
||||
* implemented. It is a little too easy to misuse these functions
|
||||
* with this container, where part of the point is that when an
|
||||
* insertion happens for a new key, it will atomically have the
|
||||
* desired value.
|
||||
*
|
||||
* - The map has no templated insert() taking an iterator range, but
|
||||
* we do provide an insert(key, value). The latter seems more
|
||||
* frequently useful for this container (to avoid sprinkling
|
||||
* make_pair everywhere), and providing both can lead to some gross
|
||||
* template error messages.
|
||||
*
|
||||
* - The Allocator must not be stateful (a new instance will be spun up for
|
||||
* each allocation), and its allocate() method must take a raw number of
|
||||
* bytes.
|
||||
*
|
||||
* - KeyT must be a 32 bit or 64 bit atomic integer type, and you must
|
||||
* define special 'locked' and 'empty' key values in the ctor
|
||||
*
|
||||
* - We don't take the Hash function object as an instance in the
|
||||
* constructor.
|
||||
*
|
||||
*/
|
||||
|
||||
// Thrown when insertion fails due to running out of space for
|
||||
// submaps.
|
||||
struct FOLLY_EXPORT AtomicHashMapFullError : std::runtime_error {
|
||||
explicit AtomicHashMapFullError()
|
||||
: std::runtime_error("AtomicHashMap is full") {}
|
||||
};
|
||||
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn,
|
||||
class EqualFcn,
|
||||
class Allocator,
|
||||
class ProbeFcn,
|
||||
class KeyConvertFcn>
|
||||
class AtomicHashMap : boost::noncopyable {
|
||||
typedef AtomicHashArray<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
ProbeFcn,
|
||||
KeyConvertFcn>
|
||||
SubMap;
|
||||
|
||||
public:
|
||||
typedef KeyT key_type;
|
||||
typedef ValueT mapped_type;
|
||||
typedef std::pair<const KeyT, ValueT> value_type;
|
||||
typedef HashFcn hasher;
|
||||
typedef EqualFcn key_equal;
|
||||
typedef KeyConvertFcn key_convert;
|
||||
typedef value_type* pointer;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef std::size_t size_type;
|
||||
typedef typename SubMap::Config Config;
|
||||
|
||||
template <class ContT, class IterVal, class SubIt>
|
||||
struct ahm_iterator;
|
||||
|
||||
typedef ahm_iterator<
|
||||
const AtomicHashMap,
|
||||
const value_type,
|
||||
typename SubMap::const_iterator>
|
||||
const_iterator;
|
||||
typedef ahm_iterator<AtomicHashMap, value_type, typename SubMap::iterator>
|
||||
iterator;
|
||||
|
||||
public:
|
||||
const float kGrowthFrac_; // How much to grow when we run out of capacity.
|
||||
|
||||
// The constructor takes a finalSizeEst which is the optimal
|
||||
// number of elements to maximize space utilization and performance,
|
||||
// and a Config object to specify more advanced options.
|
||||
explicit AtomicHashMap(size_t finalSizeEst, const Config& c = Config());
|
||||
|
||||
~AtomicHashMap() {
|
||||
const unsigned int numMaps =
|
||||
numMapsAllocated_.load(std::memory_order_relaxed);
|
||||
FOR_EACH_RANGE (i, 0, numMaps) {
|
||||
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
|
||||
DCHECK(thisMap);
|
||||
SubMap::destroy(thisMap);
|
||||
}
|
||||
}
|
||||
|
||||
key_equal key_eq() const {
|
||||
return key_equal();
|
||||
}
|
||||
hasher hash_function() const {
|
||||
return hasher();
|
||||
}
|
||||
|
||||
/*
|
||||
* insert --
|
||||
*
|
||||
* Returns a pair with iterator to the element at r.first and
|
||||
* success. Retrieve the index with ret.first.getIndex().
|
||||
*
|
||||
* Does not overwrite on key collision, but returns an iterator to
|
||||
* the existing element (since this could due to a race with
|
||||
* another thread, it is often important to check this return
|
||||
* value).
|
||||
*
|
||||
* Allocates new sub maps as the existing ones become full. If
|
||||
* all sub maps are full, no element is inserted, and
|
||||
* AtomicHashMapFullError is thrown.
|
||||
*/
|
||||
std::pair<iterator, bool> insert(const value_type& r) {
|
||||
return emplace(r.first, r.second);
|
||||
}
|
||||
std::pair<iterator, bool> insert(key_type k, const mapped_type& v) {
|
||||
return emplace(k, v);
|
||||
}
|
||||
std::pair<iterator, bool> insert(value_type&& r) {
|
||||
return emplace(r.first, std::move(r.second));
|
||||
}
|
||||
std::pair<iterator, bool> insert(key_type k, mapped_type&& v) {
|
||||
return emplace(k, std::move(v));
|
||||
}
|
||||
|
||||
/*
|
||||
* emplace --
|
||||
*
|
||||
* Same contract as insert(), but performs in-place construction
|
||||
* of the value type using the specified arguments.
|
||||
*
|
||||
* Also, like find(), this method optionally allows 'key_in' to have a type
|
||||
* different from that stored in the table; see find(). If and only if no
|
||||
* equal key is already present, this method converts 'key_in' to a key of
|
||||
* type KeyT using the provided LookupKeyToKeyFcn.
|
||||
*/
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal,
|
||||
typename LookupKeyToKeyFcn = key_convert,
|
||||
typename... ArgTs>
|
||||
std::pair<iterator, bool> emplace(LookupKeyT k, ArgTs&&... vCtorArg);
|
||||
|
||||
/*
|
||||
* find --
|
||||
*
|
||||
* Returns the iterator to the element if found, otherwise end().
|
||||
*
|
||||
* As an optional feature, the type of the key to look up (LookupKeyT) is
|
||||
* allowed to be different from the type of keys actually stored (KeyT).
|
||||
*
|
||||
* This enables use cases where materializing the key is costly and usually
|
||||
* redudant, e.g., canonicalizing/interning a set of strings and being able
|
||||
* to look up by StringPiece. To use this feature, LookupHashFcn must take
|
||||
* a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first
|
||||
* and second parameter, respectively.
|
||||
*
|
||||
* See folly/test/ArrayHashMapTest.cpp for sample usage.
|
||||
*/
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal>
|
||||
iterator find(LookupKeyT k);
|
||||
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal>
|
||||
const_iterator find(LookupKeyT k) const;
|
||||
|
||||
/*
|
||||
* erase --
|
||||
*
|
||||
* Erases key k from the map
|
||||
*
|
||||
* Returns 1 iff the key is found and erased, and 0 otherwise.
|
||||
*/
|
||||
size_type erase(key_type k);
|
||||
|
||||
/*
|
||||
* clear --
|
||||
*
|
||||
* Wipes all keys and values from primary map and destroys all secondary
|
||||
* maps. Primary map remains allocated and thus the memory can be reused
|
||||
* in place. Not thread safe.
|
||||
*
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/*
|
||||
* size --
|
||||
*
|
||||
* Returns the exact size of the map. Note this is not as cheap as typical
|
||||
* size() implementations because, for each AtomicHashArray in this AHM, we
|
||||
* need to grab a lock and accumulate the values from all the thread local
|
||||
* counters. See folly/ThreadCachedInt.h for more details.
|
||||
*/
|
||||
size_t size() const;
|
||||
|
||||
bool empty() const {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
size_type count(key_type k) const {
|
||||
return find(k) == end() ? 0 : 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* findAt --
|
||||
*
|
||||
* Returns an iterator into the map.
|
||||
*
|
||||
* idx should only be an unmodified value returned by calling getIndex() on
|
||||
* a valid iterator returned by find() or insert(). If idx is invalid you
|
||||
* have a bug and the process aborts.
|
||||
*/
|
||||
iterator findAt(uint32_t idx) {
|
||||
SimpleRetT ret = findAtInternal(idx);
|
||||
DCHECK_LT(ret.i, numSubMaps());
|
||||
return iterator(
|
||||
this,
|
||||
ret.i,
|
||||
subMaps_[ret.i].load(std::memory_order_relaxed)->makeIter(ret.j));
|
||||
}
|
||||
const_iterator findAt(uint32_t idx) const {
|
||||
return const_cast<AtomicHashMap*>(this)->findAt(idx);
|
||||
}
|
||||
|
||||
// Total capacity - summation of capacities of all submaps.
|
||||
size_t capacity() const;
|
||||
|
||||
// Number of new insertions until current submaps are all at max load factor.
|
||||
size_t spaceRemaining() const;
|
||||
|
||||
void setEntryCountThreadCacheSize(int32_t newSize) {
|
||||
const int numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
for (int i = 0; i < numMaps; ++i) {
|
||||
SubMap* map = subMaps_[i].load(std::memory_order_relaxed);
|
||||
map->setEntryCountThreadCacheSize(newSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Number of sub maps allocated so far to implement this map. The more there
|
||||
// are, the worse the performance.
|
||||
int numSubMaps() const {
|
||||
return numMapsAllocated_.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
iterator begin() {
|
||||
iterator it(this, 0, subMaps_[0].load(std::memory_order_relaxed)->begin());
|
||||
it.checkAdvanceToNextSubmap();
|
||||
return it;
|
||||
}
|
||||
|
||||
const_iterator begin() const {
|
||||
const_iterator it(
|
||||
this, 0, subMaps_[0].load(std::memory_order_relaxed)->begin());
|
||||
it.checkAdvanceToNextSubmap();
|
||||
return it;
|
||||
}
|
||||
|
||||
iterator end() {
|
||||
return iterator();
|
||||
}
|
||||
|
||||
const_iterator end() const {
|
||||
return const_iterator();
|
||||
}
|
||||
|
||||
/* Advanced functions for direct access: */
|
||||
|
||||
inline uint32_t recToIdx(const value_type& r, bool mayInsert = true) {
|
||||
SimpleRetT ret =
|
||||
mayInsert ? insertInternal(r.first, r.second) : findInternal(r.first);
|
||||
return encodeIndex(ret.i, ret.j);
|
||||
}
|
||||
|
||||
inline uint32_t recToIdx(value_type&& r, bool mayInsert = true) {
|
||||
SimpleRetT ret = mayInsert ? insertInternal(r.first, std::move(r.second))
|
||||
: findInternal(r.first);
|
||||
return encodeIndex(ret.i, ret.j);
|
||||
}
|
||||
|
||||
inline uint32_t
|
||||
recToIdx(key_type k, const mapped_type& v, bool mayInsert = true) {
|
||||
SimpleRetT ret = mayInsert ? insertInternal(k, v) : findInternal(k);
|
||||
return encodeIndex(ret.i, ret.j);
|
||||
}
|
||||
|
||||
inline uint32_t recToIdx(key_type k, mapped_type&& v, bool mayInsert = true) {
|
||||
SimpleRetT ret =
|
||||
mayInsert ? insertInternal(k, std::move(v)) : findInternal(k);
|
||||
return encodeIndex(ret.i, ret.j);
|
||||
}
|
||||
|
||||
inline uint32_t keyToIdx(const KeyT k, bool mayInsert = false) {
|
||||
return recToIdx(value_type(k), mayInsert);
|
||||
}
|
||||
|
||||
inline const value_type& idxToRec(uint32_t idx) const {
|
||||
SimpleRetT ret = findAtInternal(idx);
|
||||
return subMaps_[ret.i].load(std::memory_order_relaxed)->idxToRec(ret.j);
|
||||
}
|
||||
|
||||
/* Private data and helper functions... */
|
||||
|
||||
private:
|
||||
// This limits primary submap size to 2^31 ~= 2 billion, secondary submap
|
||||
// size to 2^(32 - kNumSubMapBits_ - 1) = 2^27 ~= 130 million, and num subMaps
|
||||
// to 2^kNumSubMapBits_ = 16.
|
||||
static const uint32_t kNumSubMapBits_ = 4;
|
||||
static const uint32_t kSecondaryMapBit_ = 1u << 31; // Highest bit
|
||||
static const uint32_t kSubMapIndexShift_ = 32 - kNumSubMapBits_ - 1;
|
||||
static const uint32_t kSubMapIndexMask_ = (1 << kSubMapIndexShift_) - 1;
|
||||
static const uint32_t kNumSubMaps_ = 1 << kNumSubMapBits_;
|
||||
static const uintptr_t kLockedPtr_ = 0x88ULL << 48; // invalid pointer
|
||||
|
||||
struct SimpleRetT {
|
||||
uint32_t i;
|
||||
size_t j;
|
||||
bool success;
|
||||
SimpleRetT(uint32_t ii, size_t jj, bool s) : i(ii), j(jj), success(s) {}
|
||||
SimpleRetT() = default;
|
||||
};
|
||||
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal,
|
||||
typename LookupKeyToKeyFcn = key_convert,
|
||||
typename... ArgTs>
|
||||
SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... value);
|
||||
|
||||
template <
|
||||
typename LookupKeyT = key_type,
|
||||
typename LookupHashFcn = hasher,
|
||||
typename LookupEqualFcn = key_equal>
|
||||
SimpleRetT findInternal(const LookupKeyT k) const;
|
||||
|
||||
SimpleRetT findAtInternal(uint32_t idx) const;
|
||||
|
||||
std::atomic<SubMap*> subMaps_[kNumSubMaps_];
|
||||
std::atomic<uint32_t> numMapsAllocated_;
|
||||
|
||||
inline bool tryLockMap(unsigned int idx) {
|
||||
SubMap* val = nullptr;
|
||||
return subMaps_[idx].compare_exchange_strong(
|
||||
val, (SubMap*)kLockedPtr_, std::memory_order_acquire);
|
||||
}
|
||||
|
||||
static inline uint32_t encodeIndex(uint32_t subMap, uint32_t subMapIdx);
|
||||
|
||||
}; // AtomicHashMap
|
||||
|
||||
template <
|
||||
class KeyT,
|
||||
class ValueT,
|
||||
class HashFcn = std::hash<KeyT>,
|
||||
class EqualFcn = std::equal_to<KeyT>,
|
||||
class Allocator = std::allocator<char>>
|
||||
using QuadraticProbingAtomicHashMap = AtomicHashMap<
|
||||
KeyT,
|
||||
ValueT,
|
||||
HashFcn,
|
||||
EqualFcn,
|
||||
Allocator,
|
||||
AtomicHashArrayQuadraticProbeFcn>;
|
||||
} // namespace folly
|
||||
|
||||
#include <folly/AtomicHashMap-inl.h>
|
178
ios/Pods/Folly/folly/AtomicIntrusiveLinkedList.h
generated
178
ios/Pods/Folly/folly/AtomicIntrusiveLinkedList.h
generated
@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* A very simple atomic single-linked list primitive.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* class MyClass {
|
||||
* AtomicIntrusiveLinkedListHook<MyClass> hook_;
|
||||
* }
|
||||
*
|
||||
* AtomicIntrusiveLinkedList<MyClass, &MyClass::hook_> list;
|
||||
* list.insert(&a);
|
||||
* list.sweep([] (MyClass* c) { doSomething(c); }
|
||||
*/
|
||||
template <class T>
|
||||
struct AtomicIntrusiveLinkedListHook {
|
||||
T* next{nullptr};
|
||||
};
|
||||
|
||||
template <class T, AtomicIntrusiveLinkedListHook<T> T::*HookMember>
|
||||
class AtomicIntrusiveLinkedList {
|
||||
public:
|
||||
AtomicIntrusiveLinkedList() {}
|
||||
AtomicIntrusiveLinkedList(const AtomicIntrusiveLinkedList&) = delete;
|
||||
AtomicIntrusiveLinkedList& operator=(const AtomicIntrusiveLinkedList&) =
|
||||
delete;
|
||||
AtomicIntrusiveLinkedList(AtomicIntrusiveLinkedList&& other) noexcept {
|
||||
auto tmp = other.head_.load();
|
||||
other.head_ = head_.load();
|
||||
head_ = tmp;
|
||||
}
|
||||
AtomicIntrusiveLinkedList& operator=(
|
||||
AtomicIntrusiveLinkedList&& other) noexcept {
|
||||
auto tmp = other.head_.load();
|
||||
other.head_ = head_.load();
|
||||
head_ = tmp;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: list must be empty on destruction.
|
||||
*/
|
||||
~AtomicIntrusiveLinkedList() {
|
||||
assert(empty());
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return head_.load() == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically insert t at the head of the list.
|
||||
* @return True if the inserted element is the only one in the list
|
||||
* after the call.
|
||||
*/
|
||||
bool insertHead(T* t) {
|
||||
assert(next(t) == nullptr);
|
||||
|
||||
auto oldHead = head_.load(std::memory_order_relaxed);
|
||||
do {
|
||||
next(t) = oldHead;
|
||||
/* oldHead is updated by the call below.
|
||||
|
||||
NOTE: we don't use next(t) instead of oldHead directly due to
|
||||
compiler bugs (GCC prior to 4.8.3 (bug 60272), clang (bug 18899),
|
||||
MSVC (bug 819819); source:
|
||||
http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange */
|
||||
} while (!head_.compare_exchange_weak(
|
||||
oldHead, t, std::memory_order_release, std::memory_order_relaxed));
|
||||
|
||||
return oldHead == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the head with nullptr,
|
||||
* and calls func() on the removed elements in the order from tail to head.
|
||||
* Returns false if the list was empty.
|
||||
*/
|
||||
template <typename F>
|
||||
bool sweepOnce(F&& func) {
|
||||
if (auto head = head_.exchange(nullptr)) {
|
||||
auto rhead = reverse(head);
|
||||
unlinkAll(rhead, std::forward<F>(func));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeatedly replaces the head with nullptr,
|
||||
* and calls func() on the removed elements in the order from tail to head.
|
||||
* Stops when the list is empty.
|
||||
*/
|
||||
template <typename F>
|
||||
void sweep(F&& func) {
|
||||
while (sweepOnce(func)) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to sweep() but calls func() on elements in LIFO order.
|
||||
*
|
||||
* func() is called for all elements in the list at the moment
|
||||
* reverseSweep() is called. Unlike sweep() it does not loop to ensure the
|
||||
* list is empty at some point after the last invocation. This way callers
|
||||
* can reason about the ordering: elements inserted since the last call to
|
||||
* reverseSweep() will be provided in LIFO order.
|
||||
*
|
||||
* Example: if elements are inserted in the order 1-2-3, the callback is
|
||||
* invoked 3-2-1. If the callback moves elements onto a stack, popping off
|
||||
* the stack will produce the original insertion order 1-2-3.
|
||||
*/
|
||||
template <typename F>
|
||||
void reverseSweep(F&& func) {
|
||||
// We don't loop like sweep() does because the overall order of callbacks
|
||||
// would be strand-wise LIFO which is meaningless to callers.
|
||||
auto head = head_.exchange(nullptr);
|
||||
unlinkAll(head, std::forward<F>(func));
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<T*> head_{nullptr};
|
||||
|
||||
static T*& next(T* t) {
|
||||
return (t->*HookMember).next;
|
||||
}
|
||||
|
||||
/* Reverses a linked list, returning the pointer to the new head
|
||||
(old tail) */
|
||||
static T* reverse(T* head) {
|
||||
T* rhead = nullptr;
|
||||
while (head != nullptr) {
|
||||
auto t = head;
|
||||
head = next(t);
|
||||
next(t) = rhead;
|
||||
rhead = t;
|
||||
}
|
||||
return rhead;
|
||||
}
|
||||
|
||||
/* Unlinks all elements in the linked list fragment pointed to by `head',
|
||||
* calling func() on every element */
|
||||
template <typename F>
|
||||
void unlinkAll(T* head, F&& func) {
|
||||
while (head != nullptr) {
|
||||
auto t = head;
|
||||
head = next(t);
|
||||
next(t) = nullptr;
|
||||
func(t);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace folly
|
108
ios/Pods/Folly/folly/AtomicLinkedList.h
generated
108
ios/Pods/Folly/folly/AtomicLinkedList.h
generated
@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/AtomicIntrusiveLinkedList.h>
|
||||
#include <folly/Memory.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* A very simple atomic single-linked list primitive.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* AtomicLinkedList<MyClass> list;
|
||||
* list.insert(a);
|
||||
* list.sweep([] (MyClass& c) { doSomething(c); }
|
||||
*/
|
||||
|
||||
template <class T>
|
||||
class AtomicLinkedList {
|
||||
public:
|
||||
AtomicLinkedList() {}
|
||||
AtomicLinkedList(const AtomicLinkedList&) = delete;
|
||||
AtomicLinkedList& operator=(const AtomicLinkedList&) = delete;
|
||||
AtomicLinkedList(AtomicLinkedList&& other) noexcept = default;
|
||||
AtomicLinkedList& operator=(AtomicLinkedList&& other) = default;
|
||||
|
||||
~AtomicLinkedList() {
|
||||
sweep([](T&&) {});
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return list_.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically insert t at the head of the list.
|
||||
* @return True if the inserted element is the only one in the list
|
||||
* after the call.
|
||||
*/
|
||||
bool insertHead(T t) {
|
||||
auto wrapper = std::make_unique<Wrapper>(std::move(t));
|
||||
|
||||
return list_.insertHead(wrapper.release());
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeatedly pops element from head,
|
||||
* and calls func() on the removed elements in the order from tail to head.
|
||||
* Stops when the list is empty.
|
||||
*/
|
||||
template <typename F>
|
||||
void sweep(F&& func) {
|
||||
list_.sweep([&](Wrapper* wrapperPtr) mutable {
|
||||
std::unique_ptr<Wrapper> wrapper(wrapperPtr);
|
||||
|
||||
func(std::move(wrapper->data));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to sweep() but calls func() on elements in LIFO order.
|
||||
*
|
||||
* func() is called for all elements in the list at the moment
|
||||
* reverseSweep() is called. Unlike sweep() it does not loop to ensure the
|
||||
* list is empty at some point after the last invocation. This way callers
|
||||
* can reason about the ordering: elements inserted since the last call to
|
||||
* reverseSweep() will be provided in LIFO order.
|
||||
*
|
||||
* Example: if elements are inserted in the order 1-2-3, the callback is
|
||||
* invoked 3-2-1. If the callback moves elements onto a stack, popping off
|
||||
* the stack will produce the original insertion order 1-2-3.
|
||||
*/
|
||||
template <typename F>
|
||||
void reverseSweep(F&& func) {
|
||||
list_.reverseSweep([&](Wrapper* wrapperPtr) mutable {
|
||||
std::unique_ptr<Wrapper> wrapper(wrapperPtr);
|
||||
|
||||
func(std::move(wrapper->data));
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
struct Wrapper {
|
||||
explicit Wrapper(T&& t) : data(std::move(t)) {}
|
||||
|
||||
AtomicIntrusiveLinkedListHook<Wrapper> hook;
|
||||
T data;
|
||||
};
|
||||
AtomicIntrusiveLinkedList<Wrapper, &Wrapper::hook> list_;
|
||||
};
|
||||
|
||||
} // namespace folly
|
515
ios/Pods/Folly/folly/AtomicUnorderedMap.h
generated
515
ios/Pods/Folly/folly/AtomicUnorderedMap.h
generated
@ -1,515 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/type_traits/has_trivial_destructor.hpp>
|
||||
|
||||
#include <folly/Conv.h>
|
||||
#include <folly/Likely.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/detail/AtomicUnorderedMapUtils.h>
|
||||
#include <folly/lang/Bits.h>
|
||||
#include <folly/portability/SysMman.h>
|
||||
#include <folly/portability/Unistd.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
/// You're probably reading this because you are looking for an
|
||||
/// AtomicUnorderedMap<K,V> that is fully general, highly concurrent (for
|
||||
/// reads, writes, and iteration), and makes no performance compromises.
|
||||
/// We haven't figured that one out yet. What you will find here is a
|
||||
/// hash table implementation that sacrifices generality so that it can
|
||||
/// give you all of the other things.
|
||||
///
|
||||
/// LIMITATIONS:
|
||||
///
|
||||
/// * Insert only (*) - the only write operation supported directly by
|
||||
/// AtomicUnorderedInsertMap is findOrConstruct. There is a (*) because
|
||||
/// values aren't moved, so you can roll your own concurrency control for
|
||||
/// in-place updates of values (see MutableData and MutableAtom below),
|
||||
/// but the hash table itself doesn't help you.
|
||||
///
|
||||
/// * No resizing - you must specify the capacity up front, and once
|
||||
/// the hash map gets full you won't be able to insert. Insert
|
||||
/// performance will degrade once the load factor is high. Insert is
|
||||
/// O(1/(1-actual_load_factor)). Note that this is a pretty strong
|
||||
/// limitation, because you can't remove existing keys.
|
||||
///
|
||||
/// * 2^30 maximum default capacity - by default AtomicUnorderedInsertMap
|
||||
/// uses uint32_t internal indexes (and steals 2 bits), limiting you
|
||||
/// to about a billion entries. If you need more you can fill in all
|
||||
/// of the template params so you change IndexType to uint64_t, or you
|
||||
/// can use AtomicUnorderedInsertMap64. 64-bit indexes will increase
|
||||
/// the space over of the map, of course.
|
||||
///
|
||||
/// WHAT YOU GET IN EXCHANGE:
|
||||
///
|
||||
/// * Arbitrary key and value types - any K and V that can be used in a
|
||||
/// std::unordered_map can be used here. In fact, the key and value
|
||||
/// types don't even have to be copyable or moveable!
|
||||
///
|
||||
/// * Keys and values in the map won't be moved - it is safe to keep
|
||||
/// pointers or references to the keys and values in the map, because
|
||||
/// they are never moved or destroyed (until the map itself is destroyed).
|
||||
///
|
||||
/// * Iterators are never invalidated - writes don't invalidate iterators,
|
||||
/// so you can scan and insert in parallel.
|
||||
///
|
||||
/// * Fast wait-free reads - reads are usually only a single cache miss,
|
||||
/// even when the hash table is very large. Wait-freedom means that
|
||||
/// you won't see latency outliers even in the face of concurrent writes.
|
||||
///
|
||||
/// * Lock-free insert - writes proceed in parallel. If a thread in the
|
||||
/// middle of a write is unlucky and gets suspended, it doesn't block
|
||||
/// anybody else.
|
||||
///
|
||||
/// COMMENTS ON INSERT-ONLY
|
||||
///
|
||||
/// This map provides wait-free linearizable reads and lock-free
|
||||
/// linearizable inserts. Inserted values won't be moved, but no
|
||||
/// concurrency control is provided for safely updating them. To remind
|
||||
/// you of that fact they are only provided in const form. This is the
|
||||
/// only simple safe thing to do while preserving something like the normal
|
||||
/// std::map iteration form, which requires that iteration be exposed
|
||||
/// via std::pair (and prevents encapsulation of access to the value).
|
||||
///
|
||||
/// There are a couple of reasonable policies for doing in-place
|
||||
/// concurrency control on the values. I am hoping that the policy can
|
||||
/// be injected via the value type or an extra template param, to keep
|
||||
/// the core AtomicUnorderedInsertMap insert-only:
|
||||
///
|
||||
/// CONST: this is the currently implemented strategy, which is simple,
|
||||
/// performant, and not that expressive. You can always put in a value
|
||||
/// with a mutable field (see MutableAtom below), but that doesn't look
|
||||
/// as pretty as it should.
|
||||
///
|
||||
/// ATOMIC: for integers and integer-size trivially copyable structs
|
||||
/// (via an adapter like tao/queues/AtomicStruct) the value can be a
|
||||
/// std::atomic and read and written atomically.
|
||||
///
|
||||
/// SEQ-LOCK: attach a counter incremented before and after write.
|
||||
/// Writers serialize by using CAS to make an even->odd transition,
|
||||
/// then odd->even after the write. Readers grab the value with memcpy,
|
||||
/// checking sequence value before and after. Readers retry until they
|
||||
/// see an even sequence number that doesn't change. This works for
|
||||
/// larger structs, but still requires memcpy to be equivalent to copy
|
||||
/// assignment, and it is no longer lock-free. It scales very well,
|
||||
/// because the readers are still invisible (no cache line writes).
|
||||
///
|
||||
/// LOCK: folly's SharedMutex would be a good choice here.
|
||||
///
|
||||
/// MEMORY ALLOCATION
|
||||
///
|
||||
/// Underlying memory is allocated as a big anonymous mmap chunk, which
|
||||
/// might be cheaper than calloc() and is certainly not more expensive
|
||||
/// for large maps. If the SkipKeyValueDeletion template param is true
|
||||
/// then deletion of the map consists of unmapping the backing memory,
|
||||
/// which is much faster than destructing all of the keys and values.
|
||||
/// Feel free to override if std::is_trivial_destructor isn't recognizing
|
||||
/// the triviality of your destructors.
|
||||
template <
|
||||
typename Key,
|
||||
typename Value,
|
||||
typename Hash = std::hash<Key>,
|
||||
typename KeyEqual = std::equal_to<Key>,
|
||||
bool SkipKeyValueDeletion =
|
||||
(boost::has_trivial_destructor<Key>::value &&
|
||||
boost::has_trivial_destructor<Value>::value),
|
||||
template <typename> class Atom = std::atomic,
|
||||
typename IndexType = uint32_t,
|
||||
typename Allocator = folly::detail::MMapAlloc>
|
||||
|
||||
struct AtomicUnorderedInsertMap {
|
||||
typedef Key key_type;
|
||||
typedef Value mapped_type;
|
||||
typedef std::pair<Key, Value> value_type;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef Hash hasher;
|
||||
typedef KeyEqual key_equal;
|
||||
typedef const value_type& const_reference;
|
||||
|
||||
typedef struct ConstIterator {
|
||||
ConstIterator(const AtomicUnorderedInsertMap& owner, IndexType slot)
|
||||
: owner_(owner), slot_(slot) {}
|
||||
|
||||
ConstIterator(const ConstIterator&) = default;
|
||||
ConstIterator& operator=(const ConstIterator&) = default;
|
||||
|
||||
const value_type& operator*() const {
|
||||
return owner_.slots_[slot_].keyValue();
|
||||
}
|
||||
|
||||
const value_type* operator->() const {
|
||||
return &owner_.slots_[slot_].keyValue();
|
||||
}
|
||||
|
||||
// pre-increment
|
||||
const ConstIterator& operator++() {
|
||||
while (slot_ > 0) {
|
||||
--slot_;
|
||||
if (owner_.slots_[slot_].state() == LINKED) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// post-increment
|
||||
ConstIterator operator++(int /* dummy */) {
|
||||
auto prev = *this;
|
||||
++*this;
|
||||
return prev;
|
||||
}
|
||||
|
||||
bool operator==(const ConstIterator& rhs) const {
|
||||
return slot_ == rhs.slot_;
|
||||
}
|
||||
bool operator!=(const ConstIterator& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
const AtomicUnorderedInsertMap& owner_;
|
||||
IndexType slot_;
|
||||
} const_iterator;
|
||||
|
||||
friend ConstIterator;
|
||||
|
||||
/// Constructs a map that will support the insertion of maxSize key-value
|
||||
/// pairs without exceeding the max load factor. Load factors of greater
|
||||
/// than 1 are not supported, and once the actual load factor of the
|
||||
/// map approaches 1 the insert performance will suffer. The capacity
|
||||
/// is limited to 2^30 (about a billion) for the default IndexType,
|
||||
/// beyond which we will throw invalid_argument.
|
||||
explicit AtomicUnorderedInsertMap(
|
||||
size_t maxSize,
|
||||
float maxLoadFactor = 0.8f,
|
||||
const Allocator& alloc = Allocator())
|
||||
: allocator_(alloc) {
|
||||
size_t capacity = size_t(maxSize / std::min(1.0f, maxLoadFactor) + 128);
|
||||
size_t avail = size_t{1} << (8 * sizeof(IndexType) - 2);
|
||||
if (capacity > avail && maxSize < avail) {
|
||||
// we'll do our best
|
||||
capacity = avail;
|
||||
}
|
||||
if (capacity < maxSize || capacity > avail) {
|
||||
throw std::invalid_argument(
|
||||
"AtomicUnorderedInsertMap capacity must fit in IndexType with 2 bits "
|
||||
"left over");
|
||||
}
|
||||
|
||||
numSlots_ = capacity;
|
||||
slotMask_ = folly::nextPowTwo(capacity * 4) - 1;
|
||||
mmapRequested_ = sizeof(Slot) * capacity;
|
||||
slots_ = reinterpret_cast<Slot*>(allocator_.allocate(mmapRequested_));
|
||||
zeroFillSlots();
|
||||
// mark the zero-th slot as in-use but not valid, since that happens
|
||||
// to be our nil value
|
||||
slots_[0].stateUpdate(EMPTY, CONSTRUCTING);
|
||||
}
|
||||
|
||||
~AtomicUnorderedInsertMap() {
|
||||
if (!SkipKeyValueDeletion) {
|
||||
for (size_t i = 1; i < numSlots_; ++i) {
|
||||
slots_[i].~Slot();
|
||||
}
|
||||
}
|
||||
allocator_.deallocate(reinterpret_cast<char*>(slots_), mmapRequested_);
|
||||
}
|
||||
|
||||
/// Searches for the key, returning (iter,false) if it is found.
|
||||
/// If it is not found calls the functor Func with a void* argument
|
||||
/// that is raw storage suitable for placement construction of a Value
|
||||
/// (see raw_value_type), then returns (iter,true). May call Func and
|
||||
/// then return (iter,false) if there are other concurrent writes, in
|
||||
/// which case the newly constructed value will be immediately destroyed.
|
||||
///
|
||||
/// This function does not block other readers or writers. If there
|
||||
/// are other concurrent writes, many parallel calls to func may happen
|
||||
/// and only the first one to complete will win. The values constructed
|
||||
/// by the other calls to func will be destroyed.
|
||||
///
|
||||
/// Usage:
|
||||
///
|
||||
/// AtomicUnorderedInsertMap<std::string,std::string> memo;
|
||||
///
|
||||
/// auto value = memo.findOrConstruct(key, [=](void* raw) {
|
||||
/// new (raw) std::string(computation(key));
|
||||
/// })->first;
|
||||
template <typename Func>
|
||||
std::pair<const_iterator, bool> findOrConstruct(const Key& key, Func&& func) {
|
||||
auto const slot = keyToSlotIdx(key);
|
||||
auto prev = slots_[slot].headAndState_.load(std::memory_order_acquire);
|
||||
|
||||
auto existing = find(key, slot);
|
||||
if (existing != 0) {
|
||||
return std::make_pair(ConstIterator(*this, existing), false);
|
||||
}
|
||||
|
||||
auto idx = allocateNear(slot);
|
||||
new (&slots_[idx].keyValue().first) Key(key);
|
||||
func(static_cast<void*>(&slots_[idx].keyValue().second));
|
||||
|
||||
while (true) {
|
||||
slots_[idx].next_ = prev >> 2;
|
||||
|
||||
// we can merge the head update and the CONSTRUCTING -> LINKED update
|
||||
// into a single CAS if slot == idx (which should happen often)
|
||||
auto after = idx << 2;
|
||||
if (slot == idx) {
|
||||
after += LINKED;
|
||||
} else {
|
||||
after += (prev & 3);
|
||||
}
|
||||
|
||||
if (slots_[slot].headAndState_.compare_exchange_strong(prev, after)) {
|
||||
// success
|
||||
if (idx != slot) {
|
||||
slots_[idx].stateUpdate(CONSTRUCTING, LINKED);
|
||||
}
|
||||
return std::make_pair(ConstIterator(*this, idx), true);
|
||||
}
|
||||
// compare_exchange_strong updates its first arg on failure, so
|
||||
// there is no need to reread prev
|
||||
|
||||
existing = find(key, slot);
|
||||
if (existing != 0) {
|
||||
// our allocated key and value are no longer needed
|
||||
slots_[idx].keyValue().first.~Key();
|
||||
slots_[idx].keyValue().second.~Value();
|
||||
slots_[idx].stateUpdate(CONSTRUCTING, EMPTY);
|
||||
|
||||
return std::make_pair(ConstIterator(*this, existing), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This isn't really emplace, but it is what we need to test.
|
||||
/// Eventually we can duplicate all of the std::pair constructor
|
||||
/// forms, including a recursive tuple forwarding template
|
||||
/// http://functionalcpp.wordpress.com/2013/08/28/tuple-forwarding/).
|
||||
template <class K, class V>
|
||||
std::pair<const_iterator, bool> emplace(const K& key, V&& value) {
|
||||
return findOrConstruct(
|
||||
key, [&](void* raw) { new (raw) Value(std::forward<V>(value)); });
|
||||
}
|
||||
|
||||
const_iterator find(const Key& key) const {
|
||||
return ConstIterator(*this, find(key, keyToSlotIdx(key)));
|
||||
}
|
||||
|
||||
const_iterator cbegin() const {
|
||||
IndexType slot = numSlots_ - 1;
|
||||
while (slot > 0 && slots_[slot].state() != LINKED) {
|
||||
--slot;
|
||||
}
|
||||
return ConstIterator(*this, slot);
|
||||
}
|
||||
|
||||
const_iterator cend() const {
|
||||
return ConstIterator(*this, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
enum : IndexType {
|
||||
kMaxAllocationTries = 1000, // after this we throw
|
||||
};
|
||||
|
||||
enum BucketState : IndexType {
|
||||
EMPTY = 0,
|
||||
CONSTRUCTING = 1,
|
||||
LINKED = 2,
|
||||
};
|
||||
|
||||
/// Lock-free insertion is easiest by prepending to collision chains.
|
||||
/// A large chaining hash table takes two cache misses instead of
|
||||
/// one, however. Our solution is to colocate the bucket storage and
|
||||
/// the head storage, so that even though we are traversing chains we
|
||||
/// are likely to stay within the same cache line. Just make sure to
|
||||
/// traverse head before looking at any keys. This strategy gives us
|
||||
/// 32 bit pointers and fast iteration.
|
||||
struct Slot {
|
||||
/// The bottom two bits are the BucketState, the rest is the index
|
||||
/// of the first bucket for the chain whose keys map to this slot.
|
||||
/// When things are going well the head usually links to this slot,
|
||||
/// but that doesn't always have to happen.
|
||||
Atom<IndexType> headAndState_;
|
||||
|
||||
/// The next bucket in the chain
|
||||
IndexType next_;
|
||||
|
||||
/// Key and Value
|
||||
typename std::aligned_storage<sizeof(value_type), alignof(value_type)>::type
|
||||
raw_;
|
||||
|
||||
~Slot() {
|
||||
auto s = state();
|
||||
assert(s == EMPTY || s == LINKED);
|
||||
if (s == LINKED) {
|
||||
keyValue().first.~Key();
|
||||
keyValue().second.~Value();
|
||||
}
|
||||
}
|
||||
|
||||
BucketState state() const {
|
||||
return BucketState(headAndState_.load(std::memory_order_acquire) & 3);
|
||||
}
|
||||
|
||||
void stateUpdate(BucketState before, BucketState after) {
|
||||
assert(state() == before);
|
||||
headAndState_ += (after - before);
|
||||
}
|
||||
|
||||
value_type& keyValue() {
|
||||
assert(state() != EMPTY);
|
||||
return *static_cast<value_type*>(static_cast<void*>(&raw_));
|
||||
}
|
||||
|
||||
const value_type& keyValue() const {
|
||||
assert(state() != EMPTY);
|
||||
return *static_cast<const value_type*>(static_cast<const void*>(&raw_));
|
||||
}
|
||||
};
|
||||
|
||||
// We manually manage the slot memory so we can bypass initialization
|
||||
// (by getting a zero-filled mmap chunk) and optionally destruction of
|
||||
// the slots
|
||||
|
||||
size_t mmapRequested_;
|
||||
size_t numSlots_;
|
||||
|
||||
/// tricky, see keyToSlodIdx
|
||||
size_t slotMask_;
|
||||
|
||||
Allocator allocator_;
|
||||
Slot* slots_;
|
||||
|
||||
IndexType keyToSlotIdx(const Key& key) const {
|
||||
size_t h = hasher()(key);
|
||||
h &= slotMask_;
|
||||
while (h >= numSlots_) {
|
||||
h -= numSlots_;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
IndexType find(const Key& key, IndexType slot) const {
|
||||
KeyEqual ke = {};
|
||||
auto hs = slots_[slot].headAndState_.load(std::memory_order_acquire);
|
||||
for (slot = hs >> 2; slot != 0; slot = slots_[slot].next_) {
|
||||
if (ke(key, slots_[slot].keyValue().first)) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Allocates a slot and returns its index. Tries to put it near
|
||||
/// slots_[start].
|
||||
IndexType allocateNear(IndexType start) {
|
||||
for (IndexType tries = 0; tries < kMaxAllocationTries; ++tries) {
|
||||
auto slot = allocationAttempt(start, tries);
|
||||
auto prev = slots_[slot].headAndState_.load(std::memory_order_acquire);
|
||||
if ((prev & 3) == EMPTY &&
|
||||
slots_[slot].headAndState_.compare_exchange_strong(
|
||||
prev, prev + CONSTRUCTING - EMPTY)) {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
/// Returns the slot we should attempt to allocate after tries failed
|
||||
/// tries, starting from the specified slot. This is pulled out so we
|
||||
/// can specialize it differently during deterministic testing
|
||||
IndexType allocationAttempt(IndexType start, IndexType tries) const {
|
||||
if (LIKELY(tries < 8 && start + tries < numSlots_)) {
|
||||
return IndexType(start + tries);
|
||||
} else {
|
||||
IndexType rv;
|
||||
if (sizeof(IndexType) <= 4) {
|
||||
rv = IndexType(folly::Random::rand32(numSlots_));
|
||||
} else {
|
||||
rv = IndexType(folly::Random::rand64(numSlots_));
|
||||
}
|
||||
assert(rv < numSlots_);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
void zeroFillSlots() {
|
||||
using folly::detail::GivesZeroFilledMemory;
|
||||
if (!GivesZeroFilledMemory<Allocator>::value) {
|
||||
memset(slots_, 0, mmapRequested_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// AtomicUnorderedInsertMap64 is just a type alias that makes it easier
|
||||
/// to select a 64 bit slot index type. Use this if you need a capacity
|
||||
/// bigger than 2^30 (about a billion). This increases memory overheads,
|
||||
/// obviously.
|
||||
template <
|
||||
typename Key,
|
||||
typename Value,
|
||||
typename Hash = std::hash<Key>,
|
||||
typename KeyEqual = std::equal_to<Key>,
|
||||
bool SkipKeyValueDeletion =
|
||||
(boost::has_trivial_destructor<Key>::value &&
|
||||
boost::has_trivial_destructor<Value>::value),
|
||||
template <typename> class Atom = std::atomic,
|
||||
typename Allocator = folly::detail::MMapAlloc>
|
||||
using AtomicUnorderedInsertMap64 = AtomicUnorderedInsertMap<
|
||||
Key,
|
||||
Value,
|
||||
Hash,
|
||||
KeyEqual,
|
||||
SkipKeyValueDeletion,
|
||||
Atom,
|
||||
uint64_t,
|
||||
Allocator>;
|
||||
|
||||
/// MutableAtom is a tiny wrapper than gives you the option of atomically
|
||||
/// updating values inserted into an AtomicUnorderedInsertMap<K,
|
||||
/// MutableAtom<V>>. This relies on AtomicUnorderedInsertMap's guarantee
|
||||
/// that it doesn't move values.
|
||||
template <typename T, template <typename> class Atom = std::atomic>
|
||||
struct MutableAtom {
|
||||
mutable Atom<T> data;
|
||||
|
||||
explicit MutableAtom(const T& init) : data(init) {}
|
||||
};
|
||||
|
||||
/// MutableData is a tiny wrapper than gives you the option of using an
|
||||
/// external concurrency control mechanism to updating values inserted
|
||||
/// into an AtomicUnorderedInsertMap.
|
||||
template <typename T>
|
||||
struct MutableData {
|
||||
mutable T data;
|
||||
explicit MutableData(const T& init) : data(init) {}
|
||||
};
|
||||
|
||||
} // namespace folly
|
579
ios/Pods/Folly/folly/Benchmark.h
generated
579
ios/Pods/Folly/folly/Benchmark.h
generated
@ -1,579 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/Portability.h>
|
||||
#include <folly/Preprocessor.h> // for FB_ANONYMOUS_VARIABLE
|
||||
#include <folly/ScopeGuard.h>
|
||||
#include <folly/Traits.h>
|
||||
#include <folly/functional/Invoke.h>
|
||||
#include <folly/portability/GFlags.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <glog/logging.h>
|
||||
|
||||
DECLARE_bool(benchmark);
|
||||
|
||||
namespace folly {
|
||||
|
||||
/**
|
||||
* Runs all benchmarks defined. Usually put in main().
|
||||
*/
|
||||
void runBenchmarks();
|
||||
|
||||
/**
|
||||
* Runs all benchmarks defined if and only if the --benchmark flag has
|
||||
* been passed to the program. Usually put in main().
|
||||
*/
|
||||
inline bool runBenchmarksOnFlag() {
|
||||
if (FLAGS_benchmark) {
|
||||
runBenchmarks();
|
||||
}
|
||||
return FLAGS_benchmark;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
using TimeIterPair =
|
||||
std::pair<std::chrono::high_resolution_clock::duration, unsigned int>;
|
||||
using BenchmarkFun = std::function<detail::TimeIterPair(unsigned int)>;
|
||||
|
||||
struct BenchmarkRegistration {
|
||||
std::string file;
|
||||
std::string name;
|
||||
BenchmarkFun func;
|
||||
};
|
||||
|
||||
struct BenchmarkResult {
|
||||
std::string file;
|
||||
std::string name;
|
||||
double timeInNs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a benchmark wrapped in a std::function. Only used
|
||||
* internally. Pass by value is intentional.
|
||||
*/
|
||||
void addBenchmarkImpl(
|
||||
const char* file,
|
||||
const char* name,
|
||||
std::function<TimeIterPair(unsigned int)>);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* Supporting type for BENCHMARK_SUSPEND defined below.
|
||||
*/
|
||||
struct BenchmarkSuspender {
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
using Duration = Clock::duration;
|
||||
|
||||
BenchmarkSuspender() {
|
||||
start = Clock::now();
|
||||
}
|
||||
|
||||
BenchmarkSuspender(const BenchmarkSuspender&) = delete;
|
||||
BenchmarkSuspender(BenchmarkSuspender&& rhs) noexcept {
|
||||
start = rhs.start;
|
||||
rhs.start = {};
|
||||
}
|
||||
|
||||
BenchmarkSuspender& operator=(const BenchmarkSuspender&) = delete;
|
||||
BenchmarkSuspender& operator=(BenchmarkSuspender&& rhs) {
|
||||
if (start != TimePoint{}) {
|
||||
tally();
|
||||
}
|
||||
start = rhs.start;
|
||||
rhs.start = {};
|
||||
return *this;
|
||||
}
|
||||
|
||||
~BenchmarkSuspender() {
|
||||
if (start != TimePoint{}) {
|
||||
tally();
|
||||
}
|
||||
}
|
||||
|
||||
void dismiss() {
|
||||
assert(start != TimePoint{});
|
||||
tally();
|
||||
start = {};
|
||||
}
|
||||
|
||||
void rehire() {
|
||||
assert(start == TimePoint{});
|
||||
start = Clock::now();
|
||||
}
|
||||
|
||||
template <class F>
|
||||
auto dismissing(F f) -> invoke_result_t<F> {
|
||||
SCOPE_EXIT {
|
||||
rehire();
|
||||
};
|
||||
dismiss();
|
||||
return f();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is for use inside of if-conditions, used in BENCHMARK macros.
|
||||
* If-conditions bypass the explicit on operator bool.
|
||||
*/
|
||||
explicit operator bool() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulates time spent outside benchmark.
|
||||
*/
|
||||
static Duration timeSpent;
|
||||
|
||||
private:
|
||||
void tally() {
|
||||
auto end = Clock::now();
|
||||
timeSpent += end - start;
|
||||
start = end;
|
||||
}
|
||||
|
||||
TimePoint start;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a benchmark. Usually not called directly but instead through
|
||||
* the macro BENCHMARK defined below. The lambda function involved
|
||||
* must take exactly one parameter of type unsigned, and the benchmark
|
||||
* uses it with counter semantics (iteration occurs inside the
|
||||
* function).
|
||||
*/
|
||||
template <typename Lambda>
|
||||
typename std::enable_if<
|
||||
boost::function_types::function_arity<
|
||||
decltype(&Lambda::operator())>::value == 2>::type
|
||||
addBenchmark(const char* file, const char* name, Lambda&& lambda) {
|
||||
auto execute = [=](unsigned int times) {
|
||||
BenchmarkSuspender::timeSpent = {};
|
||||
unsigned int niter;
|
||||
|
||||
// CORE MEASUREMENT STARTS
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
niter = lambda(times);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
// CORE MEASUREMENT ENDS
|
||||
|
||||
return detail::TimeIterPair(
|
||||
(end - start) - BenchmarkSuspender::timeSpent, niter);
|
||||
};
|
||||
|
||||
detail::addBenchmarkImpl(
|
||||
file, name, std::function<detail::TimeIterPair(unsigned int)>(execute));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a benchmark. Usually not called directly but instead through
|
||||
* the macro BENCHMARK defined below. The lambda function involved
|
||||
* must take zero parameters, and the benchmark calls it repeatedly
|
||||
* (iteration occurs outside the function).
|
||||
*/
|
||||
template <typename Lambda>
|
||||
typename std::enable_if<
|
||||
boost::function_types::function_arity<
|
||||
decltype(&Lambda::operator())>::value == 1>::type
|
||||
addBenchmark(const char* file, const char* name, Lambda&& lambda) {
|
||||
addBenchmark(file, name, [=](unsigned int times) {
|
||||
unsigned int niter = 0;
|
||||
while (times-- > 0) {
|
||||
niter += lambda();
|
||||
}
|
||||
return niter;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call doNotOptimizeAway(var) to ensure that var will be computed even
|
||||
* post-optimization. Use it for variables that are computed during
|
||||
* benchmarking but otherwise are useless. The compiler tends to do a
|
||||
* good job at eliminating unused variables, and this function fools it
|
||||
* into thinking var is in fact needed.
|
||||
*
|
||||
* Call makeUnpredictable(var) when you don't want the optimizer to use
|
||||
* its knowledge of var to shape the following code. This is useful
|
||||
* when constant propagation or power reduction is possible during your
|
||||
* benchmark but not in real use cases.
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
inline void doNotOptimizeDependencySink(const void*) {}
|
||||
|
||||
#pragma optimize("", on)
|
||||
|
||||
template <class T>
|
||||
void doNotOptimizeAway(const T& datum) {
|
||||
doNotOptimizeDependencySink(&datum);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void makeUnpredictable(T& datum) {
|
||||
doNotOptimizeDependencySink(&datum);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
struct DoNotOptimizeAwayNeedsIndirect {
|
||||
using Decayed = typename std::decay<T>::type;
|
||||
|
||||
// First two constraints ensure it can be an "r" operand.
|
||||
// std::is_pointer check is because callers seem to expect that
|
||||
// doNotOptimizeAway(&x) is equivalent to doNotOptimizeAway(x).
|
||||
constexpr static bool value = !folly::is_trivially_copyable<Decayed>::value ||
|
||||
sizeof(Decayed) > sizeof(long) || std::is_pointer<Decayed>::value;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
auto doNotOptimizeAway(const T& datum) -> typename std::enable_if<
|
||||
!detail::DoNotOptimizeAwayNeedsIndirect<T>::value>::type {
|
||||
// The "r" constraint forces the compiler to make datum available
|
||||
// in a register to the asm block, which means that it must have
|
||||
// computed/loaded it. We use this path for things that are <=
|
||||
// sizeof(long) (they have to fit), trivial (otherwise the compiler
|
||||
// doesn't want to put them in a register), and not a pointer (because
|
||||
// doNotOptimizeAway(&foo) would otherwise be a foot gun that didn't
|
||||
// necessarily compute foo).
|
||||
//
|
||||
// An earlier version of this method had a more permissive input operand
|
||||
// constraint, but that caused unnecessary variation between clang and
|
||||
// gcc benchmarks.
|
||||
asm volatile("" ::"r"(datum));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto doNotOptimizeAway(const T& datum) -> typename std::enable_if<
|
||||
detail::DoNotOptimizeAwayNeedsIndirect<T>::value>::type {
|
||||
// This version of doNotOptimizeAway tells the compiler that the asm
|
||||
// block will read datum from memory, and that in addition it might read
|
||||
// or write from any memory location. If the memory clobber could be
|
||||
// separated into input and output that would be preferrable.
|
||||
asm volatile("" ::"m"(datum) : "memory");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto makeUnpredictable(T& datum) -> typename std::enable_if<
|
||||
!detail::DoNotOptimizeAwayNeedsIndirect<T>::value>::type {
|
||||
asm volatile("" : "+r"(datum));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto makeUnpredictable(T& datum) -> typename std::enable_if<
|
||||
detail::DoNotOptimizeAwayNeedsIndirect<T>::value>::type {
|
||||
asm volatile("" ::"m"(datum) : "memory");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
struct dynamic;
|
||||
|
||||
void benchmarkResultsToDynamic(
|
||||
const std::vector<detail::BenchmarkResult>& data,
|
||||
dynamic&);
|
||||
|
||||
void benchmarkResultsFromDynamic(
|
||||
const dynamic&,
|
||||
std::vector<detail::BenchmarkResult>&);
|
||||
|
||||
void printResultComparison(
|
||||
const std::vector<detail::BenchmarkResult>& base,
|
||||
const std::vector<detail::BenchmarkResult>& test);
|
||||
|
||||
} // namespace folly
|
||||
|
||||
/**
|
||||
* Introduces a benchmark function. Used internally, see BENCHMARK and
|
||||
* friends below.
|
||||
*/
|
||||
#define BENCHMARK_IMPL(funName, stringName, rv, paramType, paramName) \
|
||||
static void funName(paramType); \
|
||||
static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \
|
||||
(::folly::addBenchmark( \
|
||||
__FILE__, \
|
||||
stringName, \
|
||||
[](paramType paramName) -> unsigned { \
|
||||
funName(paramName); \
|
||||
return rv; \
|
||||
}), \
|
||||
true); \
|
||||
static void funName(paramType paramName)
|
||||
|
||||
/**
|
||||
* Introduces a benchmark function with support for returning the actual
|
||||
* number of iterations. Used internally, see BENCHMARK_MULTI and friends
|
||||
* below.
|
||||
*/
|
||||
#define BENCHMARK_MULTI_IMPL(funName, stringName, paramType, paramName) \
|
||||
static unsigned funName(paramType); \
|
||||
static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \
|
||||
(::folly::addBenchmark( \
|
||||
__FILE__, \
|
||||
stringName, \
|
||||
[](paramType paramName) { return funName(paramName); }), \
|
||||
true); \
|
||||
static unsigned funName(paramType paramName)
|
||||
|
||||
/**
|
||||
* Introduces a benchmark function. Use with either one or two arguments.
|
||||
* The first is the name of the benchmark. Use something descriptive, such
|
||||
* as insertVectorBegin. The second argument may be missing, or could be a
|
||||
* symbolic counter. The counter dictates how many internal iteration the
|
||||
* benchmark does. Example:
|
||||
*
|
||||
* BENCHMARK(vectorPushBack) {
|
||||
* vector<int> v;
|
||||
* v.push_back(42);
|
||||
* }
|
||||
*
|
||||
* BENCHMARK(insertVectorBegin, n) {
|
||||
* vector<int> v;
|
||||
* FOR_EACH_RANGE (i, 0, n) {
|
||||
* v.insert(v.begin(), 42);
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
#define BENCHMARK(name, ...) \
|
||||
BENCHMARK_IMPL( \
|
||||
name, \
|
||||
FB_STRINGIZE(name), \
|
||||
FB_ARG_2_OR_1(1, ##__VA_ARGS__), \
|
||||
FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \
|
||||
__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Like BENCHMARK above, but allows the user to return the actual
|
||||
* number of iterations executed in the function body. This can be
|
||||
* useful if the benchmark function doesn't know upfront how many
|
||||
* iterations it's going to run or if it runs through a certain
|
||||
* number of test cases, e.g.:
|
||||
*
|
||||
* BENCHMARK_MULTI(benchmarkSomething) {
|
||||
* std::vector<int> testCases { 0, 1, 1, 2, 3, 5 };
|
||||
* for (int c : testCases) {
|
||||
* doSomething(c);
|
||||
* }
|
||||
* return testCases.size();
|
||||
* }
|
||||
*/
|
||||
#define BENCHMARK_MULTI(name, ...) \
|
||||
BENCHMARK_MULTI_IMPL( \
|
||||
name, \
|
||||
FB_STRINGIZE(name), \
|
||||
FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \
|
||||
__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Defines a benchmark that passes a parameter to another one. This is
|
||||
* common for benchmarks that need a "problem size" in addition to
|
||||
* "number of iterations". Consider:
|
||||
*
|
||||
* void pushBack(uint32_t n, size_t initialSize) {
|
||||
* vector<int> v;
|
||||
* BENCHMARK_SUSPEND {
|
||||
* v.resize(initialSize);
|
||||
* }
|
||||
* FOR_EACH_RANGE (i, 0, n) {
|
||||
* v.push_back(i);
|
||||
* }
|
||||
* }
|
||||
* BENCHMARK_PARAM(pushBack, 0)
|
||||
* BENCHMARK_PARAM(pushBack, 1000)
|
||||
* BENCHMARK_PARAM(pushBack, 1000000)
|
||||
*
|
||||
* The benchmark above estimates the speed of push_back at different
|
||||
* initial sizes of the vector. The framework will pass 0, 1000, and
|
||||
* 1000000 for initialSize, and the iteration count for n.
|
||||
*/
|
||||
#define BENCHMARK_PARAM(name, param) BENCHMARK_NAMED_PARAM(name, param, param)
|
||||
|
||||
/**
|
||||
* Same as BENCHMARK_PARAM, but allows one to return the actual number of
|
||||
* iterations that have been run.
|
||||
*/
|
||||
#define BENCHMARK_PARAM_MULTI(name, param) \
|
||||
BENCHMARK_NAMED_PARAM_MULTI(name, param, param)
|
||||
|
||||
/*
|
||||
* Like BENCHMARK_PARAM(), but allows a custom name to be specified for each
|
||||
* parameter, rather than using the parameter value.
|
||||
*
|
||||
* Useful when the parameter value is not a valid token for string pasting,
|
||||
* of when you want to specify multiple parameter arguments.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* void addValue(uint32_t n, int64_t bucketSize, int64_t min, int64_t max) {
|
||||
* Histogram<int64_t> hist(bucketSize, min, max);
|
||||
* int64_t num = min;
|
||||
* FOR_EACH_RANGE (i, 0, n) {
|
||||
* hist.addValue(num);
|
||||
* ++num;
|
||||
* if (num > max) { num = min; }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* BENCHMARK_NAMED_PARAM(addValue, 0_to_100, 1, 0, 100)
|
||||
* BENCHMARK_NAMED_PARAM(addValue, 0_to_1000, 10, 0, 1000)
|
||||
* BENCHMARK_NAMED_PARAM(addValue, 5k_to_20k, 250, 5000, 20000)
|
||||
*/
|
||||
#define BENCHMARK_NAMED_PARAM(name, param_name, ...) \
|
||||
BENCHMARK_IMPL( \
|
||||
FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \
|
||||
FB_STRINGIZE(name) "(" FB_STRINGIZE(param_name) ")", \
|
||||
iters, \
|
||||
unsigned, \
|
||||
iters) { \
|
||||
name(iters, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as BENCHMARK_NAMED_PARAM, but allows one to return the actual number
|
||||
* of iterations that have been run.
|
||||
*/
|
||||
#define BENCHMARK_NAMED_PARAM_MULTI(name, param_name, ...) \
|
||||
BENCHMARK_MULTI_IMPL( \
|
||||
FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \
|
||||
FB_STRINGIZE(name) "(" FB_STRINGIZE(param_name) ")", \
|
||||
unsigned, \
|
||||
iters) { \
|
||||
return name(iters, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Just like BENCHMARK, but prints the time relative to a
|
||||
* baseline. The baseline is the most recent BENCHMARK() seen in
|
||||
* the current scope. Example:
|
||||
*
|
||||
* // This is the baseline
|
||||
* BENCHMARK(insertVectorBegin, n) {
|
||||
* vector<int> v;
|
||||
* FOR_EACH_RANGE (i, 0, n) {
|
||||
* v.insert(v.begin(), 42);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* BENCHMARK_RELATIVE(insertListBegin, n) {
|
||||
* list<int> s;
|
||||
* FOR_EACH_RANGE (i, 0, n) {
|
||||
* s.insert(s.begin(), 42);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Any number of relative benchmark can be associated with a
|
||||
* baseline. Another BENCHMARK() occurrence effectively establishes a
|
||||
* new baseline.
|
||||
*/
|
||||
#define BENCHMARK_RELATIVE(name, ...) \
|
||||
BENCHMARK_IMPL( \
|
||||
name, \
|
||||
"%" FB_STRINGIZE(name), \
|
||||
FB_ARG_2_OR_1(1, ##__VA_ARGS__), \
|
||||
FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \
|
||||
__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Same as BENCHMARK_RELATIVE, but allows one to return the actual number
|
||||
* of iterations that have been run.
|
||||
*/
|
||||
#define BENCHMARK_RELATIVE_MULTI(name, ...) \
|
||||
BENCHMARK_MULTI_IMPL( \
|
||||
name, \
|
||||
"%" FB_STRINGIZE(name), \
|
||||
FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \
|
||||
__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* A combination of BENCHMARK_RELATIVE and BENCHMARK_PARAM.
|
||||
*/
|
||||
#define BENCHMARK_RELATIVE_PARAM(name, param) \
|
||||
BENCHMARK_RELATIVE_NAMED_PARAM(name, param, param)
|
||||
|
||||
/**
|
||||
* Same as BENCHMARK_RELATIVE_PARAM, but allows one to return the actual
|
||||
* number of iterations that have been run.
|
||||
*/
|
||||
#define BENCHMARK_RELATIVE_PARAM_MULTI(name, param) \
|
||||
BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(name, param, param)
|
||||
|
||||
/**
|
||||
* A combination of BENCHMARK_RELATIVE and BENCHMARK_NAMED_PARAM.
|
||||
*/
|
||||
#define BENCHMARK_RELATIVE_NAMED_PARAM(name, param_name, ...) \
|
||||
BENCHMARK_IMPL( \
|
||||
FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \
|
||||
"%" FB_STRINGIZE(name) "(" FB_STRINGIZE(param_name) ")", \
|
||||
iters, \
|
||||
unsigned, \
|
||||
iters) { \
|
||||
name(iters, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as BENCHMARK_RELATIVE_NAMED_PARAM, but allows one to return the
|
||||
* actual number of iterations that have been run.
|
||||
*/
|
||||
#define BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(name, param_name, ...) \
|
||||
BENCHMARK_MULTI_IMPL( \
|
||||
FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \
|
||||
"%" FB_STRINGIZE(name) "(" FB_STRINGIZE(param_name) ")", \
|
||||
unsigned, \
|
||||
iters) { \
|
||||
return name(iters, ##__VA_ARGS__); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a line of dashes.
|
||||
*/
|
||||
#define BENCHMARK_DRAW_LINE() \
|
||||
static bool FB_ANONYMOUS_VARIABLE(follyBenchmarkUnused) = \
|
||||
(::folly::addBenchmark(__FILE__, "-", []() -> unsigned { return 0; }), \
|
||||
true)
|
||||
|
||||
/**
|
||||
* Allows execution of code that doesn't count torward the benchmark's
|
||||
* time budget. Example:
|
||||
*
|
||||
* BENCHMARK_START_GROUP(insertVectorBegin, n) {
|
||||
* vector<int> v;
|
||||
* BENCHMARK_SUSPEND {
|
||||
* v.reserve(n);
|
||||
* }
|
||||
* FOR_EACH_RANGE (i, 0, n) {
|
||||
* v.insert(v.begin(), 42);
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
#define BENCHMARK_SUSPEND \
|
||||
if (auto FB_ANONYMOUS_VARIABLE(BENCHMARK_SUSPEND) = \
|
||||
::folly::BenchmarkSuspender()) { \
|
||||
} else
|
17
ios/Pods/Folly/folly/Bits.h
generated
17
ios/Pods/Folly/folly/Bits.h
generated
@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011-present Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <folly/lang/Bits.h> // @shim
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user