知乎 · 2016 下半年

2016 下半年 · 11 条
文章2016-11-26

#Android# 轮子杂评 1

工作中常常会用到一些开源的轮子,但真的是每个都如人气般质量高么?你真的敢用么?该系列文章为你揭开一些高人气轮子的内幕。

挺多 Star 的。但是首先该动画用 View 来实现就要扣分了,实际上像此类无交互(例如不需要处理 Touch 事件)的纯动画不应该使用 View 作为容器,直接用 Drawable 实现就行了(能避免基类问题)。再仔细看了下代码,妈蛋,直接是个 ViewGroup 然后内嵌几个子 View,通过 属性动画来实现的(用的还是使用反射的 ObjectAnimator),差评。

这个动画挺多的所以 Star 数也更多,但同样直接扔了个 View 出来(虽然你也可以用它内部的 Indicator,因为 Indicator 继承的 Drawable)。在 Indicator 里面借助多个 ValueAnimator 来实现数值变化,好处是用起来简单,不需要自己计算动画的中间值,而且可以使用 ArgbEvaluator 和 RectEvaluator 等这类系统提供的插值器。

然而但这并不是 Drawable 推荐实现动画的方法,更正确的做法是使用 scheduleSelf() 方法。可以参考这里 An example showing how to create and use a Drawable that animates. 虽然 ValueAnimator 和 scheduleSelf() 本质上都是通过线程内 Looper 的定时消息实现的,但是 scheduleSelf() 将动画的播放权交给所在的 View,View 可以决定是否播放 Drawable 的动画,而用 ValueAnimator 的话则需自己处理。

不至于太差,中评。(工作上要用到其中一个类似的动画,但个人最终还是没敢用这个轮子,自己造了)

♥ 68💬 27
文章2016-11-17

#Android# Docker 与自动化

前言

在之前写过的两篇文章中(如何组好队伍刷怪Android 与 Docker )粗略提到一些可以针对团队的提高开发效率、控制开发质量的工具,例如 Nexus 仓库和持续集成(CI)服务。

之前公司的代码托管在自己搭建的 Gitlab 上,所以当时 CI 服务用的是 Gitlab CI,无需自己搭建。而新公司代码托管在 Github 上,虽然有 Travis CI 等服务,但是仅对开源仓库免费,所以还是自己搭建好些。So 我选择了 Jenkins。

二进制仓库

都搞定后,我们工程中所有二进制都使用自己搭的私有仓库进行代理了,像这样:

buildscript {
    repositories {
        maven {
            url "${ZBD_NEXUS_REPO}/jcenter/"
        }
    }

    dependencies {
        // ...
    }
}

allprojects {
    repositories {
        maven {
            url "${ZBD_NEXUS_REPO}/jcenter/"
        }
        maven {
            url "${ZBD_NEXUS_REPO}/jitpack.io/"
        }
    }
}

它带来的好处是巨大的,一来是起到缓存的作用(要知道 Jcenter 或 Maven Central 在国内的访问速度有多慢)。不管是开发者还甚至是 CI 服务,只要缓存一次,再次请求时都是用的缓存,基本上几秒钟就能同步完整个工程用的所有二进制。

二来是一些私有的二进制也能上传到仓库上。例如我们公司的项目用到并修改了 ijkplayer 库,在我来之前,他们把 ijkplayer 的代码和编译好的二进制都扔进 Git 仓库中,导致我入职时 Git 仓库已经高达 4G 大小了。。。要知道,二进制这种东西是不应该出现在代码仓库中的。

好了,现在搭了 Nexus 后,先把 ijkplayer 那 Part 的代码从主项目中移出来,然后将编译生成的二进制自动上传到 Nexus 仓库上,主项目直接从上面 Pull 编译好的二进制。

持续集成

使用 Jenkins 提供的 CI 服务也给我们的开发带来了巨大收益:自动构建新的代码变动、自动设置 Github 的 Commit Status(构建失败的 Commit 无法混合进 Dev / Master 分支)。

在 Branchs 上可以查看所有分支的构建状况:

提交的 Pull Request 需要通过构建才能进行混合:

构建失败的 Commit 是无法混合进保护分支的:

利用持续集成服务还能实现更多的功能,例如构建成功后自动上传二进制、自动发邮件通知等,它实现了真正意义的 自动化

着手搭建

为什么用 Docker?只给你一台服务器,你能在一天内搭好所有东西么(笑?

下面的脚本记录我搭建的一些过程,其中 docker-android 是我自己写的镜像,包含以下工具和环境: Oracle Java 8 / Android Platform SDK 23 & 24 / Android Build Tools 23.0.3 & 24.0.3 / Pre-installed Gradle version 3.1

#!/bin/sh

# 此份脚本仅用于服务部署,如需进行服务迁移请使用 Docker 的容器备份功能
curl -sSL https://get.daocloud.io/docker | sh
service docker start
docker pull sonatype/nexus3
docker pull jenkins
docker pull daocloud.io/nekocode/docker-android:1.6

mkdir -p /hi/ci/android/jenkins-data

docker run -d \
    -p 8081:8081 \
    --name nexus \
    sonatype/nexus3
docker run -d \
    -u root \
    -v $(which docker):/usr/bin/docker \
    -v /hi/ci/android/jenkins-data/:/var/jenkins_home/workspace/ \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /usr/lib64/libsystemd-journal.so.0:/usr/lib/x86_64-linux-gnu/libsystemd-journal.so.0 \
    -v /usr/lib64/libsystemd-id128.so.0:/usr/lib/x86_64-linux-gnu/libsystemd-id128.so.0 \
    -v /usr/lib64/libdevmapper.so.1.02:/usr/lib/x86_64-linux-gnu/libdevmapper.so.1.02 \
    -v /usr/lib64/libgcrypt.so.11:/usr/lib/x86_64-linux-gnu/libgcrypt.so.11 \
    -v /usr/lib64/libdw.so.1:/usr/lib/x86_64-linux-gnu/libdw.so.1 \
    -p 8082:8080 \
    --name jenkins \
    jenkins 

在 Jenkins 下 Android 工程我也打算使用 Docker 来构建,所以要实现 Docker in docker 的功能,于是上面的脚本在开启 Jenkins 容器时必须做些挂载设置。而 Jenkins 中构建 Android 工程的命令可以写成这样:

docker run --rm -v /hi/ci/android/jenkins-data/:/workspace -w /workspace/${JOB_NAME} daocloud.io/nekocode/docker-android:1.6 gradle clean app:assembleRelease

如果签名使用的 Keystore 密码配置放在 Properties 文件中,而且加入了 .gitignore 列表的话(这是个好习惯),可以使用类似下面的命令在构建前自动在工作目录生成需要的 Properties 文件:

echo "KEY_ALIAS=xxx\nKEYSTORE_PASSWORD=xxx\nKEY_PASSWORD=xxx" > /var/jenkins_home/workspace/${JOB_NAME}/keystore.properties

对了,要想 Jenkins 从 Github 的私有仓库成功拉下代码的话,还得为 Jenkins 生成用于访问 Github 私有仓库的 SSH 密钥对(可以自由替换下面的 projectname 字符串):

docker exec -it -u root jenkins /bin/bash

# 生成 Github Deploy Key
ssh-keygen -t rsa -f id_rsa.projectname_android
chmod 755 *
su jenkins
eval "$(ssh-agent -s)"
ssh-add id_rsa.projectname_android

# 把 Public Key 设置到 Github repository 的 Deploy Key 中就行了
cat id_rsa.projectname_android.pub

另外,要让 Jenkins 将构建状态显示到 Github Commit Status 上的话还得在个人账户上生成个 Access Token,并在 Jenkins 配置好。

♥ 54💬 8
文章2016-10-12

#Android# Fragment Presenter

前言

用 Fragment 来实现 Presenter 我之前在这篇文章就已经提过了:#Android# 使用 Fragment 构建 Presenter - 『Android 还可以这样开发』 - 知乎专栏

这篇文章算是对之前的一些补充。

方案对比

看了一下目前的一些方案,包括:(以下称 屏幕旋转/意外回收时销毁时

使用 Fragment 的好处是可以同步 Activity 的生命周期方法,你可以很简单的将你老旧的代码从 Activity 迁移到 Fragment 上。

要注意在使用 Fragment 来实现 Presenter 时,setRetainInstance(true) 仅当当前 Fragment 为非 Nested 的时候才可用(只能为 Activity 的直接子 Fragment、不能为 Fragment 的子 Fagment),否则会报错。

所以说更推荐覆盖 onSaveInstanceState() 来保存数据,它在系统需要回收内存时能腾出更多的内存空间,不像其它方案一样依旧占用内存。

无法直接保存 Presenter 实例的话,需要注意在 Presenter 中进行的一些异步操作,它们在 Presenter 被回收时依然在后台运行,并且在异步操作中可能会访问 Presenter 内的一些属性,所以你可能需要在 Presenter 被回收时中止它们。

其实我是在说 ReactiveX Java。在我的项目中,基本上大部分异步操作都使用它来实现,所以我在 kotgo 中写了个 bindLifecycle() 拓展函数来将 Observable 的订阅绑定到 Presenter 的生命周期上,一旦 Presenter 被销毁时会及时终止异步流。

其它的问题暂时没有了。每个方案都各有优劣,得看具体场景。

想尝试 Kotlin 的伙伴可以看看 Kotgo 1.4 了:GitHub - nekocode/kotgo: ? An android development framework using MVP architecture on kotlin. 它已经被用在多款产品上(甚至有已上架的大型应用),也间接证明了 Kotlin 目前的稳定性。

♥ 25💬 5
回答2016-10-05

程序的漂亮界面怎么实现的?

  • 基于 GUI 的 OS 都会提供一套系统的 UI 接口(例如 Windows 的 GDI),你用这套东西搞出来的程序的 Style 会和系统高度一致。

另外一些基于系统 UI 接口的更高维度封装的框架或库也包含在上面(WPF、MFC、DirectUI)。

  • 而另外一些高维度的 UI 库是基于底层图形接口(DirectX、OpenGL)重新设计的,例如 QT。他们的好处是 Style 可以跨平台一致,而且因为提供的是更高层次的接口,所以通常写的代码也是可以跨平台的。

回到原题。怎么实现的?要么就用系统的 GUI 接口,要么就用底层的图形接口自己画咯(游戏 UI 都是这么搞的,但通常游戏 UI 也会有更高层封装的库。。)

♥ 3💬 9
回答2016-09-24

GitHub 上有哪些,简单、易学的 Python 项目?

单文件,第三方的豆瓣 FM 播放器。你可以学到如何使用 requests 进行模拟登陆和进行数据请求。可以学到如何使用 urwid 构建命令行 UI。

更重要的是,用来听听歌也还不错~

项目地址:

GitHub - nekocode/doubanfm-py: 第三方豆瓣 FM 红心频道播放器。

哦,还有个项目

GitHub - nekocode/zhihuSayHi: Say Hi to your new followers in Zhihu.

感兴趣的话可以尝试对我的知乎帐号点下关注(不是骗关注啦~),你会收到奇奇怪怪的东西哈哈(逃

♥ 18💬 17
回答2016-09-19

Bitmap和Drawable的区别,为什么要用bitmap?

可以简单地理解为 Bitmap 储存的是 像素信息,Drawable 储存的是 对 Canvas 的一系列操作

而 BitmapDrawable 储存的是「把 Bitmap 渲染到 Canvas 上」这个操作。

♥ 2💬 3
文章2016-09-19

#Kotlin# 一年の使用报告 - 函数式思想

前言

这是「一年の使用报告」系列的第二篇,上一篇可点击 一年の使用报告 - 类型设计 查看。

正文

关于函数式思想还可以拉上 C# 来讨论。C# 大体上和 Java 蛮相似的,它出现的动机也是为了和 Java 抗衡,但很可惜 .Net 的跨平台化并不像 Java 那样迅速而可靠,目前看来虽然微软已经在加快脚步,但明显已经错失了大多机会。

C# 在 v3.0 就已经开始支持 Lambda 语法了,而 Java 直到 Java 8 才开始支持 Lambda,可以看出虽然一开始大量借鉴了 Java 的思想,但在之后的发展上 C# 是超前于 Java 的。而作为现代编程语言的 Kotlin 对比起两位前辈来说要显得更为前卫些,在刚出生时已经融入了函数式、DSL、类型推导等特性。对比下三个语言的 Lambda 写法:

// Java 8
a.listen(context, e -> handler(e));

// C#
a.Listen(context, e => handler(e));

// Kotlin
a.listen(context) { e -> handler(e) }

个人比较喜欢 Kotlin 的写法,作为方法最后一位参数的 Lambda 表达式可以直接写在括号后,如果只有一位参数的话可以直接省略括号不写。

而 Kotlin 在高阶函数的声明上也比 Java,C# 要方便得多,支持形如 (Int)->Int 的 函数类型

// Java 中的高阶函数(接口表达)
public interface Test {
    int run(int a);
}
public void doTest(Test test, int a) {
    test.run(a);
}

// C# 中的高阶函数(委托表达)
public void DoTest(Func<int, int> test, int a)
{
    test(a);
}

// Kotlin 中的高阶函数(函数类型表达)
fun doTest(test: (Int)->Int, a: Int) {
    test(a)
}

传统命令式语言的代码大都由 表达式控制语句 组成,C# 和 Java 也在此类之中。

// Java 的条件语句
int a;
if (b > 0) {
    a = b;
} else {
    a = c;
}

而函数式语言的区别在于函数式语言中的 只有表达式,if else 等被纳入多元运算符。Kotlin 也吸收了这种思想,把 if else、when 等作为多元运算符。可以看出 Kotlin 比 Java 和 C# 更接近函数式思想。

// Kotlin 的 if else 表达式
val a = if (b > 0) b else c

// Kotlin 的 when 表达式
val t = when (x) {
    0, 1 -> true
    else -> false
}

另外,Kotlin 中的 Delegated Properties(例如 Lazy Delegate)也是函数式思想的表现,它能委托对变量的读写操作。例如下面的语句,只有在变量 a 第一次被访问时才计算结果。

// Kotlin 中的 lazy 代理
val a = lazy { 10 + 11 }

在函数式语言中,通常还提供了 对 Collection 进行操作的高阶函数集(map、filter 之类的方法)。C# 有 LINQ,而 Java 和 Kotlin 有 Streams。

// Kotlin 中的 Streams 接口
(1..10).asSequence().filter { 
    it > 5
}.forEach {
    System.out.print(it)
}

但要知道 Java 8 才有 Streams 接口,所以目前想在 Android 开发中用上 Streams 接口的最方便的方式依然是使用 Kotlin。

Update:感谢 @林育斌 的提醒,已将最后一个例子改为和 Java 中的 Streams 更相似的 Sequence 实现(惰性计算)。关于 Iterable 和 Sequence 之间的区别可以查看这里:StackOverflowSequence 和Iterable 具体的取舍需要看使用场景。

♥ 41💬 13
文章2016-08-25

#Android# 你还在写麻烦的 Adapter 么?

这期文章,推荐一个类库 ItemPool ,它能很大程度上减少你的 Adapter 数量,它是一个解耦实现,能把 ViewHolder/ItemView 解耦出来。请记住,这让所有 ItemView 变得可复用。

前言

自从 Android 的 RecyclerView 组件发布以来,RecyclerView 成为了 Android 开发中实现容器视图的首选。要实现一个 RecyclerView,必须为其提供一个 RecyclerView.Adapter。我们来看看一个普通 Adapter 的写法:

public class CommonAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private final List<String> list;

    public CommonAdapter(List<String> sampleData) {
        this.list = sampleData;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View item = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_test, parent, false);
        return new ViewHolder(item);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof ViewHolder) {
            ((ViewHolder) holder).bindData(list.get(position));
        }
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        private TextView text;

        public ViewHolder(View itemView) {
            super(itemView);
            text = (TextView) itemView.findViewById(R.id.textView);
        }

        public void bindData(String data) {
            text.setText(data);
        }
    }
}

这段代码已经十分简单了,但它目前只包含一种类型的 ItemView,当我们需要更多不同的 ItemView 时,需要添加新的 ViewHolder 并更改 onCreateViewHolder() 和 onBindViewHolder() 函数下的代码。

这暂且来说还没有什么大碍。但是当你要实现另外一个类似的 Adapter 时,假设里面有一些在旧 Adapter 中已经出现过的 ItemView,你会发现你没有办法复用之前写过的代码,你只能重新定义一个 Adapter 类并复制旧的 ViewHolder 代码到新的 Adapter 上。这执行起来十分机械,并且当某个 ItemView 发生更改时,你需要同步更改所有对应 Adapter 中的 ViewHolder。

我们需要做点工作把 ViewHolder 代码从 Adapter 中抽离并独立出来,而 ItemPool 能帮你完成这点工作。

ItemPool

使用 ItemPool 后,我们把写 ViewHolder 改变成了写 Item。不同的 Item 用来将不同类型的 Data 映射到相应的 ItemView 上。更重要的是,这些 Item 是可复用的,这意味着你在另外一个 RecyclerView 中也能直接使用该 Item。一个经典的 Item 如下:

public class TestItem extends Item<String> {
    TextView textView;

    @NonNull
    @Override
    public View onCreateItemView(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
        View itemView = inflater.inflate(R.layout.item_test, parent, false);
        textView = (TextView) itemView.findViewById(R.id.textView);
        return itemView;
    }

    @Override
    public void onBindItem(@NonNull final RecyclerView.ViewHolder holder, @NonNull String s, ItemEventHandler eventHandler) {
        textView.setText(s);
    }
}

上面除去 IDE 自动生成的代码,实际上仅仅需要自己写 5 行代码。这个 Item 通过 onCreateItemView() 函数来生成某个 ItemView,并挂钩了 String 这个数据类型,然后通过 onBindItem() 函数来将 String 类型的数据传递给 ItemView 进行视图更新。

好的,接下来我们可以给某个 RecyclerView 组装 Item 了,它看起来是这样的:

ItemPool items = new ItemPool();
items.addType(TestItem.class);
items.addType(TestItem2.class);

items.add(new Header());
items.add("A");
items.add("B");

items.attachTo(recyclerView);

可以看出来我们并不要再写 Adapter 了,甚至也不用额外定义一个 DataList 了,它通过以下的工作流程来生成对应的 ItemView:

简而言之就是将指定类型的数据用指定类型的 Item 来展示。这十分容易理解,你只要记住哪个 Item 对应哪个数据类型,并往 ItemPool 中塞数据就行了。

更详细的描述可以移步 README 文档。

末尾

当然,它并不是万能的,一些情况下依然推荐使用 Adapter 实现。但不管怎样,大多数情况下你都能用它,并且为你减少几杯咖啡的工作时间~

(≡ω≡.)

♥ 153💬 22
文章2016-07-26

Android 开发者们,Intel 搞了大新闻啦!

新闻新闻~Intel 最近搞出了个大新闻啦,Reddit 下已经开始火热讨论起来了:

什么鬼?Intel 也开始搞跨平台开发了?不过看这名字 Multi-OS Engine 感觉应该挺牛扳的。

?,来看看官网的介绍:

Time-saving, productivity technology to create Android* and iOS* apps

  • Use Java* coding to deliver native performance app
  • Ahead of time compilation to native code
  • Build, debug and deploy with Android Studio* integration

这意味着 可以使用 Java 开发 iOS 应用!并且能在在 Android Studio 中进行构建甚至调试应用!

OK,赶紧申请测试。

申请完测试后会提供插件的下载链接。下载并安装完插件后重启 Android Studio 就可以在菜单新建 Multi-OS Engine Project 工程了:

随便创建了个测试项目,但是我并没有看到所谓的:

  • Design native iOS* UI with extended Android Studio* UI designer capabilities

难道还在开发中?还是说我的使用姿势不正确??

点运行按钮会自动编译并在虚拟机中运行:

结语

整体走了一遍开发流程,体验还算 OK,在 iOS 虚拟机上也能流畅运行。不过看到生成的执行文件有 100 多 M 就有点萎了= =。

有人说还不如 Xamarin。不过说实话,WP 开发者也就那一丢丢人,切换 IDE 的成本还可以接受,但是一提到要切换语言那可不好商量啊。而 Intel 这次主动来亲和 Android 开发者说不定反而真能搞起来,基本和 Android 开发保持无缝的体验啊。

总而言之,可以期待 Release 版。

补充

官方 Sample 代码放上 Github 了 ,目前有 Java 和 Kotlin 版本的。从一些 Sample 中我们可以窥探到,我们可以把业务逻辑等可复用的代码写在一个 common lib 中,然后可以在 Android 和 iOS 中共同调用。

♥ 432💬 100
回答2016-07-26

你所知道的最 Hack 的解决问题的过程是?

这是个传奇的故事!做过 ACM 的人大概都知道:

Quake-III Arena (雷神之锤3)是90年代的经典游戏之一。该系列的游戏不但画面和内容不错,而且即使计算机配置低,也能极其流畅地运行。这要归功于它3D引擎的开发者约翰-卡马克(John Carmack)。事实上早在90年代初DOS时代,只要能在PC上搞个小动画都能让人惊叹一番的时候,John Carmack就推出了石破天惊的Castle Wolfstein, 然后再接再励,doom, doomII, Quake…每次都把3-D技术推到极致。他的3D引擎代码资极度高效,几乎是在压榨PC机的每条运算指令。当初MS的Direct3D也得听取他的意见,修改了不少API。

最近,QUAKE的开发商ID SOFTWARE 遵守GPL协议,公开了QUAKE-III的原代码,让世人有幸目睹Carmack传奇的3D引擎的原码。这是QUAKE-III原代码的下载地址:
http://www.fileshack.com/file.x?fid=7547

(下面是官方的下载网址,搜索 “quake3-1.32b-source.zip” 可以找到一大堆中文网页的。ftp://ftp.idsoftware.com/idstuff/source/quake3-1.32b-source.zip) )

我们知道,越底层的函数,调用越频繁。3D引擎归根到底还是数学运算。那么找到最底层的数学运算函数(在game/code/q_math.c), 必然是精心编写的。里面有很多有趣的函数,很多都令人惊奇,估计我们几年时间都学不完。在game/code/q_math.c里发现了这样一段代码。它的作用是将一个数开平方并取倒,经测试这段代码比(float)(1.0/sqrt(x))快4倍:

float Q_rsqrt( float number )
{
	long i;
	float x2, y;
	const float threehalfs = 1.5F;

	x2 = number * 0.5F;
	y   = number;
	i   = * ( long * ) &y;   // evil floating point bit level hacking
	i   = 0x5f3759df - ( i >> 1 ); // what the fuck?
	y   = * ( float * ) &i;
	y   = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
	// y   = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

	#ifndef Q3_VM
	#ifdef __linux__
		 assert( !isnan(y) ); // bk010122 - FPE?
	#endif
	#endif
	return y;
}  

函数返回1/sqrt(x),这个函数在图像处理中比sqrt(x)更有用。
注意到这个函数只用了一次叠代!(其实就是根本没用叠代,直接运算)。编译,实验,这个函数不仅工作的很好,而且比标准的sqrt()函数快4倍!要知道,编译器自带的函数,可是经过严格仔细的汇编优化的啊!
这个简洁的函数,最核心,也是最让人费解的,就是标注了“what the fuck?”的一句
i = 0x5f3759df - ( i » 1 );

再加上y = y * ( threehalfs - ( x2 * y * y ) );
两句话就完成了开方运算!而且注意到,核心那句是定点移位运算,速度极快!特别在很多没有乘法指令的RISC结构CPU上,这样做是极其高效的。

算法的原理其实不复杂,就是牛顿迭代法,用x-f(x)/f’(x)来不断的逼近f(x)=a的根。

没错,一般的求平方根都是这么循环迭代算的但是卡马克(quake3作者)真正牛B的地方是他选择了一个神秘的常数0x5f3759df 来计算那个猜测值,就是我们加注释的那一行,那一行算出的值非常接近1/sqrt(n),这样我们只需要2次牛顿迭代就可以达到我们所需要的精度。好吧如果这个还不算NB,接着看:

普渡大学的数学家Chris Lomont看了以后觉得有趣,决定要研究一下卡马克弄出来的这个猜测值有什么奥秘。Lomont也是个牛人,在精心研究之后从理论上也推导出一个最佳猜测值,和卡马克的数字非常接近, 0x5f37642f。卡马克真牛,他是外星人吗?

传奇并没有在这里结束。Lomont计算出结果以后非常满意,于是拿自己计算出的起始值和卡马克的神秘数字做比赛,看看谁的数字能够更快更精确的求得平方根。结果是卡马克赢了… 谁也不知道卡马克是怎么找到这个数字的。

最后Lomont怒了,采用暴力方法一个数字一个数字试过来,终于找到一个比卡马克数字要好上那么一丁点的数字,虽然实际上这两个数字所产生的结果非常近似,这个暴力得出的数字是0x5f375a86。

Lomont为此写下一篇论文,“Fast Inverse Square Root”。 论文下载地址:
http://www.math.purdue.edu/~clomont/Math/Papers/2003/InvSqrt.pdf
http://www.matrix67.com/data/InvSqrt.pdf

参考:<IEEE Standard 754 for Binary Floating-Point Arithmetic>

最后,给出最精简的1/sqrt()函数:

float InvSqrt(float x)
{
	float xhalf = 0.5f*x;
	int i = *(int*)&x; // get bits for floating VALUE 
	i = 0x5f375a86- (i>>1); // gives initial guess y0
	x = *(float*)&i; // convert bits BACK to float
	x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy
	return x;
}  

大家可以尝试在PC机、51、AVR、430、ARM、上面编译并实验,惊讶一下它的工作效率。

前两天有一则新闻,大意是说 Ryszard Sommefeldt 很久以前看到这么样的一段 code (可能出自 Quake III 的 source code):

float InvSqrt (float x) 
{
	float xhalf = 0.5f*x;
	int i = *(int*)&x;
	i = 0x5f3759df - (i>>1);
	x = *(float*)&i;
	x = x*(1.5f - xhalf*x*x);
	return x;
}

他一看之下惊为天人,想要拜见这位前辈高人,但是一路追寻下去却一直找不到人;同时间也有其他人在找,虽然也没找到出处,但是 Chris Lomont 写了一篇论文 (in PDF) 解析这段 code 的算法 (用的是 Newton’s Method,牛顿法;比较重要的是后半段讲到怎么找出神奇的 0x5f3759df 的)。
PS. 这个 function 之所以重要,是因为求 开根号倒数 这个动作在 3D 运算 (向量运算的部份) 里面常常会用到,如果你用最原始的 sqrt() 然后再倒数的话,速度比上面的这个版本大概慢了四倍吧… XD
PS2. 在他们追寻的过程中,有人提到一份叫做 MIT HACKMEM 的文件,这是 1970 年代的 MIT 强者们做的一些笔记 (hack memo),大部份是 algorithm,有些 code 是 PDP-10 asm 写的,另外有少数是 C code (有人整理了一份列表)

♥ 1💬 1
回答2016-07-09

如何评价动画电影《大鱼·海棠》?

某些场景和设定与宫崎骏、千与千寻有相似。阴婆这全场唯一表情最丰富的角色和汤婆婆有些像。

观影期间数次让我产生震撼的不是剧情,而是场景、音乐。反观千与千寻,一开始是通过场景让我震撼,到了后期完全是被剧情发展所吸引产生共鸣。

人设上完全败笔,毫无感染力全场死鱼脸的女主(让我莫明想起小时代),和小千对比下就发现两者对观众感染力的差距。

剧情全程是尿点,可以忽略剧情发展直接看场景片段。莫明奇妙(为剧情而剧情)、不够连贯的剧情发展,木纳的人设,偶尔来几句鸡汤台词,种种都让人觉得看着看着莫名尴尬……

票价值了,是场炫技大会,世间观也上足够宏伟,然而剧情感觉被架空了,空洞扭捏。

♥ 1💬 0